summaryrefslogtreecommitdiff
path: root/packages/frontend/src/components/MkPostForm.vue
diff options
context:
space:
mode:
Diffstat (limited to 'packages/frontend/src/components/MkPostForm.vue')
-rw-r--r--packages/frontend/src/components/MkPostForm.vue97
1 files changed, 80 insertions, 17 deletions
diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue
index cd4fabea02..46893a0752 100644
--- a/packages/frontend/src/components/MkPostForm.vue
+++ b/packages/frontend/src/components/MkPostForm.vue
@@ -72,24 +72,29 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags">
<XPostFormAttaches v-model="files" @detach="detachFile" @changeSensitive="updateFileSensitive" @changeName="updateFileName"/>
+ <div v-if="uploader.items.value.length > 0" style="padding: 12px;">
+ <MkTip k="postFormUploader">
+ {{ i18n.ts._postForm.uploaderTip }}
+ </MkTip>
+ <MkUploaderItems :items="uploader.items.value" @showMenu="(item, ev) => showPerUploadItemMenu(item, ev)" @showMenuViaContextmenu="(item, ev) => showPerUploadItemMenuViaContextmenu(item, ev)"/>
+ </div>
<MkPollEditor v-if="poll" v-model="poll" @destroyed="poll = null"/>
<MkNotePreview v-if="showPreview" :class="$style.preview" :text="text" :files="files" :poll="poll ?? undefined" :useCw="useCw" :cw="cw" :user="postAccount ?? $i"/>
<div v-if="showingOptions" style="padding: 8px 16px;">
</div>
<footer :class="$style.footer">
<div :class="$style.footerLeft">
- <button v-tooltip="i18n.ts.attachFile" class="_button" :class="$style.footerButton" @click="chooseFileFrom"><i class="ti ti-photo-plus"></i></button>
+ <button v-tooltip="i18n.ts.attachFile + ' (' + i18n.ts.upload + ')'" class="_button" :class="$style.footerButton" @click="chooseFileFromPc"><i class="ti ti-photo-plus"></i></button>
+ <button v-tooltip="i18n.ts.attachFile + ' (' + i18n.ts.fromDrive + ')'" class="_button" :class="$style.footerButton" @click="chooseFileFromDrive"><i class="ti ti-cloud-download"></i></button>
<button v-tooltip="i18n.ts.poll" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: poll }]" @click="togglePoll"><i class="ti ti-chart-arrows"></i></button>
<button v-tooltip="i18n.ts.useCw" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: useCw }]" @click="useCw = !useCw"><i class="ti ti-eye-off"></i></button>
- <button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ti ti-at"></i></button>
<button v-tooltip="i18n.ts.hashtags" class="_button" :class="[$style.footerButton, { [$style.footerButtonActive]: withHashtags }]" @click="withHashtags = !withHashtags"><i class="ti ti-hash"></i></button>
- <button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugins" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
- <button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
+ <button v-tooltip="i18n.ts.mention" class="_button" :class="$style.footerButton" @click="insertMention"><i class="ti ti-at"></i></button>
<button v-if="showAddMfmFunction" v-tooltip="i18n.ts.addMfmFunction" :class="['_button', $style.footerButton]" @click="insertMfmFunction"><i class="ti ti-palette"></i></button>
+ <button v-if="postFormActions.length > 0" v-tooltip="i18n.ts.plugins" class="_button" :class="$style.footerButton" @click="showActions"><i class="ti ti-plug"></i></button>
</div>
<div :class="$style.footerRight">
- <button v-tooltip="i18n.ts.previewNoteText" class="_button" :class="[$style.footerButton, { [$style.previewButtonActive]: showPreview }]" @click="showPreview = !showPreview"><i class="ti ti-eye"></i></button>
- <!--<button v-tooltip="i18n.ts.more" class="_button" :class="$style.footerButton" @click="showingOptions = !showingOptions"><i class="ti ti-dots"></i></button>-->
+ <button v-tooltip="i18n.ts.emoji" :class="['_button', $style.footerButton]" @click="insertEmoji"><i class="ti ti-mood-happy"></i></button>
</div>
</footer>
<datalist id="hashtags">
@@ -105,10 +110,12 @@ import * as Misskey from 'misskey-js';
import insertTextAtCursor from 'insert-text-at-cursor';
import { toASCII } from 'punycode.js';
import { host, url } from '@@/js/config.js';
+import MkUploaderItems from './MkUploaderItems.vue';
import type { ShallowRef } from 'vue';
import type { PostFormProps } from '@/types/post-form.js';
import type { MenuItem } from '@/types/menu.js';
import type { PollEditorModelValue } from '@/components/MkPollEditor.vue';
+import type { UploaderItem } from '@/composables/use-uploader.js';
import MkNotePreview from '@/components/MkNotePreview.vue';
import XPostFormAttaches from '@/components/MkPostFormAttaches.vue';
import XTextCounter from '@/components/MkPostForm.TextCounter.vue';
@@ -120,7 +127,7 @@ import { formatTimeString } from '@/utility/format-time-string.js';
import { Autocomplete } from '@/utility/autocomplete.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js';
-import { selectFile } from '@/utility/drive.js';
+import { chooseDriveFile } from '@/utility/drive.js';
import { store } from '@/store.js';
import MkInfo from '@/components/MkInfo.vue';
import { i18n } from '@/i18n.js';
@@ -138,6 +145,7 @@ import { getPluginHandlers } from '@/plugin.js';
import { DI } from '@/di.js';
import { globalEvents } from '@/events.js';
import { checkDragDataType, getDragData } from '@/drag-and-drop.js';
+import { useUploader } from '@/composables/use-uploader.js';
const $i = ensureSignin();
@@ -201,6 +209,15 @@ const justEndedComposition = ref(false);
const renoteTargetNote: ShallowRef<PostFormProps['renote'] | null> = shallowRef(props.renote);
const postFormActions = getPluginHandlers('post_form_action');
+const uploader = useUploader({
+ multiple: true,
+});
+
+uploader.events.on('itemUploaded', ctx => {
+ files.value.push(ctx.item.uploaded!);
+ uploader.removeItem(ctx.item);
+});
+
const draftKey = computed((): string => {
let key = props.channel ? `channel:${props.channel.id}` : '';
@@ -258,10 +275,11 @@ const cwTextLength = computed((): number => {
const maxCwTextLength = 100;
const canPost = computed((): boolean => {
- return !props.mock && !posting.value && !posted.value &&
+ return !props.mock && !posting.value && !posted.value && !uploader.uploading.value && (uploader.items.value.length === 0 || uploader.readyForUpload.value) &&
(
1 <= textLength.value ||
1 <= files.value.length ||
+ 1 <= uploader.items.value.length ||
poll.value != null ||
renoteTargetNote.value != null ||
quoteId.value != null
@@ -434,17 +452,20 @@ function focus() {
}
}
-function chooseFileFrom(ev) {
+function chooseFileFromPc(ev: MouseEvent) {
if (props.mock) return;
- selectFile({
- anchorElement: ev.currentTarget ?? ev.target,
- multiple: true,
- label: i18n.ts.attachFile,
- }).then(files_ => {
- for (const file of files_) {
- files.value.push(file);
- }
+ os.chooseFileFromPc({ multiple: true }).then(files => {
+ if (files.length === 0) return;
+ uploader.addFiles(files);
+ });
+}
+
+function chooseFileFromDrive(ev: MouseEvent) {
+ if (props.mock) return;
+
+ chooseDriveFile({ multiple: true }).then(driveFiles => {
+ files.value.push(...driveFiles);
});
}
@@ -571,6 +592,10 @@ function showOtherSettings() {
toggleReactionAcceptance();
},
}, { type: 'divider' }, {
+ type: 'switch',
+ text: i18n.ts.preview,
+ ref: showPreview,
+ }, {
icon: 'ti ti-trash',
text: i18n.ts.reset,
danger: true,
@@ -797,6 +822,15 @@ function isAnnoying(text: string): boolean {
text.includes('$[position');
}
+async function uploadFiles() {
+ await uploader.upload();
+
+ for (const uploadedItem of uploader.items.value.filter(x => x.uploaded != null)) {
+ files.value.push(uploadedItem.uploaded!);
+ uploader.removeItem(uploadedItem);
+ }
+}
+
async function post(ev?: MouseEvent) {
if (ev) {
const el = (ev.currentTarget ?? ev.target) as HTMLElement | null;
@@ -840,6 +874,10 @@ async function post(ev?: MouseEvent) {
}
}
+ if (uploader.items.value.some(x => x.uploaded == null)) {
+ await uploadFiles();
+ }
+
let postData = {
text: text.value === '' ? null : text.value,
fileIds: files.value.length > 0 ? files.value.map(f => f.id) : undefined,
@@ -1043,6 +1081,16 @@ function openAccountMenu(ev: MouseEvent) {
}, ev);
}
+function showPerUploadItemMenu(item: UploaderItem, ev: MouseEvent) {
+ const menu = uploader.getMenu(item);
+ os.popupMenu(menu, ev.currentTarget ?? ev.target);
+}
+
+function showPerUploadItemMenuViaContextmenu(item: UploaderItem, ev: MouseEvent) {
+ const menu = uploader.getMenu(item);
+ os.contextMenu(menu, ev);
+}
+
onMounted(() => {
if (props.autofocus) {
focus();
@@ -1111,8 +1159,23 @@ onMounted(() => {
});
});
+async function canClose() {
+ if (!uploader.allItemsUploaded.value) {
+ const { canceled } = await os.confirm({
+ type: 'question',
+ text: i18n.ts._postForm.quitInspiteOfThereAreUnuploadedFilesConfirm,
+ okText: i18n.ts.yes,
+ cancelText: i18n.ts.no,
+ });
+ if (canceled) return false;
+ }
+
+ return true;
+}
+
defineExpose({
clear,
+ canClose,
});
</script>