diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index 52aba58455..09d530c4ea 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -56,7 +56,7 @@ </template> <script lang="ts" setup> -import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, watch } from 'vue'; +import { defineAsyncComponent, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'; import { focusPrev, focusNext } from '@/scripts/focus'; import MkSwitch from '@/components/MkSwitch.vue'; import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from '@/types/menu'; @@ -111,11 +111,11 @@ watch(() => props.items, () => { immediate: true, }); -let childMenu = $ref<MenuItem[] | null>(); +let childMenu = ref<MenuItem[] | null>(); let childTarget = $shallowRef<HTMLElement | null>(); function closeChild() { - childMenu = null; + childMenu.value = null; childShowingItem = null; } @@ -140,13 +140,31 @@ function onItemMouseLeave(item) { if (childCloseTimer) window.clearTimeout(childCloseTimer); } +let childrenCache = new WeakMap(); async function showChildren(item: MenuItem, ev: MouseEvent) { + const children = ref([]); + if (childrenCache.has(item)) { + children.value = childrenCache.get(item); + } else { + if (typeof item.children === 'function') { + children.value = [{ + type: 'pending', + }]; + item.children().then(x => { + children.value = x; + childrenCache.set(item, x); + }); + } else { + children.value = item.children; + } + } + if (props.asDrawer) { - os.popupMenu(item.children, ev.currentTarget ?? ev.target); + os.popupMenu(children, ev.currentTarget ?? ev.target); close(); } else { childTarget = ev.currentTarget ?? ev.target; - childMenu = item.children; + childMenu = children; childShowingItem = item; } } diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index 9da7447bfd..f732c259fb 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -99,66 +99,6 @@ export function getNoteMenu(props: { }); } - async function clip(): Promise<void> { - const clips = await os.api('clips/list'); - os.popupMenu([{ - icon: 'ti ti-plus', - text: i18n.ts.createNew, - action: async () => { - const { canceled, result } = await os.form(i18n.ts.createNewClip, { - name: { - type: 'string', - label: i18n.ts.name, - }, - description: { - type: 'string', - required: false, - multiline: true, - label: i18n.ts.description, - }, - isPublic: { - type: 'boolean', - label: i18n.ts.public, - default: false, - }, - }); - if (canceled) return; - - const clip = await os.apiWithDialog('clips/create', result); - - claimAchievement('noteClipped1'); - os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); - }, - }, null, ...clips.map(clip => ({ - text: clip.name, - action: () => { - claimAchievement('noteClipped1'); - os.promiseDialog( - os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }), - null, - async (err) => { - if (err.id === '734806c4-542c-463a-9311-15c512803965') { - const confirm = await os.confirm({ - type: 'warning', - text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }), - }); - if (!confirm.canceled) { - os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id }); - if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true; - } - } else { - os.alert({ - type: 'error', - text: err.message + '\n' + err.id, - }); - } - }, - ); - }, - }))], props.menuButton.value, { - }).then(focus); - } - async function unclip(): Promise<void> { os.apiWithDialog('clips/remove-note', { clipId: props.currentClipPage.value.id, noteId: appearNote.id }); props.isDeleted.value = true; @@ -264,9 +204,67 @@ export function getNoteMenu(props: { action: () => toggleFavorite(true), }), { + type: 'parent', icon: 'ti ti-paperclip', text: i18n.ts.clip, - action: () => clip(), + children: async () => { + const clips = await os.api('clips/list'); + return [{ + icon: 'ti ti-plus', + text: i18n.ts.createNew, + action: async () => { + const { canceled, result } = await os.form(i18n.ts.createNewClip, { + name: { + type: 'string', + label: i18n.ts.name, + }, + description: { + type: 'string', + required: false, + multiline: true, + label: i18n.ts.description, + }, + isPublic: { + type: 'boolean', + label: i18n.ts.public, + default: false, + }, + }); + if (canceled) return; + + const clip = await os.apiWithDialog('clips/create', result); + + claimAchievement('noteClipped1'); + os.apiWithDialog('clips/add-note', { clipId: clip.id, noteId: appearNote.id }); + }, + }, null, ...clips.map(clip => ({ + text: clip.name, + action: () => { + claimAchievement('noteClipped1'); + os.promiseDialog( + os.api('clips/add-note', { clipId: clip.id, noteId: appearNote.id }), + null, + async (err) => { + if (err.id === '734806c4-542c-463a-9311-15c512803965') { + const confirm = await os.confirm({ + type: 'warning', + text: i18n.t('confirmToUnclipAlreadyClippedNote', { name: clip.name }), + }); + if (!confirm.canceled) { + os.apiWithDialog('clips/remove-note', { clipId: clip.id, noteId: appearNote.id }); + if (props.currentClipPage?.value.id === clip.id) props.isDeleted.value = true; + } + } else { + os.alert({ + type: 'error', + text: err.message + '\n' + err.id, + }); + } + }, + ); + }, + }))]; + }, }, statePromise.then(state => state.isMutedThread ? { icon: 'ti ti-message-off',