diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d246953d91..d687151a6d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,8 @@ - ### Client -- +- Enhance: 投稿フォームの下書き・予約投稿のメニューを整理 + - 投稿フォーム左上からアクセスできた、下書き・予約投稿の一覧ボタンは、右上の「…」メニュー内に移動しました ### Server - diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 26ef054b9f1..37f1461c6e5 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1330,6 +1330,7 @@ federationSpecified: "このサーバーはホワイトリスト連合で運用 federationDisabled: "このサーバーは連合が無効化されています。他のサーバーのユーザーとやり取りすることはできません。" draft: "下書き" draftsAndScheduledNotes: "下書きと予約投稿" +scheduledNotes: "予約投稿" confirmOnReact: "リアクションする際に確認する" reactAreYouSure: "\" {emoji} \" をリアクションしますか?" markAsSensitiveConfirm: "このメディアをセンシティブとして設定しますか?" @@ -2695,6 +2696,7 @@ _postForm: replyPlaceholder: "このノートに返信..." quotePlaceholder: "このノートを引用..." channelPlaceholder: "チャンネルに投稿..." + postAccount: "投稿するアカウント" showHowToUse: "フォームの説明を表示" _howToUse: content_title: "本文" @@ -2702,7 +2704,7 @@ _postForm: toolbar_title: "ツールバー" toolbar_description: "ファイルやアンケートの添付、注釈やハッシュタグの設定、絵文字やメンションの挿入などが行えます。" account_title: "アカウントメニュー" - account_description: "投稿するアカウントを切り替えたり、アカウントに保存した下書き・予約投稿を一覧できます。" + account_description: "投稿するアカウントを切り替えることができます。" visibility_title: "公開範囲" visibility_description: "ノートを公開する範囲の設定が行えます。" menu_title: "メニュー" diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index d7092860413..236e3f3d785 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -636,6 +636,63 @@ function showOtherSettings() { break; } + function showDraftsDialog(scheduled: boolean) { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), { + scheduled, + }, { + restore: async (draft: Misskey.entities.NoteDraft) => { + text.value = draft.text ?? ''; + useCw.value = draft.cw != null; + cw.value = draft.cw ?? null; + visibility.value = draft.visibility; + localOnly.value = draft.localOnly ?? false; + files.value = draft.files ?? []; + hashtags.value = draft.hashtag ?? ''; + if (draft.hashtag) withHashtags.value = true; + if (draft.poll) { + // 投票を一時的に空にしないと反映されないため + poll.value = null; + nextTick(() => { + poll.value = { + choices: draft.poll!.choices, + multiple: draft.poll!.multiple, + expiresAt: draft.poll!.expiresAt ? (new Date(draft.poll!.expiresAt)).getTime() : null, + expiredAfter: null, + }; + }); + } + if (draft.visibleUserIds) { + misskeyApi('users/show', { userIds: draft.visibleUserIds }).then(users => { + users.forEach(u => pushVisibleUser(u)); + }); + } + quoteId.value = draft.renoteId ?? null; + renoteTargetNote.value = draft.renote; + replyTargetNote.value = draft.reply; + reactionAcceptance.value = draft.reactionAcceptance; + scheduledAt.value = draft.scheduledAt ?? null; + if (draft.channel) targetChannel.value = draft.channel as unknown as Misskey.entities.Channel; + + visibleUsers.value = []; + draft.visibleUserIds?.forEach(uid => { + if (!visibleUsers.value.some(u => u.id === uid)) { + misskeyApi('users/show', { userId: uid }).then(user => { + pushVisibleUser(user); + }); + } + }); + + serverDraftId.value = draft.id; + }, + cancel: () => { + + }, + closed: () => { + dispose(); + }, + }); + } + const menuItems = [{ type: 'component', component: XTextCounter, @@ -650,25 +707,49 @@ function showOtherSettings() { toggleReactionAcceptance(); }, }, { type: 'divider' }, { - type: 'button', - text: i18n.ts._drafts.saveToDraft, - icon: 'ti ti-cloud-upload', - action: async () => { - if (!canSaveAsServerDraft.value) { - return os.alert({ - type: 'error', - text: i18n.ts._drafts.cannotCreateDraft, - }); - } - saveServerDraft(); - }, - }, ...($i.policies.scheduledNoteLimit > 0 ? [{ + type: 'parent', + icon: 'ti ti-cloud', + text: i18n.ts.drafts, + children: [{ + type: 'button', + text: i18n.ts._drafts.listDrafts, + icon: 'ti ti-cloud-download', + action: () => { + showDraftsDialog(false); + }, + }, { + type: 'button', + text: i18n.ts._drafts.saveToDraft, + icon: 'ti ti-cloud-upload', + action: async () => { + if (!canSaveAsServerDraft.value) { + return os.alert({ + type: 'error', + text: i18n.ts._drafts.cannotCreateDraft, + }); + } + saveServerDraft(); + }, + }], + }, { + type: 'parent', icon: 'ti ti-calendar-time', - text: i18n.ts.schedulePost + '...', - action: () => { - schedule(); - }, - }] : []), { type: 'divider' }, { + text: i18n.ts.scheduledNotes, + children: [{ + type: 'button', + text: i18n.ts._drafts.listScheduledNotes, + icon: 'ti ti-clock-down', + action: () => { + showDraftsDialog(true); + }, + }, ...($i.policies.scheduledNoteLimit > 0 ? [{ + icon: 'ti ti-calendar-time', + text: i18n.ts.schedulePost, + action: () => { + schedule(); + }, + }] : [])], + }, { type: 'divider' }, { type: 'switch', icon: 'ti ti-eye', text: i18n.ts.preview, @@ -1253,63 +1334,6 @@ const postAccount = ref(null); async function openAccountMenu(ev: PointerEvent) { if (props.mock) return; - function showDraftsDialog(scheduled: boolean) { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkNoteDraftsDialog.vue')), { - scheduled, - }, { - restore: async (draft: Misskey.entities.NoteDraft) => { - text.value = draft.text ?? ''; - useCw.value = draft.cw != null; - cw.value = draft.cw ?? null; - visibility.value = draft.visibility; - localOnly.value = draft.localOnly ?? false; - files.value = draft.files ?? []; - hashtags.value = draft.hashtag ?? ''; - if (draft.hashtag) withHashtags.value = true; - if (draft.poll) { - // 投票を一時的に空にしないと反映されないため - poll.value = null; - nextTick(() => { - poll.value = { - choices: draft.poll!.choices, - multiple: draft.poll!.multiple, - expiresAt: draft.poll!.expiresAt ? (new Date(draft.poll!.expiresAt)).getTime() : null, - expiredAfter: null, - }; - }); - } - if (draft.visibleUserIds) { - misskeyApi('users/show', { userIds: draft.visibleUserIds }).then(users => { - users.forEach(u => pushVisibleUser(u)); - }); - } - quoteId.value = draft.renoteId ?? null; - renoteTargetNote.value = draft.renote; - replyTargetNote.value = draft.reply; - reactionAcceptance.value = draft.reactionAcceptance; - scheduledAt.value = draft.scheduledAt ?? null; - if (draft.channel) targetChannel.value = draft.channel as unknown as Misskey.entities.Channel; - - visibleUsers.value = []; - draft.visibleUserIds?.forEach(uid => { - if (!visibleUsers.value.some(u => u.id === uid)) { - misskeyApi('users/show', { userId: uid }).then(user => { - pushVisibleUser(user); - }); - } - }); - - serverDraftId.value = draft.id; - }, - cancel: () => { - - }, - closed: () => { - dispose(); - }, - }); - } - const items = await getAccountMenu({ withExtraOperation: false, includeCurrentAccount: true, @@ -1324,20 +1348,9 @@ async function openAccountMenu(ev: PointerEvent) { }); os.popupMenu([{ - type: 'button', - text: i18n.ts._drafts.listDrafts, - icon: 'ti ti-cloud-download', - action: () => { - showDraftsDialog(false); - }, - }, { - type: 'button', - text: i18n.ts._drafts.listScheduledNotes, - icon: 'ti ti-clock-down', - action: () => { - showDraftsDialog(true); - }, - }, { type: 'divider' }, ...items], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); + type: 'label', + text: i18n.ts._postForm.postAccount, + }, ...items], (ev.currentTarget ?? ev.target ?? undefined) as HTMLElement | undefined); } function showPerUploadItemMenu(item: UploaderItem, ev: PointerEvent) { diff --git a/packages/i18n/src/autogen/locale.ts b/packages/i18n/src/autogen/locale.ts index e13458c22d3..0a5864a2a01 100644 --- a/packages/i18n/src/autogen/locale.ts +++ b/packages/i18n/src/autogen/locale.ts @@ -5332,6 +5332,10 @@ export interface Locale extends ILocale { * 下書きと予約投稿 */ "draftsAndScheduledNotes": string; + /** + * 予約投稿 + */ + "scheduledNotes": string; /** * リアクションする際に確認する */ @@ -10222,6 +10226,10 @@ export interface Locale extends ILocale { * チャンネルに投稿... */ "channelPlaceholder": string; + /** + * 投稿するアカウント + */ + "postAccount": string; /** * フォームの説明を表示 */ @@ -10248,7 +10256,7 @@ export interface Locale extends ILocale { */ "account_title": string; /** - * 投稿するアカウントを切り替えたり、アカウントに保存した下書き・予約投稿を一覧できます。 + * 投稿するアカウントを切り替えることができます。 */ "account_description": string; /**