diff --git a/frontend/src/components/TextBlockBubbleMenu.vue b/frontend/src/components/TextBlockBubbleMenu.vue index df44b032c..080543095 100644 --- a/frontend/src/components/TextBlockBubbleMenu.vue +++ b/frontend/src/components/TextBlockBubbleMenu.vue @@ -78,7 +78,6 @@ :class="{ 'bg-surface-gray-3': editor.isActive('strike') }"> - -
- - - - + @update:modelValue="setTextColor" + :show-input="true" + render-mode="inline" /> +
+ @@ -154,6 +148,14 @@ const settingLink = ref(false); const textLink = ref(""); const openInNewTab = ref(false); const linkInput = ref(null) as Ref; +const colorSwatchBtn = ref(null); +const colorPickerOpen = ref(false); +const colorPickerWrapper = ref(null); +const colorPickerStyle = ref>({ + position: "fixed", + top: "0px", + left: "0px", +}); const editorRef = computed(() => props.editor); const isEditableRef = computed(() => props.isEditable); @@ -165,9 +167,47 @@ const selectedColor = computed(() => { return null; }); +const toggleColorPicker = () => { + if (!colorPickerOpen.value) { + const rect = colorSwatchBtn.value?.getBoundingClientRect(); + if (rect) { + colorPickerStyle.value = { + position: "fixed", + top: `${rect.bottom + 8}px`, + left: `${rect.left}px`, + }; + } + } + colorPickerOpen.value = !colorPickerOpen.value; +}; + +const handleOutsideClick = (e: MouseEvent) => { + if ( + colorPickerWrapper.value && + !colorPickerWrapper.value.contains(e.target as Node) && + !colorSwatchBtn.value?.contains(e.target as Node) + ) { + colorPickerOpen.value = false; + } +}; + +watch(colorPickerOpen, (open) => { + if (open) { + document.addEventListener("mousedown", handleOutsideClick); + } else { + document.removeEventListener("mousedown", handleOutsideClick); + } +}); + +watch( + () => props.isEditable, + (editable) => { + if (!editable) colorPickerOpen.value = false; + }, +); + const enableLinkInput = () => { settingLink.value = true; - // check if link is already set on selection const link = props.editor?.isActive("link") ? props.editor?.getAttributes("link").href : null; textLink.value = link || ""; openInNewTab.value = props.editor?.isActive("link") @@ -208,38 +248,34 @@ const setHeading = (level: 1 | 2 | 3) => { } else { props.block.element = tag; } - nextTick(() => { props.block.selectBlock(); }); }; -// Text color functionality const isEntireTextSelected = () => { if (!props.editor) return false; const { from, to } = props.editor.state.selection; - const textContent = props.editor.state.doc.textContent; - const textLength = textContent.length; + const textLength = props.editor.state.doc.textContent.length; return from === 0 && to >= textLength; }; const setTextColor = debounce((color: string | undefined) => { + if (!props.editor || props.editor.isDestroyed) return; const colorValue = color as string; if (!colorValue) { - props.editor?.chain().focus().setColor(colorValue).run(); + props.editor.chain().setColor("").run(); if (isEntireTextSelected()) { props.block.setStyle("color", ""); } return; } - - props.editor?.chain().focus().setColor(colorValue).run(); + props.editor.chain().setColor(colorValue).run(); if (isEntireTextSelected()) { props.block.setStyle("color", colorValue); } }, 50); -// Keyboard handling const handleKeydown = (e: KeyboardEvent) => { if (e.key === "k" && e.metaKey) { const blockWarnings = { @@ -247,7 +283,6 @@ const handleKeydown = (e: KeyboardEvent) => { isLink: "You cannot add link inside a link block", isButton: "You cannot add link inside a button block", }; - const blockType = Object.entries(blockWarnings).find(([type]) => (props.block as any)[type]()); if (blockType) { toast.warning(blockType[1]); @@ -294,4 +329,4 @@ const bubbleMenuOptions = { defineExpose({ handleKeydown, }); - + \ No newline at end of file