UNPKG

lexical-vue

Version:

An extensible Vue 3 web text-editor based on Lexical.

1,531 lines (1,503 loc) 166 kB
// src/composables/useCanShowPlaceholder.ts import { readonly, ref } from "vue"; import { $canShowPlaceholderCurry } from "@lexical/text"; import { mergeRegister } from "@lexical/utils"; // src/composables/useMounted.ts import { onMounted, onUnmounted } from "vue"; function useMounted(cb) { let unregister; onMounted(() => { unregister = cb(); }); onUnmounted(() => { unregister?.(); }); } // src/composables/useCanShowPlaceholder.ts function canShowPlaceholderFromCurrentEditorState(editor) { const currentCanShowPlaceholder = editor.getEditorState().read($canShowPlaceholderCurry(editor.isComposing())); return currentCanShowPlaceholder; } function useCanShowPlaceholder(editor) { const initialState = editor.getEditorState().read($canShowPlaceholderCurry(editor.isComposing())); const canShowPlaceholder = ref(initialState); function resetCanShowPlaceholder() { const currentCanShowPlaceholder = canShowPlaceholderFromCurrentEditorState(editor); canShowPlaceholder.value = currentCanShowPlaceholder; } useMounted(() => { return mergeRegister( editor.registerUpdateListener(() => { resetCanShowPlaceholder(); }), editor.registerEditableListener(() => { resetCanShowPlaceholder(); }) ); }); return readonly(canShowPlaceholder); } // src/composables/useCharacterLimit.ts import { $createOverflowNode, $isOverflowNode, OverflowNode } from "@lexical/overflow"; import { $rootTextContent } from "@lexical/text"; import { $dfs, mergeRegister as mergeRegister2 } from "@lexical/utils"; import { $getSelection, $isLeafNode, $isRangeSelection, $isTextNode, $setSelection } from "lexical"; import invariant from "tiny-invariant"; import { toValue, watchEffect } from "vue"; function useCharacterLimit(editor, maxCharacters, optional = Object.freeze({})) { watchEffect((onInvalidate) => { if (!editor.hasNodes([OverflowNode])) { invariant( false, "useCharacterLimit: OverflowNode not registered on editor" ); } const { strlen = (input) => input.length, // UTF-16 remainingCharacters = (_characters) => { } } = toValue(optional); let text = editor.getEditorState().read($rootTextContent); let lastComputedTextLength = 0; const fn = mergeRegister2( editor.registerTextContentListener((currentText) => { text = currentText; }), editor.registerUpdateListener(({ dirtyLeaves }) => { const isComposing = editor.isComposing(); const hasDirtyLeaves = dirtyLeaves.size > 0; if (isComposing || !hasDirtyLeaves) return; const textLength = strlen(text); const textLengthAboveThreshold = textLength > toValue(maxCharacters) || lastComputedTextLength !== null && lastComputedTextLength > toValue(maxCharacters); const diff = toValue(maxCharacters) - textLength; remainingCharacters(diff); if (lastComputedTextLength === null || textLengthAboveThreshold) { const offset = findOffset(text, toValue(maxCharacters), strlen); editor.update( () => { $wrapOverflowedNodes(offset); }, { tag: "history-merge" } ); } lastComputedTextLength = textLength; }) ); onInvalidate(() => { fn(); }); }); } function findOffset(text, maxCharacters, strlen) { const Segmenter = Intl.Segmenter; let offsetUtf16 = 0; let offset = 0; if (typeof Segmenter === "function") { const segmenter = new Segmenter(); const graphemes = segmenter.segment(text); for (const { segment: grapheme } of graphemes) { const nextOffset = offset + strlen(grapheme); if (nextOffset > maxCharacters) break; offset = nextOffset; offsetUtf16 += grapheme.length; } } else { const codepoints = Array.from(text); const codepointsLength = codepoints.length; for (let i = 0; i < codepointsLength; i++) { const codepoint = codepoints[i]; const nextOffset = offset + strlen(codepoint); if (nextOffset > maxCharacters) break; offset = nextOffset; offsetUtf16 += codepoint.length; } } return offsetUtf16; } function $wrapOverflowedNodes(offset) { const dfsNodes = $dfs(); const dfsNodesLength = dfsNodes.length; let accumulatedLength = 0; for (let i = 0; i < dfsNodesLength; i += 1) { const { node } = dfsNodes[i]; if ($isOverflowNode(node)) { const previousLength = accumulatedLength; const nextLength = accumulatedLength + node.getTextContentSize(); if (nextLength <= offset) { const parent = node.getParent(); const previousSibling = node.getPreviousSibling(); const nextSibling = node.getNextSibling(); $unwrapNode(node); const selection = $getSelection(); if ($isRangeSelection(selection) && (!selection.anchor.getNode().isAttached() || !selection.focus.getNode().isAttached())) { if ($isTextNode(previousSibling)) previousSibling.select(); else if ($isTextNode(nextSibling)) nextSibling.select(); else if (parent !== null) parent.select(); } } else if (previousLength < offset) { const descendant = node.getFirstDescendant(); const descendantLength = descendant !== null ? descendant.getTextContentSize() : 0; const previousPlusDescendantLength = previousLength + descendantLength; const firstDescendantIsSimpleText = $isTextNode(descendant) && descendant.isSimpleText(); const firstDescendantDoesNotOverflow = previousPlusDescendantLength <= offset; if (firstDescendantIsSimpleText || firstDescendantDoesNotOverflow) $unwrapNode(node); } } else if ($isLeafNode(node)) { const previousAccumulatedLength = accumulatedLength; accumulatedLength += node.getTextContentSize(); if (accumulatedLength > offset && !$isOverflowNode(node.getParent())) { const previousSelection = $getSelection(); let overflowNode; if (previousAccumulatedLength < offset && $isTextNode(node) && node.isSimpleText()) { const [, overflowedText] = node.splitText( offset - previousAccumulatedLength ); overflowNode = $wrapNode(overflowedText); } else { overflowNode = $wrapNode(node); } if (previousSelection !== null) $setSelection(previousSelection); mergePrevious(overflowNode); } } } } function $wrapNode(node) { const overflowNode = $createOverflowNode(); node.insertBefore(overflowNode); overflowNode.append(node); return overflowNode; } function $unwrapNode(node) { const children = node.getChildren(); const childrenLength = children.length; for (let i = 0; i < childrenLength; i++) node.insertBefore(children[i]); node.remove(); return childrenLength > 0 ? children[childrenLength - 1] : null; } function mergePrevious(overflowNode) { const previousNode = overflowNode.getPreviousSibling(); if (!$isOverflowNode(previousNode)) return; const firstChild = overflowNode.getFirstChild(); const previousNodeChildren = previousNode.getChildren(); const previousNodeChildrenLength = previousNodeChildren.length; if (firstChild === null) { overflowNode.append(...previousNodeChildren); } else { for (let i = 0; i < previousNodeChildrenLength; i++) firstChild.insertBefore(previousNodeChildren[i]); } const selection = $getSelection(); if ($isRangeSelection(selection)) { const anchor = selection.anchor; const anchorNode = anchor.getNode(); const focus = selection.focus; const focusNode = anchor.getNode(); if (anchorNode.is(previousNode)) { anchor.set(overflowNode.getKey(), anchor.offset, "element"); } else if (anchorNode.is(overflowNode)) { anchor.set( overflowNode.getKey(), previousNodeChildrenLength + anchor.offset, "element" ); } if (focusNode.is(previousNode)) { focus.set(overflowNode.getKey(), focus.offset, "element"); } else if (focusNode.is(overflowNode)) { focus.set( overflowNode.getKey(), previousNodeChildrenLength + focus.offset, "element" ); } } previousNode.remove(); } // src/composables/useDecorators.ts import { Teleport, computed, h, shallowRef, unref } from "vue"; function useDecorators(editor) { const decorators = shallowRef(editor.getDecorators()); useMounted(() => { return editor.registerDecoratorListener((nextDecorators) => { decorators.value = nextDecorators; }); }); return computed(() => { const decoratedTeleports = []; const decoratorKeys = Object.keys(unref(decorators)); for (let i = 0; i < decoratorKeys.length; i++) { const nodeKey = decoratorKeys[i]; const vueDecorator = decorators.value[nodeKey]; const element = editor.getElementByKey(nodeKey); if (element !== null) { decoratedTeleports.push( h(Teleport, { to: element }, vueDecorator) ); } } return decoratedTeleports; }); } // src/composables/useEffect.ts import { watchEffect as watchEffect2 } from "vue"; function useEffect(cb, options) { watchEffect2((onInvalidate) => { const unregister = cb(); onInvalidate(() => unregister?.()); }, { ...options }); } // src/composables/useHistory.ts import { computed as computed2, toValue as toValue2, watchEffect as watchEffect3 } from "vue"; import { createEmptyHistoryState, registerHistory } from "@lexical/history"; function useHistory(editor, externalHistoryState, delay) { const historyState = computed2( () => toValue2(externalHistoryState) || createEmptyHistoryState() ); watchEffect3((onInvalidate) => { const unregisterListener = registerHistory(toValue2(editor), historyState.value, toValue2(delay) || 1e3); onInvalidate(unregisterListener); }); } // src/composables/useLexicalCommandsLog.ts import { COMMAND_PRIORITY_HIGH } from "lexical"; import { readonly as readonly2, ref as ref2 } from "vue"; function useLexicalCommandsLog(editor) { const loggedCommands = ref2([]); useMounted(() => { const unregisterCommandListeners = /* @__PURE__ */ new Set(); for (const [command] of editor._commands) { unregisterCommandListeners.add( editor.registerCommand( command, (payload) => { loggedCommands.value = [ ...loggedCommands.value, { payload, type: command.type ? command.type : "UNKNOWN" } ]; if (loggedCommands.value.length > 10) loggedCommands.value.shift(); return false; }, COMMAND_PRIORITY_HIGH ) ); } return () => { unregisterCommandListeners.forEach((unregister) => unregister()); }; }); return readonly2(loggedCommands); } // src/composables/useLexicalComposer.ts import { inject } from "vue"; import invariant2 from "tiny-invariant"; // src/composables/inject.ts var LexicalEditorProviderKey = "LexicalEditorProviderKey"; // src/composables/useLexicalComposer.ts function useLexicalComposer() { const editor = inject(LexicalEditorProviderKey); if (!editor) { invariant2( false, "useLexicalComposer: cannot find a LexicalComposer" ); } return editor; } // src/composables/useLexicalIsTextContentEmpty.ts import { readonly as readonly3, ref as ref3 } from "vue"; import { $isRootTextContentEmptyCurry } from "@lexical/text"; function useLexicalIsTextContentEmpty(editor, trim) { const isEmpty = ref3( editor.getEditorState().read($isRootTextContentEmptyCurry(editor.isComposing(), trim)) ); useMounted(() => { return editor.registerUpdateListener(({ editorState }) => { const isComposing = editor.isComposing(); isEmpty.value = editorState.read( $isRootTextContentEmptyCurry(isComposing, trim) ); }); }); return readonly3(isEmpty); } // src/composables/useLexicalNodeSelection.ts import { $createNodeSelection, $getNodeByKey, $getSelection as $getSelection2, $isNodeSelection, $setSelection as $setSelection2 } from "lexical"; import { readonly as readonly4, ref as ref4, toValue as toValue3, watchEffect as watchEffect4 } from "vue"; function isNodeSelected(editor, key) { return editor.getEditorState().read(() => { const node = $getNodeByKey(key); if (node === null) return false; return node.isSelected(); }); } function useLexicalNodeSelection(key) { const editor = useLexicalComposer(); const isSelected = ref4(isNodeSelected(editor, toValue3(key))); watchEffect4((onInvalidate) => { const unregisterListener = editor.registerUpdateListener(() => { isSelected.value = isNodeSelected(editor, toValue3(key)); }); onInvalidate(() => { unregisterListener(); }); }); const setSelected = (selected) => { editor.update(() => { let selection = $getSelection2(); if (!$isNodeSelection(selection)) { selection = $createNodeSelection(); $setSelection2(selection); } if ($isNodeSelection(selection)) { if (selected) selection.add(toValue3(key)); else selection.delete(toValue3(key)); } }); }; const clearSelection = () => { editor.update(() => { const selection = $getSelection2(); if ($isNodeSelection(selection)) selection.clear(); }); }; return { isSelected: readonly4(isSelected), setSelected, clearSelection }; } // src/composables/useLexicalTextEntity.ts import { registerLexicalTextEntity } from "@lexical/text"; import { mergeRegister as mergeRegister3 } from "@lexical/utils"; function useLexicalTextEntity(getMatch, targetNode, createNode) { const editor = useLexicalComposer(); useMounted(() => { return mergeRegister3( ...registerLexicalTextEntity(editor, getMatch, targetNode, createNode) ); }); } // src/composables/useList.ts import { $handleListInsertParagraph, INSERT_ORDERED_LIST_COMMAND, INSERT_UNORDERED_LIST_COMMAND, REMOVE_LIST_COMMAND, insertList, removeList } from "@lexical/list"; import { mergeRegister as mergeRegister4 } from "@lexical/utils"; import { COMMAND_PRIORITY_LOW, INSERT_PARAGRAPH_COMMAND } from "lexical"; function useList(editor) { useMounted(() => { return mergeRegister4( editor.registerCommand( INSERT_ORDERED_LIST_COMMAND, () => { insertList(editor, "number"); return true; }, COMMAND_PRIORITY_LOW ), editor.registerCommand( INSERT_UNORDERED_LIST_COMMAND, () => { insertList(editor, "bullet"); return true; }, COMMAND_PRIORITY_LOW ), editor.registerCommand( REMOVE_LIST_COMMAND, () => { removeList(editor); return true; }, COMMAND_PRIORITY_LOW ), editor.registerCommand( INSERT_PARAGRAPH_COMMAND, () => { const hasHandledInsertParagraph = $handleListInsertParagraph(); if (hasHandledInsertParagraph) return true; return false; }, COMMAND_PRIORITY_LOW ) ); }); } // src/composables/usePlainTextSetup.ts import { registerDragonSupport } from "@lexical/dragon"; import { registerPlainText } from "@lexical/plain-text"; import { mergeRegister as mergeRegister5 } from "@lexical/utils"; function usePlainTextSetup(editor) { useMounted(() => { return mergeRegister5( registerPlainText(editor), registerDragonSupport(editor) ); }); } // src/composables/useRichTextSetup.ts import { registerDragonSupport as registerDragonSupport2 } from "@lexical/dragon"; import { registerRichText } from "@lexical/rich-text"; import { mergeRegister as mergeRegister6 } from "@lexical/utils"; function useRichTextSetup(editor) { useMounted(() => { return mergeRegister6( registerRichText(editor), registerDragonSupport2(editor) ); }); } // src/composables/useTableOfContents.ts import { $isHeadingNode, HeadingNode } from "@lexical/rich-text"; import { $getNextRightPreorderNode, mergeRegister as mergeRegister7 } from "@lexical/utils"; import { $getNodeByKey as $getNodeByKey2, $getRoot, $isElementNode, TextNode } from "lexical"; import { ref as ref5 } from "vue"; function toEntry(heading) { return [heading.getKey(), heading.getTextContent(), heading.getTag()]; } function $insertHeadingIntoTableOfContents(prevHeading, newHeading, currentTableOfContents) { if (newHeading === null) return currentTableOfContents; const newEntry = toEntry(newHeading); let newTableOfContents = []; if (prevHeading === null) { if (currentTableOfContents.length > 0 && currentTableOfContents[0][0] === newHeading.__key) { return currentTableOfContents; } newTableOfContents = [newEntry, ...currentTableOfContents]; } else { for (let i = 0; i < currentTableOfContents.length; i++) { const key = currentTableOfContents[i][0]; newTableOfContents.push(currentTableOfContents[i]); if (key === prevHeading.getKey() && key !== newHeading.getKey()) { if (i + 1 < currentTableOfContents.length && currentTableOfContents[i + 1][0] === newHeading.__key) { return currentTableOfContents; } newTableOfContents.push(newEntry); } } } return newTableOfContents; } function $deleteHeadingFromTableOfContents(key, currentTableOfContents) { const newTableOfContents = []; for (const heading of currentTableOfContents) { if (heading[0] !== key) newTableOfContents.push(heading); } return newTableOfContents; } function $updateHeadingInTableOfContents(heading, currentTableOfContents) { const newTableOfContents = []; for (const oldHeading of currentTableOfContents) { if (oldHeading[0] === heading.getKey()) newTableOfContents.push(toEntry(heading)); else newTableOfContents.push(oldHeading); } return newTableOfContents; } function $updateHeadingPosition(prevHeading, heading, currentTableOfContents) { const newTableOfContents = []; const newEntry = toEntry(heading); if (!prevHeading) newTableOfContents.push(newEntry); for (const oldHeading of currentTableOfContents) { if (oldHeading[0] === heading.getKey()) continue; newTableOfContents.push(oldHeading); if (prevHeading && oldHeading[0] === prevHeading.getKey()) newTableOfContents.push(newEntry); } return newTableOfContents; } function $getPreviousHeading(node) { let prevHeading = $getNextRightPreorderNode(node); while (prevHeading !== null && !$isHeadingNode(prevHeading)) prevHeading = $getNextRightPreorderNode(prevHeading); return prevHeading; } function useTableOfContents(editor) { const tableOfContents = ref5([]); editor.getEditorState().read(() => { const root = $getRoot(); const rootChildren = root.getChildren(); for (const child of rootChildren) { if ($isHeadingNode(child)) { tableOfContents.value.push([ child.getKey(), child.getTextContent(), child.getTag() ]); } } }); const removeRootUpdateListener = editor.registerUpdateListener( ({ editorState, dirtyElements }) => { editorState.read(() => { const updateChildHeadings = (node) => { for (const child of node.getChildren()) { if ($isHeadingNode(child)) { const prevHeading = $getPreviousHeading(child); tableOfContents.value = $updateHeadingPosition( prevHeading, child, tableOfContents.value ); } else if ($isElementNode(child)) { updateChildHeadings(child); } } }; $getRoot().getChildren().forEach((node) => { if ($isElementNode(node) && dirtyElements.get(node.__key)) updateChildHeadings(node); }); }); } ); const removeHeaderMutationListener = editor.registerMutationListener( HeadingNode, (mutatedNodes) => { editor.getEditorState().read(() => { for (const [nodeKey, mutation] of mutatedNodes) { if (mutation === "created") { const newHeading = $getNodeByKey2(nodeKey); if (newHeading !== null) { const prevHeading = $getPreviousHeading(newHeading); tableOfContents.value = $insertHeadingIntoTableOfContents( prevHeading, newHeading, tableOfContents.value ); } } else if (mutation === "destroyed") { tableOfContents.value = $deleteHeadingFromTableOfContents( nodeKey, tableOfContents.value ); } else if (mutation === "updated") { const newHeading = $getNodeByKey2(nodeKey); if (newHeading !== null) { const prevHeading = $getPreviousHeading(newHeading); tableOfContents.value = $updateHeadingPosition( prevHeading, newHeading, tableOfContents.value ); } } } }); } ); const removeTextNodeMutationListener = editor.registerMutationListener( TextNode, (mutatedNodes) => { editor.getEditorState().read(() => { for (const [nodeKey, mutation] of mutatedNodes) { if (mutation === "updated") { const currNode = $getNodeByKey2(nodeKey); if (currNode !== null) { const parentNode = currNode.getParentOrThrow(); if ($isHeadingNode(parentNode)) { tableOfContents.value = $updateHeadingInTableOfContents( parentNode, tableOfContents.value ); } } } } }); } ); useMounted(() => mergeRegister7( removeRootUpdateListener, removeHeaderMutationListener, removeTextNodeMutationListener )); return tableOfContents; } // src/composables/useYjsCollaboration.ts import { mergeRegister as mergeRegister8 } from "@lexical/utils"; import { CONNECTED_COMMAND, TOGGLE_CONNECT_COMMAND, createBinding, createUndoManager, initLocalState, setLocalStateFocus, syncCursorPositions, syncLexicalUpdateToYjs, syncYjsChangesToLexical } from "@lexical/yjs"; import { $createParagraphNode, $getRoot as $getRoot2, $getSelection as $getSelection3, BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, FOCUS_COMMAND, REDO_COMMAND, UNDO_COMMAND } from "lexical"; import { UndoManager } from "yjs"; import { computed as computed3, ref as ref6, toRaw } from "vue"; function useYjsCollaboration(editor, id, provider, docMap, name, color, shouldBootstrap, initialEditorState, excludedProperties, awarenessData) { const isReloadingDoc = ref6(false); const doc = ref6(docMap.get(id)); const binding = computed3(() => createBinding(editor, provider, id, toRaw(doc.value), docMap, excludedProperties)); const connect = () => { provider.connect(); }; const disconnect = () => { try { provider.disconnect(); } catch { } }; useEffect(() => { const { root } = binding.value; const { awareness } = provider; const onStatus = ({ status }) => { editor.dispatchCommand(CONNECTED_COMMAND, status === "connected"); }; const onSync = (isSynced) => { if (shouldBootstrap && isSynced && root.isEmpty() && root._xmlText._length === 0 && isReloadingDoc.value === false) { initializeEditor(editor, initialEditorState); } isReloadingDoc.value = false; }; const onAwarenessUpdate = () => { syncCursorPositions(binding.value, provider); }; const onYjsTreeChanges = (events, transaction) => { const origin = transaction.origin; if (toRaw(origin) !== binding.value) { const isFromUndoManger = origin instanceof UndoManager; syncYjsChangesToLexical(binding.value, provider, events, isFromUndoManger); } }; initLocalState( provider, name, color, document.activeElement === editor.getRootElement(), awarenessData || {} ); const onProviderDocReload = (ydoc) => { clearEditorSkipCollab(editor, binding.value); doc.value = ydoc; docMap.set(id, ydoc); isReloadingDoc.value = true; }; provider.on("reload", onProviderDocReload); provider.on("status", onStatus); provider.on("sync", onSync); awareness.on("update", onAwarenessUpdate); root.getSharedType().observeDeep(onYjsTreeChanges); const removeListener = editor.registerUpdateListener( ({ prevEditorState, editorState, dirtyLeaves, dirtyElements, normalizedNodes, tags }) => { if (tags.has("skip-collab") === false) { syncLexicalUpdateToYjs( binding.value, provider, prevEditorState, editorState, dirtyElements, dirtyLeaves, normalizedNodes, tags ); } } ); connect(); return () => { if (isReloadingDoc.value === false) disconnect(); provider.off("sync", onSync); provider.off("status", onStatus); provider.off("reload", onProviderDocReload); awareness.off("update", onAwarenessUpdate); root.getSharedType().unobserveDeep(onYjsTreeChanges); docMap.delete(id); removeListener(); }; }); useEffect(() => { return editor.registerCommand( TOGGLE_CONNECT_COMMAND, (payload) => { if (connect !== void 0 && disconnect !== void 0) { const shouldConnect = payload; if (shouldConnect) { console.log("Collaboration connected!"); connect(); } else { console.log("Collaboration disconnected!"); disconnect(); } } return true; }, COMMAND_PRIORITY_EDITOR ); }); return binding; } function useYjsFocusTracking(editor, provider, name, color, awarenessData) { useEffect(() => { return mergeRegister8( editor.registerCommand( FOCUS_COMMAND, () => { setLocalStateFocus(provider, name, color, true, awarenessData || {}); return false; }, COMMAND_PRIORITY_EDITOR ), editor.registerCommand( BLUR_COMMAND, () => { setLocalStateFocus(provider, name, color, false, awarenessData || {}); return false; }, COMMAND_PRIORITY_EDITOR ) ); }); } function useYjsHistory(editor, binding) { const undoManager = computed3(() => createUndoManager(binding, binding.root.getSharedType())); useEffect(() => { const undo = () => { undoManager.value.undo(); }; const redo = () => { undoManager.value.redo(); }; return mergeRegister8( editor.registerCommand( UNDO_COMMAND, () => { undo(); return true; }, COMMAND_PRIORITY_EDITOR ), editor.registerCommand( REDO_COMMAND, () => { redo(); return true; }, COMMAND_PRIORITY_EDITOR ) ); }); const clearHistory = () => { undoManager.value.clear(); }; return clearHistory; } function initializeEditor(editor, initialEditorState) { editor.update( () => { const root = $getRoot2(); if (root.isEmpty()) { if (initialEditorState) { switch (typeof initialEditorState) { case "string": { const parsedEditorState = editor.parseEditorState(initialEditorState); editor.setEditorState(parsedEditorState, { tag: "history-merge" }); break; } case "object": { editor.setEditorState(initialEditorState, { tag: "history-merge" }); break; } case "function": { editor.update( () => { const root1 = $getRoot2(); if (root1.isEmpty()) initialEditorState(editor); }, { tag: "history-merge" } ); break; } } } else { const paragraph = $createParagraphNode(); root.append(paragraph); const { activeElement } = document; if ($getSelection3() !== null || activeElement !== null && activeElement === editor.getRootElement()) { paragraph.select(); } } } }, { tag: "history-merge" } ); } function clearEditorSkipCollab(editor, binding) { editor.update( () => { const root = $getRoot2(); root.clear(); root.select(); }, { tag: "skip-collab" } ); if (binding.cursors == null) return; const cursors = binding.cursors; if (cursors == null) return; const cursorsContainer = binding.cursorsContainer; if (cursorsContainer == null) return; const cursorsArr = Array.from(cursors.values()); for (let i = 0; i < cursorsArr.length; i++) { const cursor = cursorsArr[i]; const selection = cursor.selection; if (selection && selection.selections !== null) { const selections = selection.selections; for (let j = 0; j < selections.length; j++) cursorsContainer.removeChild(selections[i]); } } } // src/components/LexicalDecoratedTeleports.ts import { defineComponent } from "vue"; var LexicalDecoratedTeleports_default = defineComponent({ name: "LexicalDecoratedTeleports", setup() { const editor = useLexicalComposer(); const decorators = useDecorators(editor); return () => decorators.value; } }); // src/components/LexicalContentEditable.vue import { defineComponent as _defineComponent2 } from "vue"; import { unref as _unref, mergeProps as _mergeProps2, createVNode as _createVNode, renderSlot as _renderSlot, openBlock as _openBlock2, createElementBlock as _createElementBlock2, createCommentVNode as _createCommentVNode, Fragment as _Fragment } from "vue"; import { ref as ref8 } from "vue"; // src/components/LexicalContentEditableElement.vue import { defineComponent as _defineComponent } from "vue"; import { mergeProps as _mergeProps, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"; import { computed as computed4, ref as ref7 } from "vue"; var _hoisted_1 = ["aria-activedescendant", "aria-autocomplete", "aria-controls", "aria-describedby", "aria-expanded", "aria-label", "aria-labelledby", "aria-multiline", "aria-owns", "aria-readonly", "aria-required", "autocapitalize", "contenteditable", "data-testid", "role", "spellcheck", "tabindex"]; var _sfc_main = /* @__PURE__ */ _defineComponent({ __name: "LexicalContentEditableElement", props: { editor: {}, ariaActiveDescendant: {}, ariaAutoComplete: {}, ariaControls: {}, ariaDescribedBy: {}, ariaErrorMessage: {}, ariaExpanded: { type: [Boolean, String] }, ariaInvalid: { type: [Boolean, String] }, ariaLabel: {}, ariaLabelledBy: {}, ariaMultiline: { type: [Boolean, String] }, ariaOwns: {}, ariaRequired: { type: [Boolean, String] }, dataTestid: {}, innerHTML: {}, class: {}, style: { type: [Boolean, null, String, Object, Array] }, accesskey: {}, contenteditable: { type: [Boolean, String] }, contextmenu: {}, dir: {}, draggable: { type: [Boolean, String] }, hidden: { type: [Boolean, String] }, id: {}, inert: { type: [Boolean, String] }, lang: {}, spellcheck: { type: [Boolean, String], default: true }, tabindex: {}, title: {}, translate: {}, radiogroup: {}, role: { default: "textbox" }, about: {}, datatype: {}, inlist: {}, property: {}, resource: {}, typeof: {}, vocab: {}, autocapitalize: {}, autocorrect: {}, autosave: {}, color: {}, itemprop: {}, itemscope: { type: [Boolean, String] }, itemtype: {}, itemid: {}, itemref: {}, results: {}, security: {}, unselectable: {}, inputmode: {}, is: {}, "aria-activedescendant": {}, "aria-atomic": { type: [Boolean, String] }, "aria-autocomplete": {}, "aria-busy": { type: [Boolean, String] }, "aria-checked": { type: [Boolean, String] }, "aria-colcount": {}, "aria-colindex": {}, "aria-colspan": {}, "aria-controls": {}, "aria-current": { type: [Boolean, String] }, "aria-describedby": {}, "aria-details": {}, "aria-disabled": { type: [Boolean, String] }, "aria-dropeffect": {}, "aria-errormessage": {}, "aria-expanded": { type: [Boolean, String] }, "aria-flowto": {}, "aria-grabbed": { type: [Boolean, String] }, "aria-haspopup": { type: [Boolean, String] }, "aria-hidden": { type: [Boolean, String] }, "aria-invalid": { type: [Boolean, String] }, "aria-keyshortcuts": {}, "aria-label": {}, "aria-labelledby": {}, "aria-level": {}, "aria-live": {}, "aria-modal": { type: [Boolean, String] }, "aria-multiline": { type: [Boolean, String] }, "aria-multiselectable": { type: [Boolean, String] }, "aria-orientation": {}, "aria-owns": {}, "aria-placeholder": {}, "aria-posinset": {}, "aria-pressed": { type: [Boolean, String] }, "aria-readonly": { type: [Boolean, String] }, "aria-relevant": {}, "aria-required": { type: [Boolean, String] }, "aria-roledescription": {}, "aria-rowcount": {}, "aria-rowindex": {}, "aria-rowspan": {}, "aria-selected": { type: [Boolean, String] }, "aria-setsize": {}, "aria-sort": {}, "aria-valuemax": {}, "aria-valuemin": {}, "aria-valuenow": {}, "aria-valuetext": {} }, setup(__props) { const props = __props; const root = ref7(null); const isEditable = ref7(props.editor.isEditable()); const otherAttrs = computed4(() => { const ariaAttrs = {}; if (props.ariaInvalid != null) ariaAttrs["aria-invalid"] = props.ariaInvalid; if (props.ariaErrorMessage != null) ariaAttrs["aria-errormessage"] = props.ariaErrorMessage; return { ...props, ...ariaAttrs }; }); useMounted(() => { function handleRef(rootElement) { if (rootElement && rootElement.ownerDocument && rootElement.ownerDocument.defaultView) { props.editor.setRootElement(rootElement); } else { props.editor.setRootElement(null); } } handleRef(root.value); isEditable.value = props.editor.isEditable(); return props.editor.registerEditableListener((currentIsEditable) => { isEditable.value = currentIsEditable; }); }); return (_ctx, _cache) => { return _openBlock(), _createElementBlock("div", _mergeProps({ ref_key: "root", ref: root }, otherAttrs.value, { "aria-activedescendant": isEditable.value ? _ctx.ariaActiveDescendant : void 0, "aria-autocomplete": isEditable.value ? _ctx.ariaAutoComplete : "none", "aria-controls": isEditable.value ? _ctx.ariaControls : void 0, "aria-describedby": _ctx.ariaDescribedBy, "aria-expanded": isEditable.value && _ctx.role === "combobox" ? !!_ctx.ariaExpanded : void 0, "aria-label": _ctx.ariaLabel, "aria-labelledby": _ctx.ariaLabelledBy, "aria-multiline": _ctx.ariaMultiline, "aria-owns": isEditable.value ? _ctx.ariaOwns : void 0, "aria-readonly": isEditable.value ? void 0 : true, "aria-required": _ctx.ariaRequired, autocapitalize: _ctx.autocapitalize, contenteditable: isEditable.value, "data-testid": _ctx.dataTestid, role: isEditable.value ? _ctx.role : void 0, spellcheck: _ctx.spellcheck, style: _ctx.style, tabindex: _ctx.tabindex }), null, 16, _hoisted_1); }; } }); var LexicalContentEditableElement_default = _sfc_main; // src/components/LexicalContentEditable.vue var _hoisted_12 = { key: 0, "aria-hidden": "true" }; var _sfc_main2 = /* @__PURE__ */ _defineComponent2({ __name: "LexicalContentEditable", props: { ariaActiveDescendant: {}, ariaAutoComplete: {}, ariaControls: {}, ariaDescribedBy: {}, ariaErrorMessage: {}, ariaExpanded: {}, ariaInvalid: {}, ariaLabel: {}, ariaLabelledBy: {}, ariaMultiline: {}, ariaOwns: {}, ariaRequired: {}, dataTestid: {}, innerHTML: {}, class: {}, style: { type: [Boolean, null, String, Object, Array] }, accesskey: {}, contenteditable: { type: [Boolean, String] }, contextmenu: {}, dir: {}, draggable: { type: [Boolean, String] }, hidden: { type: [Boolean, String] }, id: {}, inert: { type: [Boolean, String] }, lang: {}, spellcheck: { type: [Boolean, String], default: true }, tabindex: {}, title: {}, translate: {}, radiogroup: {}, role: { default: "textbox" }, about: {}, datatype: {}, inlist: {}, property: {}, resource: {}, typeof: {}, vocab: {}, autocapitalize: {}, autocorrect: {}, autosave: {}, color: {}, itemprop: {}, itemscope: { type: [Boolean, String] }, itemtype: {}, itemid: {}, itemref: {}, results: {}, security: {}, unselectable: {}, inputmode: {}, is: {}, "aria-activedescendant": {}, "aria-atomic": { type: [Boolean, String] }, "aria-autocomplete": {}, "aria-busy": { type: [Boolean, String] }, "aria-checked": { type: [Boolean, String] }, "aria-colcount": {}, "aria-colindex": {}, "aria-colspan": {}, "aria-controls": {}, "aria-current": { type: [Boolean, String] }, "aria-describedby": {}, "aria-details": {}, "aria-disabled": { type: [Boolean, String] }, "aria-dropeffect": {}, "aria-errormessage": {}, "aria-expanded": { type: [Boolean, String] }, "aria-flowto": {}, "aria-grabbed": { type: [Boolean, String] }, "aria-haspopup": { type: [Boolean, String] }, "aria-hidden": { type: [Boolean, String] }, "aria-invalid": { type: [Boolean, String] }, "aria-keyshortcuts": {}, "aria-label": {}, "aria-labelledby": {}, "aria-level": {}, "aria-live": {}, "aria-modal": { type: [Boolean, String] }, "aria-multiline": { type: [Boolean, String] }, "aria-multiselectable": { type: [Boolean, String] }, "aria-orientation": {}, "aria-owns": {}, "aria-placeholder": {}, "aria-posinset": {}, "aria-pressed": { type: [Boolean, String] }, "aria-readonly": { type: [Boolean, String] }, "aria-relevant": {}, "aria-required": { type: [Boolean, String] }, "aria-roledescription": {}, "aria-rowcount": {}, "aria-rowindex": {}, "aria-rowspan": {}, "aria-selected": { type: [Boolean, String] }, "aria-setsize": {}, "aria-sort": {}, "aria-valuemax": {}, "aria-valuemin": {}, "aria-valuenow": {}, "aria-valuetext": {} }, setup(__props) { const editor = useLexicalComposer(); const isEditable = ref8(false); const showPlaceholder = useCanShowPlaceholder(editor); useMounted(() => { isEditable.value = editor.isEditable(); return editor.registerEditableListener((currentIsEditable) => { isEditable.value = currentIsEditable; }); }); return (_ctx, _cache) => { return _openBlock2(), _createElementBlock2( _Fragment, null, [ _createVNode(LexicalContentEditableElement_default, _mergeProps2({ editor: _unref(editor) }, _ctx.$props), null, 16, ["editor"]), _unref(showPlaceholder) ? (_openBlock2(), _createElementBlock2("div", _hoisted_12, [ _renderSlot(_ctx.$slots, "placeholder") ])) : _createCommentVNode("v-if", true) ], 64 /* STABLE_FRAGMENT */ ); }; } }); var LexicalContentEditable_default = _sfc_main2; // src/components/LexicalPlainTextPlugin.vue import { defineComponent as _defineComponent3 } from "vue"; import { unref as _unref2, renderSlot as _renderSlot2, createCommentVNode as _createCommentVNode2, createVNode as _createVNode2, Fragment as _Fragment2, openBlock as _openBlock3, createElementBlock as _createElementBlock3 } from "vue"; var _sfc_main3 = /* @__PURE__ */ _defineComponent3({ __name: "LexicalPlainTextPlugin", setup(__props) { const editor = useLexicalComposer(); const showPlaceholder = useCanShowPlaceholder(editor); usePlainTextSetup(editor); return (_ctx, _cache) => { return _openBlock3(), _createElementBlock3( _Fragment2, null, [ _unref2(showPlaceholder) ? _renderSlot2(_ctx.$slots, "placeholder", { key: 0 }) : _createCommentVNode2("v-if", true), _renderSlot2(_ctx.$slots, "contentEditable"), _createVNode2(_unref2(LexicalDecoratedTeleports_default)) ], 64 /* STABLE_FRAGMENT */ ); }; } }); var LexicalPlainTextPlugin_default = _sfc_main3; // src/components/LexicalComposer.vue import { defineComponent as _defineComponent4 } from "vue"; import { renderSlot as _renderSlot3 } from "vue"; import { onMounted as onMounted2, provide } from "vue"; import { $createParagraphNode as $createParagraphNode2, $getRoot as $getRoot3, $getSelection as $getSelection4, createEditor } from "lexical"; var _sfc_main4 = /* @__PURE__ */ _defineComponent4({ __name: "LexicalComposer", props: { initialConfig: {} }, emits: ["error"], setup(__props, { emit: __emit }) { const props = __props; const emit = __emit; const HISTORY_MERGE_OPTIONS = { tag: "history-merge" }; const { theme, namespace, nodes, onError, editorState: initialEditorState, html } = props.initialConfig; const editor = createEditor({ editable: props.initialConfig.editable, html, namespace, nodes, theme, onError(error) { emit("error", error, editor); onError?.(error, editor); } }); initializeEditor2(editor, initialEditorState); function initializeEditor2(editor2, initialEditorState2) { if (initialEditorState2 === null) return; if (initialEditorState2 === void 0) { editor2.update(() => { const root = $getRoot3(); if (root.isEmpty()) { const paragraph = $createParagraphNode2(); root.append(paragraph); const activeElement = document.activeElement; if ($getSelection4() !== null || activeElement !== null && activeElement === editor2.getRootElement()) { paragraph.select(); } } }, HISTORY_MERGE_OPTIONS); } else if (initialEditorState2 !== null) { switch (typeof initialEditorState2) { case "string": { const parsedEditorState = editor2.parseEditorState(initialEditorState2); editor2.setEditorState(parsedEditorState, HISTORY_MERGE_OPTIONS); break; } case "object": { editor2.setEditorState(initialEditorState2, HISTORY_MERGE_OPTIONS); break; } case "function": { editor2.update(() => { const root = $getRoot3(); if (root.isEmpty()) initialEditorState2(editor2); }, HISTORY_MERGE_OPTIONS); break; } } } } provide(LexicalEditorProviderKey, editor); onMounted2(() => { const isEditable = props.initialConfig.editable; editor.setEditable(isEditable !== void 0 ? isEditable : true); }); return (_ctx, _cache) => { return _renderSlot3(_ctx.$slots, "default"); }; } }); var LexicalComposer_default = _sfc_main4; // src/components/LexicalOnChangePlugin.vue import { defineComponent as _defineComponent5 } from "vue"; import { watchEffect as watchEffect5 } from "vue"; var _sfc_main5 = /* @__PURE__ */ _defineComponent5({ __name: "LexicalOnChangePlugin", props: { ignoreInitialChange: { type: Boolean, default: true }, ignoreSelectionChange: { type: Boolean, default: false }, ignoreHistoryMergeTagChange: { type: Boolean, default: true } }, emits: ["change"], setup(__props, { emit: __emit }) { const props = __props; const emit = __emit; const editor = useLexicalComposer(); watchEffect5(() => { return editor.registerUpdateListener(({ editorState, dirtyElements, dirtyLeaves, prevEditorState, tags }) => { if (props.ignoreSelectionChange && dirtyElements.size === 0 && dirtyLeaves.size === 0 || props.ignoreHistoryMergeTagChange && tags.has("history-merge") || props.ignoreInitialChange && prevEditorState.isEmpty()) { return; } emit("change", editorState, editor, tags); }); }); return (_ctx, _cache) => { return null; }; } }); var LexicalOnChangePlugin_default = _sfc_main5; // src/components/LexicalHistoryPlugin.vue import { defineComponent as _defineComponent6 } from "vue"; var _sfc_main6 = /* @__PURE__ */ _defineComponent6({ __name: "LexicalHistoryPlugin", props: { delay: {}, externalHistoryState: {} }, setup(__props) { const props = __props; const editor = useLexicalComposer(); useHistory(editor, () => props.externalHistoryState, () => props.delay); return (_ctx, _cache) => { return null; }; } }); var LexicalHistoryPlugin_default = _sfc_main6; // src/components/LexicalTreeViewPlugin.vue import { defineComponent as _defineComponent7 } from "vue"; import { createElementVNode as _createElementVNode, openBlock as _openBlock4, createElementBlock as _createElementBlock4, createCommentVNode as _createCommentVNode3, toDisplayString as _toDisplayString, normalizeClass as _normalizeClass } from "vue"; import { $generateHtmlFromNodes } from "@lexical/html"; import { $isLinkNode } from "@lexical/link"; import { $isMarkNode } from "@lexical/mark"; import { $isTableSelection } from "@lexical/table"; import { mergeRegister as mergeRegister9 } from "@lexical/utils"; import { $getRoot as $getRoot4, $getSelection as $getSelection5, $isElementNode as $isElementNode2, $isNodeSelection as $isNodeSelection2, $isRangeSelection as $isRangeSelection2, $isTextNode as $isTextNode2 } from "lexical"; import { computed as computed5, ref as ref9, watchEffect as watchEffect6 } from "vue"; var _hoisted_13 = { key: 0, style: { "padding": "20px" } }; var _hoisted_2 = { key: 1 }; var _hoisted_3 = ["max"]; var _sfc_main7 = /* @__PURE__ */ _defineComponent7({ __name: "LexicalTreeViewPlugin", props: { treeTypeButtonClassName: {}, timeTravelButtonClassName: {}, timeTravelPanelSliderClassName: {}, timeTravelPanelButtonClassName: {}, timeTravelPanelClassName: {}, viewClassName: {} }, setup(__props) { const NON_SINGLE_WIDTH_CHARS_REPLACEMENT = Object.freeze({ " ": "\\t", "\n": "\\n" }); const NON_SINGLE_WIDTH_CHARS_REGEX = new RegExp( Object.keys(NON_SINGLE_WIDTH_CHARS_REPLACEMENT).join("|"), "g" ); const SYMBOLS = Object.freeze({ ancestorHasNextSibling: "|", ancestorIsLastChild: " ", hasNextSibling: "\u251C", isLastChild: "\u2514", selectedChar: "^", selectedLine: ">" }); function printRangeSelection(selection) { let res = ""; const formatText = printFormatProperties(selection); res += `: range ${formatText !== "" ? `{ ${formatText} }` : ""}`; const anchor = selection.anchor; const focus = selection.focus; const anchorOffset = anchor.offset; const focusOffset = focus.offset; res += ` \u251C anchor { key: ${anchor.key}, offset: ${anchorOffset === null ? "null" : anchorOffset}, type: ${anchor.type} }`; res += ` \u2514 focus { key: ${focus.key}, offset: ${focusOffset === null ? "null" : focusOffset}, type: ${focus.type} }`; return res; } function generateContent(editor2, commandsLog2, exportDOM) { const editorState = editor2.getEditorState(); const editorConfig = editor2._config; const compositionKey = editor2._compositionKey; const editable = editor2._editable; if (exportDOM) { let htmlString = ""; editorState.read(() => { htmlString = printPrettyHTML($generateHtmlFromNodes(editor2)); }); return htmlString; } let res = " root\n"; const selectionString = editorState.read(() => { const selection = $getSelection5(); visitTree($getRoot4(), (node, indent) => { const nodeKey = node.getKey(); const nodeKeyDisplay = `(${nodeKey})`; const typeDisplay = node.getType() || ""; const isSelected = node.isSelected(); const idsDisplay = $isMarkNode(node) ? ` id: [ ${node.getIDs().join(", ")} ] ` : ""; res += `${isSelected ? SYMBOLS.selectedLine : " "} ${indent.join( " " )} ${nodeKeyDisplay} ${typeDisplay} ${idsDisplay} ${printNode(node)} `; res += printSelectedCharsLine({ indent, isSelected, node, nodeKeyDisplay, selection, typeDisplay }); }); return selection === null ? ": null" : $isRangeSelection2(selection) ? printRangeSelection(selection) : $isTableSelection(selection) ? printTableSelection(selection) : printNodeSelection(selection); });