lexical-vue
Version:
An extensible Vue 3 web text-editor based on Lexical.
1,531 lines (1,503 loc) • 166 kB
JavaScript
// 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);
});