UNPKG

lexical-vue

Version:

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

148 lines (147 loc) 6.85 kB
import { computed, createBlock, createCommentVNode, defineComponent, guardReactiveProps, normalizeProps, openBlock, ref, renderSlot, unref, useSlots, watchEffect, withCtx } from "vue"; import { $isLinkNode, AutoLinkNode, LinkNode } from "@lexical/link"; import { mergeRegister } from "@lexical/utils"; import { $getNodeByKey, $getSelection, COMMAND_PRIORITY_EDITOR, PASTE_TAG, createCommand } from "lexical"; import { useLexicalComposer } from "./LexicalComposer.vine.js"; import { NodeMenuPlugin } from "./LexicalNodeMenuPlugin.vine.js"; import { MenuOption } from "./shared/LexicalMenu.vine.js"; function _define_property(obj, key, value) { if (key in obj) Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); else obj[key] = value; return obj; } const URL_MATCHER = /((https?:\/\/(www\.)?)|(www\.))[-\w@:%.+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-\w()@:%+.~#?&/=]*)/; const INSERT_EMBED_COMMAND = createCommand('INSERT_EMBED_COMMAND'); class AutoEmbedOption extends MenuOption { constructor(title, options){ super(title), _define_property(this, "title", void 0), _define_property(this, "onSelect", void 0); this.title = title; this.onSelect = options.onSelect.bind(this); } } const LexicalAutoEmbedPlugin = (()=>{ const __vine = defineComponent({ name: 'LexicalAutoEmbedPlugin', props: { embedConfigs: { required: true }, getMenuOptions: { required: true }, menuCommandPriority: {} }, emits: [ 'openEmbedModalForConfig' ], setup (__props, param) { let { emit: __emit, expose: __expose } = param; const emit = __emit; __expose(); const props = __props; useSlots(); const editor = useLexicalComposer(); const nodeKey = ref(null); const activeEmbedConfig = ref(null); function reset() { nodeKey.value = null; activeEmbedConfig.value = null; } async function checkIfLinkNodeIsEmbeddable(key) { const url = editor.getEditorState().read(()=>{ const linkNode = $getNodeByKey(key); if ($isLinkNode(linkNode)) return linkNode.getURL(); }); if (void 0 === url) return; for (const embedConfig of props.embedConfigs){ const urlMatch = await Promise.resolve(embedConfig.parseUrl(url)); if (null != urlMatch) { activeEmbedConfig.value = embedConfig; nodeKey.value = key; } } } const listener = (nodeMutations, param)=>{ let { updateTags, dirtyLeaves } = param; for (const [key, mutation] of nodeMutations)if ('created' === mutation && updateTags.has(PASTE_TAG) && dirtyLeaves.size <= 3) checkIfLinkNodeIsEmbeddable(key); else if (key === nodeKey.value) reset(); }; watchEffect((onInvalidate)=>{ const unregister = mergeRegister(...[ LinkNode, AutoLinkNode ].map((Klass)=>editor.registerMutationListener(Klass, function() { for(var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++)args[_key] = arguments[_key]; return listener(...args); }, { skipInitialization: true }))); onInvalidate(unregister); }); watchEffect((onInvalidate)=>{ const unregister = editor.registerCommand(INSERT_EMBED_COMMAND, (embedConfigType)=>{ const embedConfig = props.embedConfigs.find((param)=>{ let { type } = param; return type === embedConfigType; }); if (embedConfig) { emit('openEmbedModalForConfig', embedConfig); return true; } return false; }, COMMAND_PRIORITY_EDITOR); onInvalidate(unregister); }); async function embedLinkViaActiveEmbedConfig() { if (null != activeEmbedConfig.value && null != nodeKey.value) { const linkNode = editor.getEditorState().read(()=>{ const node = $getNodeByKey(nodeKey.value); if ($isLinkNode(node)) return node; return null; }); if ($isLinkNode(linkNode)) { const result = await Promise.resolve(activeEmbedConfig.value.parseUrl(linkNode.__url)); if (null != result) editor.update(()=>{ if (!$getSelection()) linkNode.selectEnd(); activeEmbedConfig.value.insertNode(editor, result); if (linkNode.isAttached()) linkNode.remove(); }); } } } const options = computed(()=>null != activeEmbedConfig.value && null != nodeKey.value ? props.getMenuOptions(activeEmbedConfig.value, embedLinkViaActiveEmbedConfig, reset) : []); function onSelectOption(param) { let { option: selectedOption, closeMenu, textNodeContainingQuery: targetNode } = param; editor.update(()=>{ selectedOption.onSelect(targetNode); closeMenu(); }); } return (_ctx, _cache)=>null !== nodeKey.value ? (openBlock(), createBlock(unref(NodeMenuPlugin), { key: 0, "node-key": nodeKey.value, onClose: reset, options: options.value, "command-priority": _ctx.menuCommandPriority, onSelectOption: onSelectOption }, { default: withCtx((slotProps)=>[ renderSlot(_ctx.$slots, "default", normalizeProps(guardReactiveProps(slotProps))) ]), _: 3 }, 8, [ "node-key", "options", "command-priority" ])) : createCommentVNode("", true); } }); __vine.__vue_vine = true; return __vine; })(); export { AutoEmbedOption, INSERT_EMBED_COMMAND, LexicalAutoEmbedPlugin, URL_MATCHER };