Files
nocodb/packages/nc-gui/components/cell/RichText/SelectedBubbleMenuPopup.vue
2026-03-07 13:46:49 +05:30

104 lines
2.8 KiB
Vue

<script lang="ts" setup>
import type { Editor } from '@tiptap/vue-3'
import { BubbleMenu } from '@tiptap/vue-3'
import type { RichTextBubbleMenuOptions } from '#imports'
interface Props {
editor: Editor
hideMention?: boolean
hideOnSelectAllSortcut?: boolean
isFormField?: boolean
hiddenOptions?: RichTextBubbleMenuOptions[]
}
const props = withDefaults(defineProps<Props>(), {
isFormField: false,
hiddenOptions: () => [],
})
const editor = computed(() => props.editor)
const isSelectAllShortcut = ref(false)
// Debounce show menu to prevent flickering
const showMenu = computed(() => {
if (!editor || isSelectAllShortcut.value) return false
const selection = editor.value.state.selection
const selectedNode = selection && 'node' in selection ? (selection as any).node : null
return !editor.value.state.selection.empty && selectedNode?.type?.name !== 'image'
})
const showMenuDebounced = ref(false)
watchDebounced(
() => showMenu.value,
(value) => {
showMenuDebounced.value = value
},
{
debounce: 200,
maxWait: 800,
immediate: true,
},
)
const handleEditorMouseDown = (e: MouseEvent) => {
const domsInEvent = document.elementsFromPoint(e.clientX, e.clientY) as HTMLElement[]
const isBubble = domsInEvent.some((dom) => dom?.classList?.contains('bubble-menu'))
if (isBubble || isSelectAllShortcut.value) {
isSelectAllShortcut.value = false
return
}
const pageContent = document.querySelector('.nc-textarea-rich-editor')
pageContent?.classList.add('bubble-menu-hidden')
}
const handleEditorMouseUp = (e: MouseEvent) => {
const domsInEvent = document.elementsFromPoint(e.clientX, e.clientY) as HTMLElement[]
const isBubble = domsInEvent.some((dom) => dom?.classList?.contains('bubble-menu'))
if (isBubble || isSelectAllShortcut.value) {
isSelectAllShortcut.value = false
return
}
setTimeout(() => {
const pageContent = document.querySelector('.nc-textarea-rich-editor')
pageContent?.classList.remove('bubble-menu-hidden')
}, 100)
}
useEventListener('keydown', (e: KeyboardEvent) => {
if (!props.hideOnSelectAllSortcut) return
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === 'a') {
isSelectAllShortcut.value = true
}
})
onMounted(() => {
document.addEventListener('mouseup', handleEditorMouseUp)
document.addEventListener('mousedown', handleEditorMouseDown)
})
onUnmounted(() => {
document.removeEventListener('mouseup', handleEditorMouseUp)
document.removeEventListener('mousedown', handleEditorMouseDown)
})
</script>
<template>
<BubbleMenu :editor="editor" :update-delay="300" :tippy-options="{ duration: 100, maxWidth: 600 }">
<CellRichTextSelectedBubbleMenu
v-if="showMenuDebounced"
:editor="editor"
:hide-mention="hideMention"
:is-form-field="isFormField"
:hidden-options="hiddenOptions"
/>
</BubbleMenu>
</template>