lexical-vue
Version:
An extensible Vue 3 web text-editor based on Lexical.
148 lines (147 loc) • 6.85 kB
JavaScript
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 };