mirror of
https://github.com/nocodb/nocodb.git
synced 2026-05-01 14:26:43 +00:00
104 lines
2.8 KiB
Vue
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>
|