@lexical/react
Version:
This package provides Lexical components and hooks for React applications.
145 lines (139 loc) • 5.09 kB
JavaScript
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
;
var link = require('@lexical/link');
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var LexicalNodeMenuPlugin = require('@lexical/react/LexicalNodeMenuPlugin');
var utils = require('@lexical/utils');
var lexical = require('lexical');
var react = require('react');
var jsxRuntime = require('react/jsx-runtime');
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
*/
const URL_MATCHER = /((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
const INSERT_EMBED_COMMAND = lexical.createCommand('INSERT_EMBED_COMMAND');
class AutoEmbedOption extends LexicalNodeMenuPlugin.MenuOption {
constructor(title, options) {
super(title);
this.title = title;
this.onSelect = options.onSelect.bind(this);
}
}
function LexicalAutoEmbedPlugin({
embedConfigs,
onOpenEmbedModalForConfig,
getMenuOptions,
menuRenderFn,
menuCommandPriority = lexical.COMMAND_PRIORITY_LOW
}) {
const [editor] = LexicalComposerContext.useLexicalComposerContext();
const [nodeKey, setNodeKey] = react.useState(null);
const [activeEmbedConfig, setActiveEmbedConfig] = react.useState(null);
const reset = react.useCallback(() => {
setNodeKey(null);
setActiveEmbedConfig(null);
}, []);
const checkIfLinkNodeIsEmbeddable = react.useCallback(async key => {
const url = editor.getEditorState().read(function () {
const linkNode = lexical.$getNodeByKey(key);
if (link.$isLinkNode(linkNode)) {
return linkNode.getURL();
}
});
if (url === undefined) {
return;
}
for (const embedConfig of embedConfigs) {
const urlMatch = await Promise.resolve(embedConfig.parseUrl(url));
if (urlMatch != null) {
setActiveEmbedConfig(embedConfig);
setNodeKey(key);
}
}
}, [editor, embedConfigs]);
react.useEffect(() => {
const listener = (nodeMutations, {
updateTags,
dirtyLeaves
}) => {
for (const [key, mutation] of nodeMutations) {
if (mutation === 'created' && updateTags.has('paste') && dirtyLeaves.size <= 3) {
checkIfLinkNodeIsEmbeddable(key);
} else if (key === nodeKey) {
reset();
}
}
};
return utils.mergeRegister(...[link.LinkNode, link.AutoLinkNode].map(Klass => editor.registerMutationListener(Klass, (...args) => listener(...args), {
skipInitialization: true
})));
}, [checkIfLinkNodeIsEmbeddable, editor, embedConfigs, nodeKey, reset]);
react.useEffect(() => {
return editor.registerCommand(INSERT_EMBED_COMMAND, embedConfigType => {
const embedConfig = embedConfigs.find(({
type
}) => type === embedConfigType);
if (embedConfig) {
onOpenEmbedModalForConfig(embedConfig);
return true;
}
return false;
}, lexical.COMMAND_PRIORITY_EDITOR);
}, [editor, embedConfigs, onOpenEmbedModalForConfig]);
const embedLinkViaActiveEmbedConfig = react.useCallback(async function () {
if (activeEmbedConfig != null && nodeKey != null) {
const linkNode = editor.getEditorState().read(() => {
const node = lexical.$getNodeByKey(nodeKey);
if (link.$isLinkNode(node)) {
return node;
}
return null;
});
if (link.$isLinkNode(linkNode)) {
const result = await Promise.resolve(activeEmbedConfig.parseUrl(linkNode.__url));
if (result != null) {
editor.update(() => {
if (!lexical.$getSelection()) {
linkNode.selectEnd();
}
activeEmbedConfig.insertNode(editor, result);
if (linkNode.isAttached()) {
linkNode.remove();
}
});
}
}
}
}, [activeEmbedConfig, editor, nodeKey]);
const options = react.useMemo(() => {
return activeEmbedConfig != null && nodeKey != null ? getMenuOptions(activeEmbedConfig, embedLinkViaActiveEmbedConfig, reset) : [];
}, [activeEmbedConfig, embedLinkViaActiveEmbedConfig, getMenuOptions, nodeKey, reset]);
const onSelectOption = react.useCallback((selectedOption, targetNode, closeMenu) => {
editor.update(() => {
selectedOption.onSelect(targetNode);
closeMenu();
});
}, [editor]);
return nodeKey != null ? /*#__PURE__*/jsxRuntime.jsx(LexicalNodeMenuPlugin.LexicalNodeMenuPlugin, {
nodeKey: nodeKey,
onClose: reset,
onSelectOption: onSelectOption,
options: options,
menuRenderFn: menuRenderFn,
commandPriority: menuCommandPriority
}) : null;
}
exports.AutoEmbedOption = AutoEmbedOption;
exports.INSERT_EMBED_COMMAND = INSERT_EMBED_COMMAND;
exports.LexicalAutoEmbedPlugin = LexicalAutoEmbedPlugin;
exports.URL_MATCHER = URL_MATCHER;