diff --git a/packages/nc-gui/components/cmd-k/index.vue b/packages/nc-gui/components/cmd-k/index.vue index e4584ecd8a..032c5069aa 100644 --- a/packages/nc-gui/components/cmd-k/index.vue +++ b/packages/nc-gui/components/cmd-k/index.vue @@ -40,10 +40,22 @@ const cmdInputEl = ref() const cmdInput = ref('') +const debouncedCmdInput = ref('') + const { user } = useGlobal() const selected = ref() +const cmdkActionsRef = ref() + +const cmdkActionSelectedRef = ref() + +const ACTION_HEIGHT = 48 + +const WRAPPER_HEIGHT = 300 + +const SCROLL_MARGIN = ACTION_HEIGHT / 2 + const { cmdPlaceholder, loadScope, cmdLoading } = useCommandPalette() const formattedData: ComputedRef<(CmdAction & { weight: number })[]> = computed(() => { @@ -56,7 +68,7 @@ const formattedData: ComputedRef<(CmdAction & { weight: number })[]> = computed( parent: el.parent || 'root', weight: commandScore( `${el.section}${el?.section === 'Views' && el?.is_default ? t('title.defaultView') : el.title}${el.keywords?.join()}`, - cmdInput.value, + debouncedCmdInput.value, ), }) } @@ -117,7 +129,7 @@ const actionList = computed(() => { return 0 }) return formattedData.value.filter((el) => { - if (cmdInput.value === '') { + if (debouncedCmdInput.value === '') { if (el.parent === activeScope.value) { if (!el.handler) { return isThereAnyActionInScope(el.id) @@ -138,7 +150,7 @@ const actionList = computed(() => { }) const searchedActionList = computed(() => { - if (cmdInput.value === '') return actionList.value + if (debouncedCmdInput.value === '') return actionList.value actionList.value.sort((a, b) => { if (a.weight > b.weight) return -1 if (a.weight < b.weight) return 1 @@ -149,49 +161,90 @@ const searchedActionList = computed(() => { .sort((a, b) => b.section?.toLowerCase().localeCompare(a.section?.toLowerCase() as string) || 0) }) -const actionListGroupedBySection = computed(() => { - const rt: { [key: string]: CmdAction[] } = {} +const visibleSections = computed(() => { + const sections: string[] = [] searchedActionList.value.forEach((el) => { - if (el.section === 'hidden') return - if (el.section) { - if (!rt[el.section]) rt[el.section] = [] - rt[el.section].push(el) - } else { - if (!rt.default) rt.default = [] - rt.default.push(el) + if (el.section && !sections.includes(el.section)) { + sections.push(el.section) } }) + return sections +}) + +const actionListNormalized = computed(() => { + const rt: (CmdAction | { sectionTitle: string })[] = [] + visibleSections.value.forEach((el) => { + rt.push({ sectionTitle: el || 'default' }) + rt.push(...searchedActionList.value.filter((el2) => el2.section === el)) + }) return rt }) +const { list, containerProps, wrapperProps } = useVirtualList(actionListNormalized, { + itemHeight: ACTION_HEIGHT, +}) + const keys = useMagicKeys() const shiftModifier = keys.shift const setAction = (action: string) => { + const oldActionIndex = searchedActionList.value.findIndex((el) => el.id === selected.value) selected.value = action nextTick(() => { const actionIndex = searchedActionList.value.findIndex((el) => el.id === action) if (actionIndex === -1) return + if (actionIndex === 0) { - document.querySelector('.cmdk-actions')?.scrollTo({ top: 0, behavior: 'smooth' }) + containerProps.ref.value?.scrollTo({ top: 0, behavior: 'smooth' }) + return } else if (actionIndex === searchedActionList.value.length - 1) { - document.querySelector('.cmdk-actions')?.scrollTo({ top: 999999, behavior: 'smooth' }) - } else { - document.querySelector('.cmdk-action.selected')?.scrollIntoView({ + containerProps.ref.value?.scrollTo({ + top: actionIndex * ACTION_HEIGHT, behavior: 'smooth', - block: 'nearest', }) + return } - }) -} -const selectFirstAction = () => { - if (searchedActionList.value.length > 0) { - setAction(searchedActionList.value[0].id) - } else { - selected.value = undefined - } + // check if selected action rendered + const actionEl = Array.isArray(cmdkActionSelectedRef.value) ? cmdkActionSelectedRef.value[0] : cmdkActionSelectedRef.value + + if (!actionEl || !actionEl.classList?.contains('selected')) { + // if above the old selected action + if (actionIndex < oldActionIndex) { + containerProps.ref.value?.scrollTo({ top: (actionIndex + 1) * ACTION_HEIGHT - SCROLL_MARGIN, behavior: 'smooth' }) + } else { + containerProps.ref.value?.scrollTo({ + top: (actionIndex + 2) * ACTION_HEIGHT - WRAPPER_HEIGHT + SCROLL_MARGIN, + behavior: 'smooth', + }) + } + return + } + + // count sections before the selected action + const sectionBefore = visibleSections.value.findIndex((el) => el === searchedActionList.value[actionIndex].section) + 1 + + // check if selected action is visible in the list + const actionRect = actionEl?.getBoundingClientRect() + const listRect = cmdkActionsRef.value?.getBoundingClientRect() + if (actionRect && listRect) { + if (actionRect.top < listRect.top || actionRect.bottom > listRect.bottom) { + // if above the old selected action + if (actionIndex < oldActionIndex) { + containerProps.ref.value?.scrollTo({ + top: (actionIndex + sectionBefore) * ACTION_HEIGHT - SCROLL_MARGIN, + behavior: 'smooth', + }) + } else { + containerProps.ref.value?.scrollTo({ + top: (actionIndex + 1 + sectionBefore) * ACTION_HEIGHT - WRAPPER_HEIGHT + SCROLL_MARGIN, + behavior: 'smooth', + }) + } + } + } + }) } const setScope = (scope: string) => { @@ -201,7 +254,6 @@ const setScope = (scope: string) => { nextTick(() => { cmdInputEl.value?.focus() - selectFirstAction() }) } @@ -241,6 +293,22 @@ const fireAction = (action: CmdAction, preview = false) => { } } +const updateDebouncedInput = useDebounceFn(() => { + debouncedCmdInput.value = cmdInput.value + + nextTick(() => { + cmdInputEl.value?.focus() + }) +}, 100) + +watch(cmdInput, () => { + if (cmdInput.value === '') { + debouncedCmdInput.value = '' + } else { + updateDebouncedInput() + } +}) + whenever(keys.ctrl_k, () => { show() }) @@ -312,6 +380,12 @@ onClickOutside(modalEl, () => { if (vOpen.value) hide() }) +watch(searchedActionList, () => { + if (searchedActionList.value.length > 0) { + setAction(searchedActionList.value[0].id) + } +}) + defineExpose({ open: show, close: hide, @@ -382,18 +456,11 @@ defineExpose({ / - +
-
+
@@ -403,73 +470,92 @@ defineExpose({