deploy: v0.12 fix 存档key名

This commit is contained in:
2026-06-24 03:09:02 +00:00
parent 7a08d49fe3
commit e0a5281119
58743 changed files with 7297269 additions and 39505 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) Meta Platforms, Inc. and affiliates.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+39
View File
@@ -0,0 +1,39 @@
/**
* 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.
*
*/
import type { CommandListenerPriority, LexicalNode } from 'lexical';
import type { JSX } from 'react';
import { MenuOption, MenuRenderFn } from '@lexical/react/LexicalNodeMenuPlugin';
import { LexicalCommand, LexicalEditor } from 'lexical';
export type EmbedMatchResult<TEmbedMatchResult = unknown> = {
url: string;
id: string;
data?: TEmbedMatchResult;
};
export interface EmbedConfig<TEmbedMatchResultData = unknown, TEmbedMatchResult = EmbedMatchResult<TEmbedMatchResultData>> {
type: string;
parseUrl: (text: string) => Promise<TEmbedMatchResult | null> | TEmbedMatchResult | null;
insertNode: (editor: LexicalEditor, result: TEmbedMatchResult) => void;
}
export declare const URL_MATCHER: RegExp;
export declare const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']>;
export declare class AutoEmbedOption extends MenuOption {
title: string;
onSelect: (targetNode: LexicalNode | null) => void;
constructor(title: string, options: {
onSelect: (targetNode: LexicalNode | null) => void;
});
}
type LexicalAutoEmbedPluginProps<TEmbedConfig extends EmbedConfig> = {
embedConfigs: Array<TEmbedConfig>;
onOpenEmbedModalForConfig: (embedConfig: TEmbedConfig) => void;
getMenuOptions: (activeEmbedConfig: TEmbedConfig, embedFn: () => void, dismissFn: () => void) => Array<AutoEmbedOption>;
menuRenderFn: MenuRenderFn<AutoEmbedOption>;
menuCommandPriority?: CommandListenerPriority;
};
export declare function LexicalAutoEmbedPlugin<TEmbedConfig extends EmbedConfig>({ embedConfigs, onOpenEmbedModalForConfig, getMenuOptions, menuRenderFn, menuCommandPriority, }: LexicalAutoEmbedPluginProps<TEmbedConfig>): JSX.Element | null;
export {};
+144
View File
@@ -0,0 +1,144 @@
/**
* 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.
*
*/
'use strict';
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(lexical.PASTE_TAG) && 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;
+139
View File
@@ -0,0 +1,139 @@
/**
* 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.
*
*/
import { $isLinkNode, LinkNode, AutoLinkNode } from '@lexical/link';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { MenuOption, LexicalNodeMenuPlugin } from '@lexical/react/LexicalNodeMenuPlugin';
import { mergeRegister } from '@lexical/utils';
import { createCommand, $getNodeByKey, COMMAND_PRIORITY_EDITOR, $getSelection, COMMAND_PRIORITY_LOW, PASTE_TAG } from 'lexical';
import { useState, useCallback, useEffect, useMemo } from 'react';
import { jsx } from '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 = createCommand('INSERT_EMBED_COMMAND');
class AutoEmbedOption extends MenuOption {
constructor(title, options) {
super(title);
this.title = title;
this.onSelect = options.onSelect.bind(this);
}
}
function LexicalAutoEmbedPlugin({
embedConfigs,
onOpenEmbedModalForConfig,
getMenuOptions,
menuRenderFn,
menuCommandPriority = COMMAND_PRIORITY_LOW
}) {
const [editor] = useLexicalComposerContext();
const [nodeKey, setNodeKey] = useState(null);
const [activeEmbedConfig, setActiveEmbedConfig] = useState(null);
const reset = useCallback(() => {
setNodeKey(null);
setActiveEmbedConfig(null);
}, []);
const checkIfLinkNodeIsEmbeddable = useCallback(async key => {
const url = editor.getEditorState().read(function () {
const linkNode = $getNodeByKey(key);
if ($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]);
useEffect(() => {
const listener = (nodeMutations, {
updateTags,
dirtyLeaves
}) => {
for (const [key, mutation] of nodeMutations) {
if (mutation === 'created' && updateTags.has(PASTE_TAG) && dirtyLeaves.size <= 3) {
checkIfLinkNodeIsEmbeddable(key);
} else if (key === nodeKey) {
reset();
}
}
};
return mergeRegister(...[LinkNode, AutoLinkNode].map(Klass => editor.registerMutationListener(Klass, (...args) => listener(...args), {
skipInitialization: true
})));
}, [checkIfLinkNodeIsEmbeddable, editor, embedConfigs, nodeKey, reset]);
useEffect(() => {
return editor.registerCommand(INSERT_EMBED_COMMAND, embedConfigType => {
const embedConfig = embedConfigs.find(({
type
}) => type === embedConfigType);
if (embedConfig) {
onOpenEmbedModalForConfig(embedConfig);
return true;
}
return false;
}, COMMAND_PRIORITY_EDITOR);
}, [editor, embedConfigs, onOpenEmbedModalForConfig]);
const embedLinkViaActiveEmbedConfig = useCallback(async function () {
if (activeEmbedConfig != null && nodeKey != null) {
const linkNode = editor.getEditorState().read(() => {
const node = $getNodeByKey(nodeKey);
if ($isLinkNode(node)) {
return node;
}
return null;
});
if ($isLinkNode(linkNode)) {
const result = await Promise.resolve(activeEmbedConfig.parseUrl(linkNode.__url));
if (result != null) {
editor.update(() => {
if (!$getSelection()) {
linkNode.selectEnd();
}
activeEmbedConfig.insertNode(editor, result);
if (linkNode.isAttached()) {
linkNode.remove();
}
});
}
}
}
}, [activeEmbedConfig, editor, nodeKey]);
const options = useMemo(() => {
return activeEmbedConfig != null && nodeKey != null ? getMenuOptions(activeEmbedConfig, embedLinkViaActiveEmbedConfig, reset) : [];
}, [activeEmbedConfig, embedLinkViaActiveEmbedConfig, getMenuOptions, nodeKey, reset]);
const onSelectOption = useCallback((selectedOption, targetNode, closeMenu) => {
editor.update(() => {
selectedOption.onSelect(targetNode);
closeMenu();
});
}, [editor]);
return nodeKey != null ? /*#__PURE__*/jsx(LexicalNodeMenuPlugin, {
nodeKey: nodeKey,
onClose: reset,
onSelectOption: onSelectOption,
options: options,
menuRenderFn: menuRenderFn,
commandPriority: menuCommandPriority
}) : null;
}
export { AutoEmbedOption, INSERT_EMBED_COMMAND, LexicalAutoEmbedPlugin, URL_MATCHER };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalAutoEmbedPlugin = process.env.NODE_ENV !== 'production' ? require('./LexicalAutoEmbedPlugin.dev.js') : require('./LexicalAutoEmbedPlugin.prod.js');
module.exports = LexicalAutoEmbedPlugin;
+64
View File
@@ -0,0 +1,64 @@
/**
* 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.
*
* @flow strict
*/
import type {LexicalNode, MutationListener} from 'lexical';
import {MenuOption} from '@lexical/react/LexicalTypeaheadMenuPlugin';
import type {LexicalCommand, LexicalEditor, NodeKey, TextNode} from 'lexical';
import * as React from 'react';
import {createCommand} from 'lexical';
import type {MenuRenderFn} from './LexicalTypeaheadMenuPlugin';
export type EmbedMatchResult = {
url: string,
id: string,
};
export interface EmbedConfig {
// Used to identify this config e.g. youtube, tweet, google-maps.
type: string;
// Determine if a given URL is a match and return url data.
parseUrl: (text: string) => EmbedMatchResult | null;
// Create the Lexical embed node from the url data.
insertNode: (editor: LexicalEditor, result: EmbedMatchResult) => void;
}
export const URL_MATCHER: RegExp =
/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
export const INSERT_EMBED_COMMAND: LexicalCommand<EmbedConfig['type']> =
createCommand('INSERT_EMBED_COMMAND');
type LexicalAutoEmbedPluginProps<TEmbedConfig> = {
embedConfigs: Array<TEmbedConfig>,
onOpenEmbedModalForConfig: (embedConfig: TEmbedConfig) => void,
getMenuOptions: (
activeEmbedConfig: TEmbedConfig,
embedFn: () => void,
dismissFn: () => void,
) => Array<AutoEmbedOption>,
menuRenderFn: MenuRenderFn<AutoEmbedOption>,
};
declare export class AutoEmbedOption extends MenuOption {
title: string;
icon: React.MixedElement;
onSelect: (targetNode: LexicalNode | null) => void;
constructor(
title: string,
options: {
icon: React.MixedElement,
onSelect: (targetNode: LexicalNode | null) => void,
},
): void;
}
declare export function LexicalAutoEmbedPlugin<TEmbedConfig>(
LexicalAutoEmbedPluginProps<TEmbedConfig>,
): React.MixedElement | null;
+15
View File
@@ -0,0 +1,15 @@
/**
* 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.
*
*/
import * as modDev from './LexicalAutoEmbedPlugin.dev.mjs';
import * as modProd from './LexicalAutoEmbedPlugin.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const AutoEmbedOption = mod.AutoEmbedOption;
export const INSERT_EMBED_COMMAND = mod.INSERT_EMBED_COMMAND;
export const LexicalAutoEmbedPlugin = mod.LexicalAutoEmbedPlugin;
export const URL_MATCHER = mod.URL_MATCHER;
+13
View File
@@ -0,0 +1,13 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalAutoEmbedPlugin.dev.mjs') : import('./LexicalAutoEmbedPlugin.prod.mjs'));
export const AutoEmbedOption = mod.AutoEmbedOption;
export const INSERT_EMBED_COMMAND = mod.INSERT_EMBED_COMMAND;
export const LexicalAutoEmbedPlugin = mod.LexicalAutoEmbedPlugin;
export const URL_MATCHER = mod.URL_MATCHER;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var e=require("@lexical/link"),t=require("@lexical/react/LexicalComposerContext"),n=require("@lexical/react/LexicalNodeMenuPlugin"),o=require("@lexical/utils"),i=require("lexical"),r=require("react"),l=require("react/jsx-runtime");const s=i.createCommand("INSERT_EMBED_COMMAND");class u extends n.MenuOption{constructor(e,t){super(e),this.title=e,this.onSelect=t.onSelect.bind(this)}}exports.AutoEmbedOption=u,exports.INSERT_EMBED_COMMAND=s,exports.LexicalAutoEmbedPlugin=function({embedConfigs:u,onOpenEmbedModalForConfig:a,getMenuOptions:c,menuRenderFn:d,menuCommandPriority:m=i.COMMAND_PRIORITY_LOW}){const[p]=t.useLexicalComposerContext(),[x,C]=r.useState(null),[f,M]=r.useState(null),g=r.useCallback((()=>{C(null),M(null)}),[]),E=r.useCallback((async t=>{const n=p.getEditorState().read((function(){const n=i.$getNodeByKey(t);if(e.$isLinkNode(n))return n.getURL()}));if(void 0!==n)for(const e of u){null!=await Promise.resolve(e.parseUrl(n))&&(M(e),C(t))}}),[p,u]);r.useEffect((()=>o.mergeRegister(...[e.LinkNode,e.AutoLinkNode].map((e=>p.registerMutationListener(e,((...e)=>((e,{updateTags:t,dirtyLeaves:n})=>{for(const[o,r]of e)"created"===r&&t.has(i.PASTE_TAG)&&n.size<=3?E(o):o===x&&g()})(...e)),{skipInitialization:!0}))))),[E,p,u,x,g]),r.useEffect((()=>p.registerCommand(s,(e=>{const t=u.find((({type:t})=>t===e));return!!t&&(a(t),!0)}),i.COMMAND_PRIORITY_EDITOR)),[p,u,a]);const N=r.useCallback((async function(){if(null!=f&&null!=x){const t=p.getEditorState().read((()=>{const t=i.$getNodeByKey(x);return e.$isLinkNode(t)?t:null}));if(e.$isLinkNode(t)){const e=await Promise.resolve(f.parseUrl(t.__url));null!=e&&p.update((()=>{i.$getSelection()||t.selectEnd(),f.insertNode(p,e),t.isAttached()&&t.remove()}))}}}),[f,p,x]),L=r.useMemo((()=>null!=f&&null!=x?c(f,N,g):[]),[f,N,c,x,g]),A=r.useCallback(((e,t,n)=>{p.update((()=>{e.onSelect(t),n()}))}),[p]);return null!=x?l.jsx(n.LexicalNodeMenuPlugin,{nodeKey:x,onClose:g,onSelectOption:A,options:L,menuRenderFn:d,commandPriority:m}):null},exports.URL_MATCHER=/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{$isLinkNode as e,LinkNode as t,AutoLinkNode as n}from"@lexical/link";import{useLexicalComposerContext as o}from"@lexical/react/LexicalComposerContext";import{MenuOption as r,LexicalNodeMenuPlugin as i}from"@lexical/react/LexicalNodeMenuPlugin";import{mergeRegister as l}from"@lexical/utils";import{createCommand as s,$getNodeByKey as a,COMMAND_PRIORITY_EDITOR as c,$getSelection as u,COMMAND_PRIORITY_LOW as m,PASTE_TAG as d}from"lexical";import{useState as p,useCallback as f,useEffect as x,useMemo as g}from"react";import{jsx as w}from"react/jsx-runtime";const C=/((https?:\/\/(www\.)?)|(www\.))[-a-zA-Z0-9@:%._+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_+.~#?&//=]*)/,y=s("INSERT_EMBED_COMMAND");class E extends r{constructor(e,t){super(e),this.title=e,this.onSelect=t.onSelect.bind(this)}}function M({embedConfigs:r,onOpenEmbedModalForConfig:s,getMenuOptions:C,menuRenderFn:E,menuCommandPriority:M=m}){const[S]=o(),[h,_]=p(null),[v,z]=p(null),A=f((()=>{_(null),z(null)}),[]),L=f((async t=>{const n=S.getEditorState().read((function(){const n=a(t);if(e(n))return n.getURL()}));if(void 0!==n)for(const e of r){null!=await Promise.resolve(e.parseUrl(n))&&(z(e),_(t))}}),[S,r]);x((()=>l(...[t,n].map((e=>S.registerMutationListener(e,((...e)=>((e,{updateTags:t,dirtyLeaves:n})=>{for(const[o,r]of e)"created"===r&&t.has(d)&&n.size<=3?L(o):o===h&&A()})(...e)),{skipInitialization:!0}))))),[L,S,r,h,A]),x((()=>S.registerCommand(y,(e=>{const t=r.find((({type:t})=>t===e));return!!t&&(s(t),!0)}),c)),[S,r,s]);const P=f((async function(){if(null!=v&&null!=h){const t=S.getEditorState().read((()=>{const t=a(h);return e(t)?t:null}));if(e(t)){const e=await Promise.resolve(v.parseUrl(t.__url));null!=e&&S.update((()=>{u()||t.selectEnd(),v.insertNode(S,e),t.isAttached()&&t.remove()}))}}}),[v,S,h]),b=g((()=>null!=v&&null!=h?C(v,P,A):[]),[v,P,C,h,A]),N=f(((e,t,n)=>{S.update((()=>{e.onSelect(t),n()}))}),[S]);return null!=h?w(i,{nodeKey:h,onClose:A,onSelectOption:N,options:b,menuRenderFn:E,commandPriority:M}):null}export{E as AutoEmbedOption,y as INSERT_EMBED_COMMAND,M as LexicalAutoEmbedPlugin,C as URL_MATCHER};
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
*/
type Props = {
defaultSelection?: 'rootStart' | 'rootEnd';
};
export declare function AutoFocusPlugin({ defaultSelection }: Props): null;
export {};
+47
View File
@@ -0,0 +1,47 @@
/**
* 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.
*
*/
'use strict';
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var react = require('react');
/**
* 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.
*
*/
function AutoFocusPlugin({
defaultSelection
}) {
const [editor] = LexicalComposerContext.useLexicalComposerContext();
react.useEffect(() => {
editor.focus(() => {
// If we try and move selection to the same point with setBaseAndExtent, it won't
// trigger a re-focus on the element. So in the case this occurs, we'll need to correct it.
// Normally this is fine, Selection API !== Focus API, but fore the intents of the naming
// of this plugin, which should preserve focus too.
const activeElement = document.activeElement;
const rootElement = editor.getRootElement();
if (rootElement !== null && (activeElement === null || !rootElement.contains(activeElement))) {
// Note: preventScroll won't work in Webkit.
rootElement.focus({
preventScroll: true
});
}
}, {
defaultSelection
});
}, [defaultSelection, editor]);
return null;
}
exports.AutoFocusPlugin = AutoFocusPlugin;
+45
View File
@@ -0,0 +1,45 @@
/**
* 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.
*
*/
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
/**
* 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.
*
*/
function AutoFocusPlugin({
defaultSelection
}) {
const [editor] = useLexicalComposerContext();
useEffect(() => {
editor.focus(() => {
// If we try and move selection to the same point with setBaseAndExtent, it won't
// trigger a re-focus on the element. So in the case this occurs, we'll need to correct it.
// Normally this is fine, Selection API !== Focus API, but fore the intents of the naming
// of this plugin, which should preserve focus too.
const activeElement = document.activeElement;
const rootElement = editor.getRootElement();
if (rootElement !== null && (activeElement === null || !rootElement.contains(activeElement))) {
// Note: preventScroll won't work in Webkit.
rootElement.focus({
preventScroll: true
});
}
}, {
defaultSelection
});
}, [defaultSelection, editor]);
return null;
}
export { AutoFocusPlugin };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalAutoFocusPlugin = process.env.NODE_ENV !== 'production' ? require('./LexicalAutoFocusPlugin.dev.js') : require('./LexicalAutoFocusPlugin.prod.js');
module.exports = LexicalAutoFocusPlugin;
+14
View File
@@ -0,0 +1,14 @@
/**
* 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.
*
* @flow strict
*/
type Props = $ReadOnly<{
defaultSelection?: 'rootStart' | 'rootEnd',
}>;
declare export function AutoFocusPlugin(props: Props): null;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
*/
import * as modDev from './LexicalAutoFocusPlugin.dev.mjs';
import * as modProd from './LexicalAutoFocusPlugin.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const AutoFocusPlugin = mod.AutoFocusPlugin;
+10
View File
@@ -0,0 +1,10 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalAutoFocusPlugin.dev.mjs') : import('./LexicalAutoFocusPlugin.prod.mjs'));
export const AutoFocusPlugin = mod.AutoFocusPlugin;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var e=require("@lexical/react/LexicalComposerContext"),t=require("react");exports.AutoFocusPlugin=function({defaultSelection:o}){const[c]=e.useLexicalComposerContext();return t.useEffect((()=>{c.focus((()=>{const e=document.activeElement,t=c.getRootElement();null===t||null!==e&&t.contains(e)||t.focus({preventScroll:!0})}),{defaultSelection:o})}),[o,c]),null};
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{useLexicalComposerContext as e}from"@lexical/react/LexicalComposerContext";import{useEffect as t}from"react";function o({defaultSelection:o}){const[l]=e();return t((()=>{l.focus((()=>{const e=document.activeElement,t=l.getRootElement();null===t||null!==e&&t.contains(e)||t.focus({preventScroll:!0})}),{defaultSelection:o})}),[o,l]),null}export{o as AutoFocusPlugin};
+29
View File
@@ -0,0 +1,29 @@
/**
* 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.
*
*/
import type { AutoLinkAttributes } from '@lexical/link';
import type { JSX } from 'react';
type ChangeHandler = (url: string | null, prevUrl: string | null) => void;
type LinkMatcherResult = {
attributes?: AutoLinkAttributes;
index: number;
length: number;
text: string;
url: string;
};
export type LinkMatcher = (text: string) => LinkMatcherResult | null;
export declare function createLinkMatcherWithRegExp(regExp: RegExp, urlTransformer?: (text: string) => string): (text: string) => {
index: number;
length: number;
text: string;
url: string;
} | null;
export declare function AutoLinkPlugin({ matchers, onChange, }: {
matchers: Array<LinkMatcher>;
onChange?: ChangeHandler;
}): JSX.Element | null;
export {};
+356
View File
@@ -0,0 +1,356 @@
/**
* 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.
*
*/
'use strict';
var link = require('@lexical/link');
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var utils = require('@lexical/utils');
var lexical = require('lexical');
var react = require('react');
/**
* 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.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
function createLinkMatcherWithRegExp(regExp, urlTransformer = text => text) {
return text => {
const match = regExp.exec(text);
if (match === null) {
return null;
}
return {
index: match.index,
length: match[0].length,
text: match[0],
url: urlTransformer(match[0])
};
};
}
function findFirstMatch(text, matchers) {
for (let i = 0; i < matchers.length; i++) {
const match = matchers[i](text);
if (match) {
return match;
}
}
return null;
}
const PUNCTUATION_OR_SPACE = /[.,;\s]/;
function isSeparator(char) {
return PUNCTUATION_OR_SPACE.test(char);
}
function endsWithSeparator(textContent) {
return isSeparator(textContent[textContent.length - 1]);
}
function startsWithSeparator(textContent) {
return isSeparator(textContent[0]);
}
/**
* Check if the text content starts with a fullstop followed by a top-level domain.
* Meaning if the text content can be a beginning of a top level domain.
* @param textContent
* @param isEmail
* @returns boolean
*/
function startsWithTLD(textContent, isEmail) {
if (isEmail) {
return /^\.[a-zA-Z]{2,}/.test(textContent);
} else {
return /^\.[a-zA-Z0-9]{1,}/.test(textContent);
}
}
function isPreviousNodeValid(node) {
let previousNode = node.getPreviousSibling();
if (lexical.$isElementNode(previousNode)) {
previousNode = previousNode.getLastDescendant();
}
return previousNode === null || lexical.$isLineBreakNode(previousNode) || lexical.$isTextNode(previousNode) && endsWithSeparator(previousNode.getTextContent());
}
function isNextNodeValid(node) {
let nextNode = node.getNextSibling();
if (lexical.$isElementNode(nextNode)) {
nextNode = nextNode.getFirstDescendant();
}
return nextNode === null || lexical.$isLineBreakNode(nextNode) || lexical.$isTextNode(nextNode) && startsWithSeparator(nextNode.getTextContent());
}
function isContentAroundIsValid(matchStart, matchEnd, text, nodes) {
const contentBeforeIsValid = matchStart > 0 ? isSeparator(text[matchStart - 1]) : isPreviousNodeValid(nodes[0]);
if (!contentBeforeIsValid) {
return false;
}
const contentAfterIsValid = matchEnd < text.length ? isSeparator(text[matchEnd]) : isNextNodeValid(nodes[nodes.length - 1]);
return contentAfterIsValid;
}
function extractMatchingNodes(nodes, startIndex, endIndex) {
const unmodifiedBeforeNodes = [];
const matchingNodes = [];
const unmodifiedAfterNodes = [];
let matchingOffset = 0;
let currentOffset = 0;
const currentNodes = [...nodes];
while (currentNodes.length > 0) {
const currentNode = currentNodes[0];
const currentNodeText = currentNode.getTextContent();
const currentNodeLength = currentNodeText.length;
const currentNodeStart = currentOffset;
const currentNodeEnd = currentOffset + currentNodeLength;
if (currentNodeEnd <= startIndex) {
unmodifiedBeforeNodes.push(currentNode);
matchingOffset += currentNodeLength;
} else if (currentNodeStart >= endIndex) {
unmodifiedAfterNodes.push(currentNode);
} else {
matchingNodes.push(currentNode);
}
currentOffset += currentNodeLength;
currentNodes.shift();
}
return [matchingOffset, unmodifiedBeforeNodes, matchingNodes, unmodifiedAfterNodes];
}
function $createAutoLinkNode_(nodes, startIndex, endIndex, match) {
const linkNode = link.$createAutoLinkNode(match.url, match.attributes);
if (nodes.length === 1) {
let remainingTextNode = nodes[0];
let linkTextNode;
if (startIndex === 0) {
[linkTextNode, remainingTextNode] = remainingTextNode.splitText(endIndex);
} else {
[, linkTextNode, remainingTextNode] = remainingTextNode.splitText(startIndex, endIndex);
}
const textNode = lexical.$createTextNode(match.text);
textNode.setFormat(linkTextNode.getFormat());
textNode.setDetail(linkTextNode.getDetail());
textNode.setStyle(linkTextNode.getStyle());
linkNode.append(textNode);
linkTextNode.replace(linkNode);
return remainingTextNode;
} else if (nodes.length > 1) {
const firstTextNode = nodes[0];
let offset = firstTextNode.getTextContent().length;
let firstLinkTextNode;
if (startIndex === 0) {
firstLinkTextNode = firstTextNode;
} else {
[, firstLinkTextNode] = firstTextNode.splitText(startIndex);
}
const linkNodes = [];
let remainingTextNode;
for (let i = 1; i < nodes.length; i++) {
const currentNode = nodes[i];
const currentNodeText = currentNode.getTextContent();
const currentNodeLength = currentNodeText.length;
const currentNodeStart = offset;
const currentNodeEnd = offset + currentNodeLength;
if (currentNodeStart < endIndex) {
if (currentNodeEnd <= endIndex) {
linkNodes.push(currentNode);
} else {
const [linkTextNode, endNode] = currentNode.splitText(endIndex - currentNodeStart);
linkNodes.push(linkTextNode);
remainingTextNode = endNode;
}
}
offset += currentNodeLength;
}
const selection = lexical.$getSelection();
const selectedTextNode = selection ? selection.getNodes().find(lexical.$isTextNode) : undefined;
const textNode = lexical.$createTextNode(firstLinkTextNode.getTextContent());
textNode.setFormat(firstLinkTextNode.getFormat());
textNode.setDetail(firstLinkTextNode.getDetail());
textNode.setStyle(firstLinkTextNode.getStyle());
linkNode.append(textNode, ...linkNodes);
// it does not preserve caret position if caret was at the first text node
// so we need to restore caret position
if (selectedTextNode && selectedTextNode === firstLinkTextNode) {
if (lexical.$isRangeSelection(selection)) {
textNode.select(selection.anchor.offset, selection.focus.offset);
} else if (lexical.$isNodeSelection(selection)) {
textNode.select(0, textNode.getTextContent().length);
}
}
firstLinkTextNode.replace(linkNode);
return remainingTextNode;
}
return undefined;
}
function $handleLinkCreation(nodes, matchers, onChange) {
let currentNodes = [...nodes];
const initialText = currentNodes.map(node => node.getTextContent()).join('');
let text = initialText;
let match;
let invalidMatchEnd = 0;
while ((match = findFirstMatch(text, matchers)) && match !== null) {
const matchStart = match.index;
const matchLength = match.length;
const matchEnd = matchStart + matchLength;
const isValid = isContentAroundIsValid(invalidMatchEnd + matchStart, invalidMatchEnd + matchEnd, initialText, currentNodes);
if (isValid) {
const [matchingOffset,, matchingNodes, unmodifiedAfterNodes] = extractMatchingNodes(currentNodes, invalidMatchEnd + matchStart, invalidMatchEnd + matchEnd);
const actualMatchStart = invalidMatchEnd + matchStart - matchingOffset;
const actualMatchEnd = invalidMatchEnd + matchEnd - matchingOffset;
const remainingTextNode = $createAutoLinkNode_(matchingNodes, actualMatchStart, actualMatchEnd, match);
currentNodes = remainingTextNode ? [remainingTextNode, ...unmodifiedAfterNodes] : unmodifiedAfterNodes;
onChange(match.url, null);
invalidMatchEnd = 0;
} else {
invalidMatchEnd += matchEnd;
}
text = text.substring(matchEnd);
}
}
function handleLinkEdit(linkNode, matchers, onChange) {
// Check children are simple text
const children = linkNode.getChildren();
const childrenLength = children.length;
for (let i = 0; i < childrenLength; i++) {
const child = children[i];
if (!lexical.$isTextNode(child) || !child.isSimpleText()) {
replaceWithChildren(linkNode);
onChange(null, linkNode.getURL());
return;
}
}
// Check text content fully matches
const text = linkNode.getTextContent();
const match = findFirstMatch(text, matchers);
if (match === null || match.text !== text) {
replaceWithChildren(linkNode);
onChange(null, linkNode.getURL());
return;
}
// Check neighbors
if (!isPreviousNodeValid(linkNode) || !isNextNodeValid(linkNode)) {
replaceWithChildren(linkNode);
onChange(null, linkNode.getURL());
return;
}
const url = linkNode.getURL();
if (url !== match.url) {
linkNode.setURL(match.url);
onChange(match.url, url);
}
if (match.attributes) {
const rel = linkNode.getRel();
if (rel !== match.attributes.rel) {
linkNode.setRel(match.attributes.rel || null);
onChange(match.attributes.rel || null, rel);
}
const target = linkNode.getTarget();
if (target !== match.attributes.target) {
linkNode.setTarget(match.attributes.target || null);
onChange(match.attributes.target || null, target);
}
}
}
// Bad neighbors are edits in neighbor nodes that make AutoLinks incompatible.
// Given the creation preconditions, these can only be simple text nodes.
function handleBadNeighbors(textNode, matchers, onChange) {
const previousSibling = textNode.getPreviousSibling();
const nextSibling = textNode.getNextSibling();
const text = textNode.getTextContent();
if (link.$isAutoLinkNode(previousSibling) && !previousSibling.getIsUnlinked() && (!startsWithSeparator(text) || startsWithTLD(text, previousSibling.isEmailURI()))) {
previousSibling.append(textNode);
handleLinkEdit(previousSibling, matchers, onChange);
onChange(null, previousSibling.getURL());
}
if (link.$isAutoLinkNode(nextSibling) && !nextSibling.getIsUnlinked() && !endsWithSeparator(text)) {
replaceWithChildren(nextSibling);
handleLinkEdit(nextSibling, matchers, onChange);
onChange(null, nextSibling.getURL());
}
}
function replaceWithChildren(node) {
const children = node.getChildren();
const childrenLength = children.length;
for (let j = childrenLength - 1; j >= 0; j--) {
node.insertAfter(children[j]);
}
node.remove();
return children.map(child => child.getLatest());
}
function getTextNodesToMatch(textNode) {
// check if next siblings are simple text nodes till a node contains a space separator
const textNodesToMatch = [textNode];
let nextSibling = textNode.getNextSibling();
while (nextSibling !== null && lexical.$isTextNode(nextSibling) && nextSibling.isSimpleText()) {
textNodesToMatch.push(nextSibling);
if (/[\s]/.test(nextSibling.getTextContent())) {
break;
}
nextSibling = nextSibling.getNextSibling();
}
return textNodesToMatch;
}
function useAutoLink(editor, matchers, onChange) {
react.useEffect(() => {
if (!editor.hasNodes([link.AutoLinkNode])) {
{
formatDevErrorMessage(`LexicalAutoLinkPlugin: AutoLinkNode not registered on editor`);
}
}
const onChangeWrapped = (url, prevUrl) => {
if (onChange) {
onChange(url, prevUrl);
}
};
return utils.mergeRegister(editor.registerNodeTransform(lexical.TextNode, textNode => {
const parent = textNode.getParentOrThrow();
const previous = textNode.getPreviousSibling();
if (link.$isAutoLinkNode(parent) && !parent.getIsUnlinked()) {
handleLinkEdit(parent, matchers, onChangeWrapped);
} else if (!link.$isLinkNode(parent)) {
if (textNode.isSimpleText() && (startsWithSeparator(textNode.getTextContent()) || !link.$isAutoLinkNode(previous))) {
const textNodesToMatch = getTextNodesToMatch(textNode);
$handleLinkCreation(textNodesToMatch, matchers, onChangeWrapped);
}
handleBadNeighbors(textNode, matchers, onChangeWrapped);
}
}), editor.registerCommand(link.TOGGLE_LINK_COMMAND, payload => {
const selection = lexical.$getSelection();
if (payload !== null || !lexical.$isRangeSelection(selection)) {
return false;
}
const nodes = selection.extract();
nodes.forEach(node => {
const parent = node.getParent();
if (link.$isAutoLinkNode(parent)) {
// invert the value
parent.setIsUnlinked(!parent.getIsUnlinked());
parent.markDirty();
}
});
return false;
}, lexical.COMMAND_PRIORITY_LOW));
}, [editor, matchers, onChange]);
}
function AutoLinkPlugin({
matchers,
onChange
}) {
const [editor] = LexicalComposerContext.useLexicalComposerContext();
useAutoLink(editor, matchers, onChange);
return null;
}
exports.AutoLinkPlugin = AutoLinkPlugin;
exports.createLinkMatcherWithRegExp = createLinkMatcherWithRegExp;
+353
View File
@@ -0,0 +1,353 @@
/**
* 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.
*
*/
import { AutoLinkNode, $isAutoLinkNode, $isLinkNode, TOGGLE_LINK_COMMAND, $createAutoLinkNode } from '@lexical/link';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { mergeRegister } from '@lexical/utils';
import { TextNode, $getSelection, $isRangeSelection, COMMAND_PRIORITY_LOW, $isTextNode, $isElementNode, $isLineBreakNode, $createTextNode, $isNodeSelection } from 'lexical';
import { useEffect } from 'react';
/**
* 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.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
function createLinkMatcherWithRegExp(regExp, urlTransformer = text => text) {
return text => {
const match = regExp.exec(text);
if (match === null) {
return null;
}
return {
index: match.index,
length: match[0].length,
text: match[0],
url: urlTransformer(match[0])
};
};
}
function findFirstMatch(text, matchers) {
for (let i = 0; i < matchers.length; i++) {
const match = matchers[i](text);
if (match) {
return match;
}
}
return null;
}
const PUNCTUATION_OR_SPACE = /[.,;\s]/;
function isSeparator(char) {
return PUNCTUATION_OR_SPACE.test(char);
}
function endsWithSeparator(textContent) {
return isSeparator(textContent[textContent.length - 1]);
}
function startsWithSeparator(textContent) {
return isSeparator(textContent[0]);
}
/**
* Check if the text content starts with a fullstop followed by a top-level domain.
* Meaning if the text content can be a beginning of a top level domain.
* @param textContent
* @param isEmail
* @returns boolean
*/
function startsWithTLD(textContent, isEmail) {
if (isEmail) {
return /^\.[a-zA-Z]{2,}/.test(textContent);
} else {
return /^\.[a-zA-Z0-9]{1,}/.test(textContent);
}
}
function isPreviousNodeValid(node) {
let previousNode = node.getPreviousSibling();
if ($isElementNode(previousNode)) {
previousNode = previousNode.getLastDescendant();
}
return previousNode === null || $isLineBreakNode(previousNode) || $isTextNode(previousNode) && endsWithSeparator(previousNode.getTextContent());
}
function isNextNodeValid(node) {
let nextNode = node.getNextSibling();
if ($isElementNode(nextNode)) {
nextNode = nextNode.getFirstDescendant();
}
return nextNode === null || $isLineBreakNode(nextNode) || $isTextNode(nextNode) && startsWithSeparator(nextNode.getTextContent());
}
function isContentAroundIsValid(matchStart, matchEnd, text, nodes) {
const contentBeforeIsValid = matchStart > 0 ? isSeparator(text[matchStart - 1]) : isPreviousNodeValid(nodes[0]);
if (!contentBeforeIsValid) {
return false;
}
const contentAfterIsValid = matchEnd < text.length ? isSeparator(text[matchEnd]) : isNextNodeValid(nodes[nodes.length - 1]);
return contentAfterIsValid;
}
function extractMatchingNodes(nodes, startIndex, endIndex) {
const unmodifiedBeforeNodes = [];
const matchingNodes = [];
const unmodifiedAfterNodes = [];
let matchingOffset = 0;
let currentOffset = 0;
const currentNodes = [...nodes];
while (currentNodes.length > 0) {
const currentNode = currentNodes[0];
const currentNodeText = currentNode.getTextContent();
const currentNodeLength = currentNodeText.length;
const currentNodeStart = currentOffset;
const currentNodeEnd = currentOffset + currentNodeLength;
if (currentNodeEnd <= startIndex) {
unmodifiedBeforeNodes.push(currentNode);
matchingOffset += currentNodeLength;
} else if (currentNodeStart >= endIndex) {
unmodifiedAfterNodes.push(currentNode);
} else {
matchingNodes.push(currentNode);
}
currentOffset += currentNodeLength;
currentNodes.shift();
}
return [matchingOffset, unmodifiedBeforeNodes, matchingNodes, unmodifiedAfterNodes];
}
function $createAutoLinkNode_(nodes, startIndex, endIndex, match) {
const linkNode = $createAutoLinkNode(match.url, match.attributes);
if (nodes.length === 1) {
let remainingTextNode = nodes[0];
let linkTextNode;
if (startIndex === 0) {
[linkTextNode, remainingTextNode] = remainingTextNode.splitText(endIndex);
} else {
[, linkTextNode, remainingTextNode] = remainingTextNode.splitText(startIndex, endIndex);
}
const textNode = $createTextNode(match.text);
textNode.setFormat(linkTextNode.getFormat());
textNode.setDetail(linkTextNode.getDetail());
textNode.setStyle(linkTextNode.getStyle());
linkNode.append(textNode);
linkTextNode.replace(linkNode);
return remainingTextNode;
} else if (nodes.length > 1) {
const firstTextNode = nodes[0];
let offset = firstTextNode.getTextContent().length;
let firstLinkTextNode;
if (startIndex === 0) {
firstLinkTextNode = firstTextNode;
} else {
[, firstLinkTextNode] = firstTextNode.splitText(startIndex);
}
const linkNodes = [];
let remainingTextNode;
for (let i = 1; i < nodes.length; i++) {
const currentNode = nodes[i];
const currentNodeText = currentNode.getTextContent();
const currentNodeLength = currentNodeText.length;
const currentNodeStart = offset;
const currentNodeEnd = offset + currentNodeLength;
if (currentNodeStart < endIndex) {
if (currentNodeEnd <= endIndex) {
linkNodes.push(currentNode);
} else {
const [linkTextNode, endNode] = currentNode.splitText(endIndex - currentNodeStart);
linkNodes.push(linkTextNode);
remainingTextNode = endNode;
}
}
offset += currentNodeLength;
}
const selection = $getSelection();
const selectedTextNode = selection ? selection.getNodes().find($isTextNode) : undefined;
const textNode = $createTextNode(firstLinkTextNode.getTextContent());
textNode.setFormat(firstLinkTextNode.getFormat());
textNode.setDetail(firstLinkTextNode.getDetail());
textNode.setStyle(firstLinkTextNode.getStyle());
linkNode.append(textNode, ...linkNodes);
// it does not preserve caret position if caret was at the first text node
// so we need to restore caret position
if (selectedTextNode && selectedTextNode === firstLinkTextNode) {
if ($isRangeSelection(selection)) {
textNode.select(selection.anchor.offset, selection.focus.offset);
} else if ($isNodeSelection(selection)) {
textNode.select(0, textNode.getTextContent().length);
}
}
firstLinkTextNode.replace(linkNode);
return remainingTextNode;
}
return undefined;
}
function $handleLinkCreation(nodes, matchers, onChange) {
let currentNodes = [...nodes];
const initialText = currentNodes.map(node => node.getTextContent()).join('');
let text = initialText;
let match;
let invalidMatchEnd = 0;
while ((match = findFirstMatch(text, matchers)) && match !== null) {
const matchStart = match.index;
const matchLength = match.length;
const matchEnd = matchStart + matchLength;
const isValid = isContentAroundIsValid(invalidMatchEnd + matchStart, invalidMatchEnd + matchEnd, initialText, currentNodes);
if (isValid) {
const [matchingOffset,, matchingNodes, unmodifiedAfterNodes] = extractMatchingNodes(currentNodes, invalidMatchEnd + matchStart, invalidMatchEnd + matchEnd);
const actualMatchStart = invalidMatchEnd + matchStart - matchingOffset;
const actualMatchEnd = invalidMatchEnd + matchEnd - matchingOffset;
const remainingTextNode = $createAutoLinkNode_(matchingNodes, actualMatchStart, actualMatchEnd, match);
currentNodes = remainingTextNode ? [remainingTextNode, ...unmodifiedAfterNodes] : unmodifiedAfterNodes;
onChange(match.url, null);
invalidMatchEnd = 0;
} else {
invalidMatchEnd += matchEnd;
}
text = text.substring(matchEnd);
}
}
function handleLinkEdit(linkNode, matchers, onChange) {
// Check children are simple text
const children = linkNode.getChildren();
const childrenLength = children.length;
for (let i = 0; i < childrenLength; i++) {
const child = children[i];
if (!$isTextNode(child) || !child.isSimpleText()) {
replaceWithChildren(linkNode);
onChange(null, linkNode.getURL());
return;
}
}
// Check text content fully matches
const text = linkNode.getTextContent();
const match = findFirstMatch(text, matchers);
if (match === null || match.text !== text) {
replaceWithChildren(linkNode);
onChange(null, linkNode.getURL());
return;
}
// Check neighbors
if (!isPreviousNodeValid(linkNode) || !isNextNodeValid(linkNode)) {
replaceWithChildren(linkNode);
onChange(null, linkNode.getURL());
return;
}
const url = linkNode.getURL();
if (url !== match.url) {
linkNode.setURL(match.url);
onChange(match.url, url);
}
if (match.attributes) {
const rel = linkNode.getRel();
if (rel !== match.attributes.rel) {
linkNode.setRel(match.attributes.rel || null);
onChange(match.attributes.rel || null, rel);
}
const target = linkNode.getTarget();
if (target !== match.attributes.target) {
linkNode.setTarget(match.attributes.target || null);
onChange(match.attributes.target || null, target);
}
}
}
// Bad neighbors are edits in neighbor nodes that make AutoLinks incompatible.
// Given the creation preconditions, these can only be simple text nodes.
function handleBadNeighbors(textNode, matchers, onChange) {
const previousSibling = textNode.getPreviousSibling();
const nextSibling = textNode.getNextSibling();
const text = textNode.getTextContent();
if ($isAutoLinkNode(previousSibling) && !previousSibling.getIsUnlinked() && (!startsWithSeparator(text) || startsWithTLD(text, previousSibling.isEmailURI()))) {
previousSibling.append(textNode);
handleLinkEdit(previousSibling, matchers, onChange);
onChange(null, previousSibling.getURL());
}
if ($isAutoLinkNode(nextSibling) && !nextSibling.getIsUnlinked() && !endsWithSeparator(text)) {
replaceWithChildren(nextSibling);
handleLinkEdit(nextSibling, matchers, onChange);
onChange(null, nextSibling.getURL());
}
}
function replaceWithChildren(node) {
const children = node.getChildren();
const childrenLength = children.length;
for (let j = childrenLength - 1; j >= 0; j--) {
node.insertAfter(children[j]);
}
node.remove();
return children.map(child => child.getLatest());
}
function getTextNodesToMatch(textNode) {
// check if next siblings are simple text nodes till a node contains a space separator
const textNodesToMatch = [textNode];
let nextSibling = textNode.getNextSibling();
while (nextSibling !== null && $isTextNode(nextSibling) && nextSibling.isSimpleText()) {
textNodesToMatch.push(nextSibling);
if (/[\s]/.test(nextSibling.getTextContent())) {
break;
}
nextSibling = nextSibling.getNextSibling();
}
return textNodesToMatch;
}
function useAutoLink(editor, matchers, onChange) {
useEffect(() => {
if (!editor.hasNodes([AutoLinkNode])) {
{
formatDevErrorMessage(`LexicalAutoLinkPlugin: AutoLinkNode not registered on editor`);
}
}
const onChangeWrapped = (url, prevUrl) => {
if (onChange) {
onChange(url, prevUrl);
}
};
return mergeRegister(editor.registerNodeTransform(TextNode, textNode => {
const parent = textNode.getParentOrThrow();
const previous = textNode.getPreviousSibling();
if ($isAutoLinkNode(parent) && !parent.getIsUnlinked()) {
handleLinkEdit(parent, matchers, onChangeWrapped);
} else if (!$isLinkNode(parent)) {
if (textNode.isSimpleText() && (startsWithSeparator(textNode.getTextContent()) || !$isAutoLinkNode(previous))) {
const textNodesToMatch = getTextNodesToMatch(textNode);
$handleLinkCreation(textNodesToMatch, matchers, onChangeWrapped);
}
handleBadNeighbors(textNode, matchers, onChangeWrapped);
}
}), editor.registerCommand(TOGGLE_LINK_COMMAND, payload => {
const selection = $getSelection();
if (payload !== null || !$isRangeSelection(selection)) {
return false;
}
const nodes = selection.extract();
nodes.forEach(node => {
const parent = node.getParent();
if ($isAutoLinkNode(parent)) {
// invert the value
parent.setIsUnlinked(!parent.getIsUnlinked());
parent.markDirty();
}
});
return false;
}, COMMAND_PRIORITY_LOW));
}, [editor, matchers, onChange]);
}
function AutoLinkPlugin({
matchers,
onChange
}) {
const [editor] = useLexicalComposerContext();
useAutoLink(editor, matchers, onChange);
return null;
}
export { AutoLinkPlugin, createLinkMatcherWithRegExp };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalAutoLinkPlugin = process.env.NODE_ENV !== 'production' ? require('./LexicalAutoLinkPlugin.dev.js') : require('./LexicalAutoLinkPlugin.prod.js');
module.exports = LexicalAutoLinkPlugin;
+32
View File
@@ -0,0 +1,32 @@
/**
* 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.
*
* @flow strict
*/
import type {LinkAttributes} from '@lexical/link';
type ChangeHandler = (url: string | null, prevUrl: string | null) => void;
type LinkMatcherResult = {
attributes?: LinkAttributes,
index: number,
length: number,
text: string,
url: string,
};
export type LinkMatcher = (text: string) => LinkMatcherResult | null;
declare export function createLinkMatcherWithRegExp(
regExp: RegExp,
urlTransformer?: (text: string) => string,
): LinkMatcher;
declare export function AutoLinkPlugin(props: {
matchers: Array<LinkMatcher>,
onChange?: ChangeHandler,
}): React.Node;
+13
View File
@@ -0,0 +1,13 @@
/**
* 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.
*
*/
import * as modDev from './LexicalAutoLinkPlugin.dev.mjs';
import * as modProd from './LexicalAutoLinkPlugin.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const AutoLinkPlugin = mod.AutoLinkPlugin;
export const createLinkMatcherWithRegExp = mod.createLinkMatcherWithRegExp;
+11
View File
@@ -0,0 +1,11 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalAutoLinkPlugin.dev.mjs') : import('./LexicalAutoLinkPlugin.prod.mjs'));
export const AutoLinkPlugin = mod.AutoLinkPlugin;
export const createLinkMatcherWithRegExp = mod.createLinkMatcherWithRegExp;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var e=require("@lexical/link"),t=require("@lexical/react/LexicalComposerContext"),n=require("@lexical/utils"),i=require("lexical"),r=require("react");function o(e,t){for(let n=0;n<t.length;n++){const i=t[n](e);if(i)return i}return null}const l=/[.,;\s]/;function s(e){return l.test(e)}function u(e){return s(e[e.length-1])}function g(e){return s(e[0])}function c(e){let t=e.getPreviousSibling();return i.$isElementNode(t)&&(t=t.getLastDescendant()),null===t||i.$isLineBreakNode(t)||i.$isTextNode(t)&&u(t.getTextContent())}function a(e){let t=e.getNextSibling();return i.$isElementNode(t)&&(t=t.getFirstDescendant()),null===t||i.$isLineBreakNode(t)||i.$isTextNode(t)&&g(t.getTextContent())}function f(e,t,n,i){if(!(e>0?s(n[e-1]):c(i[0])))return!1;return t<n.length?s(n[t]):a(i[i.length-1])}function d(e,t,n){const i=[],r=[],o=[];let l=0,s=0;const u=[...e];for(;u.length>0;){const e=u[0],g=e.getTextContent().length,c=s;s+g<=t?(i.push(e),l+=g):c>=n?o.push(e):r.push(e),s+=g,u.shift()}return[l,i,r,o]}function x(t,n,r,o){const l=e.$createAutoLinkNode(o.url,o.attributes);if(1===t.length){let e,s=t[0];0===n?[e,s]=s.splitText(r):[,e,s]=s.splitText(n,r);const u=i.$createTextNode(o.text);return u.setFormat(e.getFormat()),u.setDetail(e.getDetail()),u.setStyle(e.getStyle()),l.append(u),e.replace(l),s}if(t.length>1){const e=t[0];let o,s=e.getTextContent().length;0===n?o=e:[,o]=e.splitText(n);const u=[];let g;for(let e=1;e<t.length;e++){const n=t[e],i=n.getTextContent().length,o=s;if(o<r)if(s+i<=r)u.push(n);else{const[e,t]=n.splitText(r-o);u.push(e),g=t}s+=i}const c=i.$getSelection(),a=c?c.getNodes().find(i.$isTextNode):void 0,f=i.$createTextNode(o.getTextContent());return f.setFormat(o.getFormat()),f.setDetail(o.getDetail()),f.setStyle(o.getStyle()),l.append(f,...u),a&&a===o&&(i.$isRangeSelection(c)?f.select(c.anchor.offset,c.focus.offset):i.$isNodeSelection(c)&&f.select(0,f.getTextContent().length)),o.replace(l),g}}function h(e,t,n){const r=e.getChildren(),l=r.length;for(let t=0;t<l;t++){const o=r[t];if(!i.$isTextNode(o)||!o.isSimpleText())return p(e),void n(null,e.getURL())}const s=e.getTextContent(),u=o(s,t);if(null===u||u.text!==s)return p(e),void n(null,e.getURL());if(!c(e)||!a(e))return p(e),void n(null,e.getURL());const g=e.getURL();if(g!==u.url&&(e.setURL(u.url),n(u.url,g)),u.attributes){const t=e.getRel();t!==u.attributes.rel&&(e.setRel(u.attributes.rel||null),n(u.attributes.rel||null,t));const i=e.getTarget();i!==u.attributes.target&&(e.setTarget(u.attributes.target||null),n(u.attributes.target||null,i))}}function p(e){const t=e.getChildren();for(let n=t.length-1;n>=0;n--)e.insertAfter(t[n]);return e.remove(),t.map((e=>e.getLatest()))}function T(t,l,s){r.useEffect((()=>{t.hasNodes([e.AutoLinkNode])||function(e,...t){const n=new URL("https://lexical.dev/docs/error"),i=new URLSearchParams;i.append("code",e);for(const e of t)i.append("v",e);throw n.search=i.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(77);const r=(e,t)=>{s&&s(e,t)};return n.mergeRegister(t.registerNodeTransform(i.TextNode,(t=>{const n=t.getParentOrThrow(),s=t.getPreviousSibling();if(e.$isAutoLinkNode(n)&&!n.getIsUnlinked())h(n,l,r);else if(!e.$isLinkNode(n)){if(t.isSimpleText()&&(g(t.getTextContent())||!e.$isAutoLinkNode(s))){const e=function(e){const t=[e];let n=e.getNextSibling();for(;null!==n&&i.$isTextNode(n)&&n.isSimpleText()&&(t.push(n),!/[\s]/.test(n.getTextContent()));)n=n.getNextSibling();return t}(t);!function(e,t,n){let i=[...e];const r=i.map((e=>e.getTextContent())).join("");let l,s=r,u=0;for(;(l=o(s,t))&&null!==l;){const e=l.index,t=e+l.length;if(f(u+e,u+t,r,i)){const[r,,o,s]=d(i,u+e,u+t),g=x(o,u+e-r,u+t-r,l);i=g?[g,...s]:s,n(l.url,null),u=0}else u+=t;s=s.substring(t)}}(e,l,r)}!function(t,n,i){const r=t.getPreviousSibling(),o=t.getNextSibling(),l=t.getTextContent();var s;!e.$isAutoLinkNode(r)||r.getIsUnlinked()||g(l)&&(s=l,!(r.isEmailURI()?/^\.[a-zA-Z]{2,}/.test(s):/^\.[a-zA-Z0-9]{1,}/.test(s)))||(r.append(t),h(r,n,i),i(null,r.getURL())),!e.$isAutoLinkNode(o)||o.getIsUnlinked()||u(l)||(p(o),h(o,n,i),i(null,o.getURL()))}(t,l,r)}})),t.registerCommand(e.TOGGLE_LINK_COMMAND,(t=>{const n=i.$getSelection();if(null!==t||!i.$isRangeSelection(n))return!1;return n.extract().forEach((t=>{const n=t.getParent();e.$isAutoLinkNode(n)&&(n.setIsUnlinked(!n.getIsUnlinked()),n.markDirty())})),!1}),i.COMMAND_PRIORITY_LOW))}),[t,l,s])}exports.AutoLinkPlugin=function({matchers:e,onChange:n}){const[i]=t.useLexicalComposerContext();return T(i,e,n),null},exports.createLinkMatcherWithRegExp=function(e,t=(e=>e)){return n=>{const i=e.exec(n);return null===i?null:{index:i.index,length:i[0].length,text:i[0],url:t(i[0])}}};
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{AutoLinkNode as t,$isAutoLinkNode as e,$isLinkNode as n,TOGGLE_LINK_COMMAND as r,$createAutoLinkNode as l}from"@lexical/link";import{useLexicalComposerContext as o}from"@lexical/react/LexicalComposerContext";import{mergeRegister as i}from"@lexical/utils";import{TextNode as s,$getSelection as u,$isRangeSelection as g,COMMAND_PRIORITY_LOW as c,$isTextNode as a,$isElementNode as f,$isLineBreakNode as x,$createTextNode as h,$isNodeSelection as p}from"lexical";import{useEffect as d}from"react";function m(t,e=(t=>t)){return n=>{const r=t.exec(n);return null===r?null:{index:r.index,length:r[0].length,text:r[0],url:e(r[0])}}}function T(t,e){for(let n=0;n<e.length;n++){const r=e[n](t);if(r)return r}return null}const C=/[.,;\s]/;function S(t){return C.test(t)}function b(t){return S(t[t.length-1])}function U(t){return S(t[0])}function v(t){let e=t.getPreviousSibling();return f(e)&&(e=e.getLastDescendant()),null===e||x(e)||a(e)&&b(e.getTextContent())}function L(t){let e=t.getNextSibling();return f(e)&&(e=e.getFirstDescendant()),null===e||x(e)||a(e)&&U(e.getTextContent())}function R(t,e,n,r){if(!(t>0?S(n[t-1]):v(r[0])))return!1;return e<n.length?S(n[e]):L(r[r.length-1])}function k(t,e,n){const r=[],l=[],o=[];let i=0,s=0;const u=[...t];for(;u.length>0;){const t=u[0],g=t.getTextContent().length,c=s;s+g<=e?(r.push(t),i+=g):c>=n?o.push(t):l.push(t),s+=g,u.shift()}return[i,r,l,o]}function D(t,e,n,r){const o=l(r.url,r.attributes);if(1===t.length){let l,i=t[0];0===e?[l,i]=i.splitText(n):[,l,i]=i.splitText(e,n);const s=h(r.text);return s.setFormat(l.getFormat()),s.setDetail(l.getDetail()),s.setStyle(l.getStyle()),o.append(s),l.replace(o),i}if(t.length>1){const r=t[0];let l,i=r.getTextContent().length;0===e?l=r:[,l]=r.splitText(e);const s=[];let c;for(let e=1;e<t.length;e++){const r=t[e],l=r.getTextContent().length,o=i;if(o<n)if(i+l<=n)s.push(r);else{const[t,e]=r.splitText(n-o);s.push(t),c=e}i+=l}const f=u(),x=f?f.getNodes().find(a):void 0,d=h(l.getTextContent());return d.setFormat(l.getFormat()),d.setDetail(l.getDetail()),d.setStyle(l.getStyle()),o.append(d,...s),x&&x===l&&(g(f)?d.select(f.anchor.offset,f.focus.offset):p(f)&&d.select(0,d.getTextContent().length)),l.replace(o),c}}function N(t,e,n){const r=t.getChildren(),l=r.length;for(let e=0;e<l;e++){const l=r[e];if(!a(l)||!l.isSimpleText())return I(t),void n(null,t.getURL())}const o=t.getTextContent(),i=T(o,e);if(null===i||i.text!==o)return I(t),void n(null,t.getURL());if(!v(t)||!L(t))return I(t),void n(null,t.getURL());const s=t.getURL();if(s!==i.url&&(t.setURL(i.url),n(i.url,s)),i.attributes){const e=t.getRel();e!==i.attributes.rel&&(t.setRel(i.attributes.rel||null),n(i.attributes.rel||null,e));const r=t.getTarget();r!==i.attributes.target&&(t.setTarget(i.attributes.target||null),n(i.attributes.target||null,r))}}function I(t){const e=t.getChildren();for(let n=e.length-1;n>=0;n--)t.insertAfter(e[n]);return t.remove(),e.map((t=>t.getLatest()))}function P(l,o,f){d((()=>{l.hasNodes([t])||function(t,...e){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",t);for(const t of e)r.append("v",t);throw n.search=r.toString(),Error(`Minified Lexical error #${t}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(77);const x=(t,e)=>{f&&f(t,e)};return i(l.registerNodeTransform(s,(t=>{const r=t.getParentOrThrow(),l=t.getPreviousSibling();if(e(r)&&!r.getIsUnlinked())N(r,o,x);else if(!n(r)){if(t.isSimpleText()&&(U(t.getTextContent())||!e(l))){const e=function(t){const e=[t];let n=t.getNextSibling();for(;null!==n&&a(n)&&n.isSimpleText()&&(e.push(n),!/[\s]/.test(n.getTextContent()));)n=n.getNextSibling();return e}(t);!function(t,e,n){let r=[...t];const l=r.map((t=>t.getTextContent())).join("");let o,i=l,s=0;for(;(o=T(i,e))&&null!==o;){const t=o.index,e=t+o.length;if(R(s+t,s+e,l,r)){const[l,,i,u]=k(r,s+t,s+e),g=D(i,s+t-l,s+e-l,o);r=g?[g,...u]:u,n(o.url,null),s=0}else s+=e;i=i.substring(e)}}(e,o,x)}!function(t,n,r){const l=t.getPreviousSibling(),o=t.getNextSibling(),i=t.getTextContent();var s;!e(l)||l.getIsUnlinked()||U(i)&&(s=i,!(l.isEmailURI()?/^\.[a-zA-Z]{2,}/.test(s):/^\.[a-zA-Z0-9]{1,}/.test(s)))||(l.append(t),N(l,n,r),r(null,l.getURL())),!e(o)||o.getIsUnlinked()||b(i)||(I(o),N(o,n,r),r(null,o.getURL()))}(t,o,x)}})),l.registerCommand(r,(t=>{const n=u();if(null!==t||!g(n))return!1;return n.extract().forEach((t=>{const n=t.getParent();e(n)&&(n.setIsUnlinked(!n.getIsUnlinked()),n.markDirty())})),!1}),c))}),[l,o,f])}function w({matchers:t,onChange:e}){const[n]=o();return P(n,t,e),null}export{w as AutoLinkPlugin,m as createLinkMatcherWithRegExp};
+21
View File
@@ -0,0 +1,21 @@
/**
* 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.
*
*/
import type { ElementFormatType, NodeKey } from 'lexical';
import type { JSX } from 'react';
import { ReactNode } from 'react';
type Props = Readonly<{
children: ReactNode;
format?: ElementFormatType | null;
nodeKey: NodeKey;
className: Readonly<{
base: string;
focus: string;
}>;
}>;
export declare function BlockWithAlignableContents({ children, format, nodeKey, className, }: Props): JSX.Element;
export {};
+81
View File
@@ -0,0 +1,81 @@
/**
* 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.
*
*/
'use strict';
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var LexicalDecoratorBlockNode = require('@lexical/react/LexicalDecoratorBlockNode');
var useLexicalNodeSelection = require('@lexical/react/useLexicalNodeSelection');
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.
*
*/
function BlockWithAlignableContents({
children,
format,
nodeKey,
className
}) {
const [editor] = LexicalComposerContext.useLexicalComposerContext();
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection.useLexicalNodeSelection(nodeKey);
const ref = react.useRef(null);
react.useEffect(() => {
return utils.mergeRegister(editor.registerCommand(lexical.FORMAT_ELEMENT_COMMAND, formatType => {
if (isSelected) {
const selection = lexical.$getSelection();
if (lexical.$isNodeSelection(selection)) {
const node = lexical.$getNodeByKey(nodeKey);
if (LexicalDecoratorBlockNode.$isDecoratorBlockNode(node)) {
node.setFormat(formatType);
}
} else if (lexical.$isRangeSelection(selection)) {
const nodes = selection.getNodes();
for (const node of nodes) {
if (LexicalDecoratorBlockNode.$isDecoratorBlockNode(node)) {
node.setFormat(formatType);
} else {
const element = utils.$getNearestBlockElementAncestorOrThrow(node);
element.setFormat(formatType);
}
}
}
return true;
}
return false;
}, lexical.COMMAND_PRIORITY_LOW), editor.registerCommand(lexical.CLICK_COMMAND, event => {
if (event.target === ref.current) {
event.preventDefault();
if (!event.shiftKey) {
clearSelection();
}
setSelected(!isSelected);
return true;
}
return false;
}, lexical.COMMAND_PRIORITY_LOW));
}, [clearSelection, editor, isSelected, nodeKey, setSelected]);
return /*#__PURE__*/jsxRuntime.jsx("div", {
className: [className.base, isSelected ? className.focus : null].filter(Boolean).join(' '),
ref: ref,
style: {
textAlign: format ? format : undefined
},
children: children
});
}
exports.BlockWithAlignableContents = BlockWithAlignableContents;
+79
View File
@@ -0,0 +1,79 @@
/**
* 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.
*
*/
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $isDecoratorBlockNode } from '@lexical/react/LexicalDecoratorBlockNode';
import { useLexicalNodeSelection } from '@lexical/react/useLexicalNodeSelection';
import { mergeRegister, $getNearestBlockElementAncestorOrThrow } from '@lexical/utils';
import { FORMAT_ELEMENT_COMMAND, $getSelection, $isNodeSelection, $getNodeByKey, $isRangeSelection, COMMAND_PRIORITY_LOW, CLICK_COMMAND } from 'lexical';
import { useRef, useEffect } from 'react';
import { jsx } from '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.
*
*/
function BlockWithAlignableContents({
children,
format,
nodeKey,
className
}) {
const [editor] = useLexicalComposerContext();
const [isSelected, setSelected, clearSelection] = useLexicalNodeSelection(nodeKey);
const ref = useRef(null);
useEffect(() => {
return mergeRegister(editor.registerCommand(FORMAT_ELEMENT_COMMAND, formatType => {
if (isSelected) {
const selection = $getSelection();
if ($isNodeSelection(selection)) {
const node = $getNodeByKey(nodeKey);
if ($isDecoratorBlockNode(node)) {
node.setFormat(formatType);
}
} else if ($isRangeSelection(selection)) {
const nodes = selection.getNodes();
for (const node of nodes) {
if ($isDecoratorBlockNode(node)) {
node.setFormat(formatType);
} else {
const element = $getNearestBlockElementAncestorOrThrow(node);
element.setFormat(formatType);
}
}
}
return true;
}
return false;
}, COMMAND_PRIORITY_LOW), editor.registerCommand(CLICK_COMMAND, event => {
if (event.target === ref.current) {
event.preventDefault();
if (!event.shiftKey) {
clearSelection();
}
setSelected(!isSelected);
return true;
}
return false;
}, COMMAND_PRIORITY_LOW));
}, [clearSelection, editor, isSelected, nodeKey, setSelected]);
return /*#__PURE__*/jsx("div", {
className: [className.base, isSelected ? className.focus : null].filter(Boolean).join(' '),
ref: ref,
style: {
textAlign: format ? format : undefined
},
children: children
});
}
export { BlockWithAlignableContents };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalBlockWithAlignableContents = process.env.NODE_ENV !== 'production' ? require('./LexicalBlockWithAlignableContents.dev.js') : require('./LexicalBlockWithAlignableContents.prod.js');
module.exports = LexicalBlockWithAlignableContents;
+28
View File
@@ -0,0 +1,28 @@
/**
* 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.
*
* @flow strict
*/
import type {
ElementFormatType,
LexicalEditor,
EditorThemeClasses,
LexicalNode,
NodeKey,
} from 'lexical';
type Props = $ReadOnly<{
children: React.Node,
format: ?ElementFormatType,
nodeKey: NodeKey,
className: $ReadOnly<{
base: string,
focus: string,
}>,
}>;
declare export function BlockWithAlignableContents(Props): React.Node;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
*/
import * as modDev from './LexicalBlockWithAlignableContents.dev.mjs';
import * as modProd from './LexicalBlockWithAlignableContents.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const BlockWithAlignableContents = mod.BlockWithAlignableContents;
+10
View File
@@ -0,0 +1,10 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalBlockWithAlignableContents.dev.mjs') : import('./LexicalBlockWithAlignableContents.prod.mjs'));
export const BlockWithAlignableContents = mod.BlockWithAlignableContents;
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var e=require("@lexical/react/LexicalComposerContext"),t=require("@lexical/react/LexicalDecoratorBlockNode"),r=require("@lexical/react/useLexicalNodeSelection"),o=require("@lexical/utils"),i=require("lexical"),l=require("react"),c=require("react/jsx-runtime");exports.BlockWithAlignableContents=function({children:s,format:a,nodeKey:n,className:u}){const[f]=e.useLexicalComposerContext(),[x,N,d]=r.useLexicalNodeSelection(n),m=l.useRef(null);return l.useEffect((()=>o.mergeRegister(f.registerCommand(i.FORMAT_ELEMENT_COMMAND,(e=>{if(x){const r=i.$getSelection();if(i.$isNodeSelection(r)){const r=i.$getNodeByKey(n);t.$isDecoratorBlockNode(r)&&r.setFormat(e)}else if(i.$isRangeSelection(r)){const i=r.getNodes();for(const r of i)if(t.$isDecoratorBlockNode(r))r.setFormat(e);else{o.$getNearestBlockElementAncestorOrThrow(r).setFormat(e)}}return!0}return!1}),i.COMMAND_PRIORITY_LOW),f.registerCommand(i.CLICK_COMMAND,(e=>e.target===m.current&&(e.preventDefault(),e.shiftKey||d(),N(!x),!0)),i.COMMAND_PRIORITY_LOW))),[d,f,x,n,N]),c.jsx("div",{className:[u.base,x?u.focus:null].filter(Boolean).join(" "),ref:m,style:{textAlign:a||void 0},children:s})};
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{useLexicalComposerContext as e}from"@lexical/react/LexicalComposerContext";import{$isDecoratorBlockNode as t}from"@lexical/react/LexicalDecoratorBlockNode";import{useLexicalNodeSelection as r}from"@lexical/react/useLexicalNodeSelection";import{mergeRegister as o,$getNearestBlockElementAncestorOrThrow as i}from"@lexical/utils";import{FORMAT_ELEMENT_COMMAND as l,$getSelection as a,$isNodeSelection as c,$getNodeByKey as m,$isRangeSelection as n,COMMAND_PRIORITY_LOW as s,CLICK_COMMAND as f}from"lexical";import{useRef as u,useEffect as x}from"react";import{jsx as d}from"react/jsx-runtime";function p({children:p,format:g,nodeKey:N,className:C}){const[h]=e(),[v,y,F]=r(N),L=u(null);return x((()=>o(h.registerCommand(l,(e=>{if(v){const r=a();if(c(r)){const r=m(N);t(r)&&r.setFormat(e)}else if(n(r)){const o=r.getNodes();for(const r of o)if(t(r))r.setFormat(e);else{i(r).setFormat(e)}}return!0}return!1}),s),h.registerCommand(f,(e=>e.target===L.current&&(e.preventDefault(),e.shiftKey||F(),y(!v),!0)),s))),[F,h,v,N,y]),d("div",{className:[C.base,v?C.focus:null].filter(Boolean).join(" "),ref:L,style:{textAlign:g||void 0},children:p})}export{p as BlockWithAlignableContents};
+15
View File
@@ -0,0 +1,15 @@
/**
* 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.
*
*/
import type { JSX } from 'react';
export declare function CharacterLimitPlugin({ charset, maxLength, renderer, }: {
charset: 'UTF-8' | 'UTF-16';
maxLength: number;
renderer?: ({ remainingCharacters, }: {
remainingCharacters: number;
}) => JSX.Element;
}): JSX.Element;
+292
View File
@@ -0,0 +1,292 @@
/**
* 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.
*
*/
'use strict';
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var react = require('react');
var overflow = require('@lexical/overflow');
var text = require('@lexical/text');
var utils = require('@lexical/utils');
var lexical = require('lexical');
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.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
function useCharacterLimit(editor, maxCharacters, optional = Object.freeze({})) {
const {
strlen = input => input.length,
// UTF-16
remainingCharacters = () => {
return;
}
} = optional;
react.useEffect(() => {
if (!editor.hasNodes([overflow.OverflowNode])) {
{
formatDevErrorMessage(`useCharacterLimit: OverflowNode not registered on editor`);
}
}
}, [editor]);
react.useEffect(() => {
let text$1 = editor.getEditorState().read(text.$rootTextContent);
let lastComputedTextLength = 0;
return utils.mergeRegister(editor.registerTextContentListener(currentText => {
text$1 = currentText;
}), editor.registerUpdateListener(({
dirtyLeaves,
dirtyElements
}) => {
const isComposing = editor.isComposing();
const hasContentChanges = dirtyLeaves.size > 0 || dirtyElements.size > 0;
if (isComposing || !hasContentChanges) {
return;
}
const textLength = strlen(text$1);
const textLengthAboveThreshold = textLength > maxCharacters || lastComputedTextLength !== null && lastComputedTextLength > maxCharacters;
const diff = maxCharacters - textLength;
remainingCharacters(diff);
if (lastComputedTextLength === null || textLengthAboveThreshold) {
const offset = findOffset(text$1, maxCharacters, strlen);
editor.update(() => {
$wrapOverflowedNodes(offset);
}, {
tag: lexical.HISTORY_MERGE_TAG
});
}
lastComputedTextLength = textLength;
}), editor.registerCommand(lexical.DELETE_CHARACTER_COMMAND, isBackward => {
const selection = lexical.$getSelection();
if (!lexical.$isRangeSelection(selection)) {
return false;
}
const anchorNode = selection.anchor.getNode();
const overflow = anchorNode.getParent();
const overflowParent = overflow ? overflow.getParent() : null;
const parentNext = overflowParent ? overflowParent.getNextSibling() : null;
selection.deleteCharacter(isBackward);
if (overflowParent && overflowParent.isEmpty()) {
overflowParent.remove();
} else if (lexical.$isElementNode(parentNext) && parentNext.isEmpty()) {
parentNext.remove();
}
return true;
}, lexical.COMMAND_PRIORITY_LOW));
}, [editor, maxCharacters, remainingCharacters, strlen]);
}
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 = utils.$dfs();
const dfsNodesLength = dfsNodes.length;
let accumulatedLength = 0;
for (let i = 0; i < dfsNodesLength; i += 1) {
const {
node
} = dfsNodes[i];
const needsOverflowParent = lexical.$isLeafNode(node) && !utils.$findMatchingParent(node, overflow.$isOverflowNode);
if (overflow.$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();
utils.$unwrapNode(node);
const selection = lexical.$getSelection();
// Restore selection when the overflow children are removed
if (lexical.$isRangeSelection(selection) && (!selection.anchor.getNode().isAttached() || !selection.focus.getNode().isAttached())) {
if (lexical.$isTextNode(previousSibling)) {
previousSibling.select();
} else if (lexical.$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;
// For simple text we can redimension the overflow into a smaller and more accurate
// container
const firstDescendantIsSimpleText = lexical.$isTextNode(descendant) && descendant.isSimpleText();
const firstDescendantDoesNotOverflow = previousPlusDescendantLength <= offset;
if (firstDescendantIsSimpleText || firstDescendantDoesNotOverflow) {
utils.$unwrapNode(node);
}
}
} else if (needsOverflowParent) {
const previousAccumulatedLength = accumulatedLength;
accumulatedLength += node.getTextContentSize();
if (accumulatedLength > offset && !overflow.$isOverflowNode(node.getParent())) {
const previousSelection = lexical.$getSelection();
let overflowNode;
// For simple text we can improve the limit accuracy by splitting the TextNode
// on the split point
if (previousAccumulatedLength < offset && lexical.$isTextNode(node) && node.isSimpleText()) {
const [, overflowedText] = node.splitText(offset - previousAccumulatedLength);
overflowNode = $wrapNode(overflowedText);
} else {
overflowNode = $wrapNode(node);
}
if (previousSelection !== null) {
lexical.$setSelection(previousSelection);
}
$mergePrevious(overflowNode);
}
}
}
}
function $wrapNode(node) {
const overflowNode = overflow.$createOverflowNode();
node.replace(overflowNode);
overflowNode.append(node);
return overflowNode;
}
function $mergePrevious(overflowNode) {
const previousNode = overflowNode.getPreviousSibling();
if (!overflow.$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 = lexical.$getSelection();
if (lexical.$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();
}
/**
* 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 CHARACTER_LIMIT = 5;
let textEncoderInstance = null;
function textEncoder() {
if (window.TextEncoder === undefined) {
return null;
}
if (textEncoderInstance === null) {
textEncoderInstance = new window.TextEncoder();
}
return textEncoderInstance;
}
function utf8Length(text) {
const currentTextEncoder = textEncoder();
if (currentTextEncoder === null) {
// http://stackoverflow.com/a/5515960/210370
const m = encodeURIComponent(text).match(/%[89ABab]/g);
return text.length + (m ? m.length : 0);
}
return currentTextEncoder.encode(text).length;
}
function DefaultRenderer({
remainingCharacters
}) {
return /*#__PURE__*/jsxRuntime.jsx("span", {
className: `characters-limit ${remainingCharacters < 0 ? 'characters-limit-exceeded' : ''}`,
children: remainingCharacters
});
}
function CharacterLimitPlugin({
charset = 'UTF-16',
maxLength = CHARACTER_LIMIT,
renderer = DefaultRenderer
}) {
const [editor] = LexicalComposerContext.useLexicalComposerContext();
const [remainingCharacters, setRemainingCharacters] = react.useState(maxLength);
const characterLimitProps = react.useMemo(() => ({
remainingCharacters: setRemainingCharacters,
strlen: text => {
if (charset === 'UTF-8') {
return utf8Length(text);
} else if (charset === 'UTF-16') {
return text.length;
} else {
throw new Error('Unrecognized charset');
}
}
}), [charset]);
useCharacterLimit(editor, maxLength, characterLimitProps);
return renderer({
remainingCharacters
});
}
exports.CharacterLimitPlugin = CharacterLimitPlugin;
+290
View File
@@ -0,0 +1,290 @@
/**
* 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.
*
*/
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect, useState, useMemo } from 'react';
import { OverflowNode, $isOverflowNode, $createOverflowNode } from '@lexical/overflow';
import { $rootTextContent } from '@lexical/text';
import { mergeRegister, $dfs, $findMatchingParent, $unwrapNode } from '@lexical/utils';
import { HISTORY_MERGE_TAG, DELETE_CHARACTER_COMMAND, $getSelection, $isRangeSelection, $isElementNode, COMMAND_PRIORITY_LOW, $isLeafNode, $isTextNode, $setSelection } from 'lexical';
import { jsx } from '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.
*
*/
// Do not require this module directly! Use normal `invariant` calls.
function formatDevErrorMessage(message) {
throw new Error(message);
}
function useCharacterLimit(editor, maxCharacters, optional = Object.freeze({})) {
const {
strlen = input => input.length,
// UTF-16
remainingCharacters = () => {
return;
}
} = optional;
useEffect(() => {
if (!editor.hasNodes([OverflowNode])) {
{
formatDevErrorMessage(`useCharacterLimit: OverflowNode not registered on editor`);
}
}
}, [editor]);
useEffect(() => {
let text = editor.getEditorState().read($rootTextContent);
let lastComputedTextLength = 0;
return mergeRegister(editor.registerTextContentListener(currentText => {
text = currentText;
}), editor.registerUpdateListener(({
dirtyLeaves,
dirtyElements
}) => {
const isComposing = editor.isComposing();
const hasContentChanges = dirtyLeaves.size > 0 || dirtyElements.size > 0;
if (isComposing || !hasContentChanges) {
return;
}
const textLength = strlen(text);
const textLengthAboveThreshold = textLength > maxCharacters || lastComputedTextLength !== null && lastComputedTextLength > maxCharacters;
const diff = maxCharacters - textLength;
remainingCharacters(diff);
if (lastComputedTextLength === null || textLengthAboveThreshold) {
const offset = findOffset(text, maxCharacters, strlen);
editor.update(() => {
$wrapOverflowedNodes(offset);
}, {
tag: HISTORY_MERGE_TAG
});
}
lastComputedTextLength = textLength;
}), editor.registerCommand(DELETE_CHARACTER_COMMAND, isBackward => {
const selection = $getSelection();
if (!$isRangeSelection(selection)) {
return false;
}
const anchorNode = selection.anchor.getNode();
const overflow = anchorNode.getParent();
const overflowParent = overflow ? overflow.getParent() : null;
const parentNext = overflowParent ? overflowParent.getNextSibling() : null;
selection.deleteCharacter(isBackward);
if (overflowParent && overflowParent.isEmpty()) {
overflowParent.remove();
} else if ($isElementNode(parentNext) && parentNext.isEmpty()) {
parentNext.remove();
}
return true;
}, COMMAND_PRIORITY_LOW));
}, [editor, maxCharacters, remainingCharacters, strlen]);
}
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];
const needsOverflowParent = $isLeafNode(node) && !$findMatchingParent(node, $isOverflowNode);
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();
// Restore selection when the overflow children are removed
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;
// For simple text we can redimension the overflow into a smaller and more accurate
// container
const firstDescendantIsSimpleText = $isTextNode(descendant) && descendant.isSimpleText();
const firstDescendantDoesNotOverflow = previousPlusDescendantLength <= offset;
if (firstDescendantIsSimpleText || firstDescendantDoesNotOverflow) {
$unwrapNode(node);
}
}
} else if (needsOverflowParent) {
const previousAccumulatedLength = accumulatedLength;
accumulatedLength += node.getTextContentSize();
if (accumulatedLength > offset && !$isOverflowNode(node.getParent())) {
const previousSelection = $getSelection();
let overflowNode;
// For simple text we can improve the limit accuracy by splitting the TextNode
// on the split point
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.replace(overflowNode);
overflowNode.append(node);
return overflowNode;
}
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();
}
/**
* 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 CHARACTER_LIMIT = 5;
let textEncoderInstance = null;
function textEncoder() {
if (window.TextEncoder === undefined) {
return null;
}
if (textEncoderInstance === null) {
textEncoderInstance = new window.TextEncoder();
}
return textEncoderInstance;
}
function utf8Length(text) {
const currentTextEncoder = textEncoder();
if (currentTextEncoder === null) {
// http://stackoverflow.com/a/5515960/210370
const m = encodeURIComponent(text).match(/%[89ABab]/g);
return text.length + (m ? m.length : 0);
}
return currentTextEncoder.encode(text).length;
}
function DefaultRenderer({
remainingCharacters
}) {
return /*#__PURE__*/jsx("span", {
className: `characters-limit ${remainingCharacters < 0 ? 'characters-limit-exceeded' : ''}`,
children: remainingCharacters
});
}
function CharacterLimitPlugin({
charset = 'UTF-16',
maxLength = CHARACTER_LIMIT,
renderer = DefaultRenderer
}) {
const [editor] = useLexicalComposerContext();
const [remainingCharacters, setRemainingCharacters] = useState(maxLength);
const characterLimitProps = useMemo(() => ({
remainingCharacters: setRemainingCharacters,
strlen: text => {
if (charset === 'UTF-8') {
return utf8Length(text);
} else if (charset === 'UTF-16') {
return text.length;
} else {
throw new Error('Unrecognized charset');
}
}
}), [charset]);
useCharacterLimit(editor, maxLength, characterLimitProps);
return renderer({
remainingCharacters
});
}
export { CharacterLimitPlugin };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalCharacterLimitPlugin = process.env.NODE_ENV !== 'production' ? require('./LexicalCharacterLimitPlugin.dev.js') : require('./LexicalCharacterLimitPlugin.prod.js');
module.exports = LexicalCharacterLimitPlugin;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
* @flow strict
*/
declare export function CharacterLimitPlugin(props: {
charset: 'UTF-8' | 'UTF-16',
}): React.Node;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
*/
import * as modDev from './LexicalCharacterLimitPlugin.dev.mjs';
import * as modProd from './LexicalCharacterLimitPlugin.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const CharacterLimitPlugin = mod.CharacterLimitPlugin;
+10
View File
@@ -0,0 +1,10 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalCharacterLimitPlugin.dev.mjs') : import('./LexicalCharacterLimitPlugin.prod.mjs'));
export const CharacterLimitPlugin = mod.CharacterLimitPlugin;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var e=require("@lexical/react/LexicalComposerContext"),t=require("react"),n=require("@lexical/overflow"),r=require("@lexical/text"),i=require("@lexical/utils"),o=require("lexical"),s=require("react/jsx-runtime");function l(e,s,l=Object.freeze({})){const{strlen:f=(e=>e.length),remainingCharacters:g=(()=>{})}=l;t.useEffect((()=>{e.hasNodes([n.OverflowNode])||function(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(57)}),[e]),t.useEffect((()=>{let t=e.getEditorState().read(r.$rootTextContent),l=0;return i.mergeRegister(e.registerTextContentListener((e=>{t=e})),e.registerUpdateListener((({dirtyLeaves:r,dirtyElements:u})=>{const d=e.isComposing(),h=r.size>0||u.size>0;if(d||!h)return;const m=f(t),x=m>s||null!==l&&l>s;if(g(s-m),null===l||x){const r=function(e,t,n){const r=Intl.Segmenter;let i=0,o=0;if("function"==typeof r){const s=(new r).segment(e);for(const{segment:e}of s){const r=o+n(e);if(r>t)break;o=r,i+=e.length}}else{const r=Array.from(e),s=r.length;for(let e=0;e<s;e++){const s=r[e],l=o+n(s);if(l>t)break;o=l,i+=s.length}}return i}(t,s,f);e.update((()=>{!function(e){const t=i.$dfs(),r=t.length;let s=0;for(let l=0;l<r;l+=1){const{node:r}=t[l],f=o.$isLeafNode(r)&&!i.$findMatchingParent(r,n.$isOverflowNode);if(n.$isOverflowNode(r)){const t=s;if(s+r.getTextContentSize()<=e){const e=r.getParent(),t=r.getPreviousSibling(),n=r.getNextSibling();i.$unwrapNode(r);const s=o.$getSelection();!o.$isRangeSelection(s)||s.anchor.getNode().isAttached()&&s.focus.getNode().isAttached()||(o.$isTextNode(t)?t.select():o.$isTextNode(n)?n.select():null!==e&&e.select())}else if(t<e){const n=r.getFirstDescendant(),s=t+(null!==n?n.getTextContentSize():0);(o.$isTextNode(n)&&n.isSimpleText()||s<=e)&&i.$unwrapNode(r)}}else if(f){const t=s;if(s+=r.getTextContentSize(),s>e&&!n.$isOverflowNode(r.getParent())){const n=o.$getSelection();let i;if(t<e&&o.$isTextNode(r)&&r.isSimpleText()){const[,n]=r.splitText(e-t);i=c(n)}else i=c(r);null!==n&&o.$setSelection(n),a(i)}}}}(r)}),{tag:o.HISTORY_MERGE_TAG})}l=m})),e.registerCommand(o.DELETE_CHARACTER_COMMAND,(e=>{const t=o.$getSelection();if(!o.$isRangeSelection(t))return!1;const n=t.anchor.getNode().getParent(),r=n?n.getParent():null,i=r?r.getNextSibling():null;return t.deleteCharacter(e),r&&r.isEmpty()?r.remove():o.$isElementNode(i)&&i.isEmpty()&&i.remove(),!0}),o.COMMAND_PRIORITY_LOW))}),[e,s,g,f])}function c(e){const t=n.$createOverflowNode();return e.replace(t),t.append(e),t}function a(e){const t=e.getPreviousSibling();if(!n.$isOverflowNode(t))return;const r=e.getFirstChild(),i=t.getChildren(),s=i.length;if(null===r)e.append(...i);else for(let e=0;e<s;e++)r.insertBefore(i[e]);const l=o.$getSelection();if(o.$isRangeSelection(l)){const n=l.anchor,r=n.getNode(),i=l.focus,o=n.getNode();r.is(t)?n.set(e.getKey(),n.offset,"element"):r.is(e)&&n.set(e.getKey(),s+n.offset,"element"),o.is(t)?i.set(e.getKey(),i.offset,"element"):o.is(e)&&i.set(e.getKey(),s+i.offset,"element")}t.remove()}let f=null;function g(e){const t=void 0===window.TextEncoder?null:(null===f&&(f=new window.TextEncoder),f);if(null===t){const t=encodeURIComponent(e).match(/%[89ABab]/g);return e.length+(t?t.length:0)}return t.encode(e).length}function u({remainingCharacters:e}){return s.jsx("span",{className:"characters-limit "+(e<0?"characters-limit-exceeded":""),children:e})}exports.CharacterLimitPlugin=function({charset:n="UTF-16",maxLength:r=5,renderer:i=u}){const[o]=e.useLexicalComposerContext(),[s,c]=t.useState(r),a=t.useMemo((()=>({remainingCharacters:c,strlen:e=>{if("UTF-8"===n)return g(e);if("UTF-16"===n)return e.length;throw new Error("Unrecognized charset")}})),[n]);return l(o,r,a),i({remainingCharacters:s})};
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{useLexicalComposerContext as e}from"@lexical/react/LexicalComposerContext";import{useEffect as t,useState as n,useMemo as r}from"react";import{OverflowNode as o,$isOverflowNode as i,$createOverflowNode as s}from"@lexical/overflow";import{$rootTextContent as l}from"@lexical/text";import{mergeRegister as c,$dfs as a,$findMatchingParent as f,$unwrapNode as g}from"@lexical/utils";import{HISTORY_MERGE_TAG as m,DELETE_CHARACTER_COMMAND as u,$getSelection as d,$isRangeSelection as h,$isElementNode as p,COMMAND_PRIORITY_LOW as x,$isLeafNode as C,$isTextNode as S,$setSelection as v}from"lexical";import{jsx as w}from"react/jsx-runtime";function T(e,n,r=Object.freeze({})){const{strlen:s=(e=>e.length),remainingCharacters:w=(()=>{})}=r;t((()=>{e.hasNodes([o])||function(e,...t){const n=new URL("https://lexical.dev/docs/error"),r=new URLSearchParams;r.append("code",e);for(const e of t)r.append("v",e);throw n.search=r.toString(),Error(`Minified Lexical error #${e}; visit ${n.toString()} for the full message or use the non-minified dev environment for full errors and additional helpful warnings.`)}(57)}),[e]),t((()=>{let t=e.getEditorState().read(l),r=0;return c(e.registerTextContentListener((e=>{t=e})),e.registerUpdateListener((({dirtyLeaves:o,dirtyElements:l})=>{const c=e.isComposing(),u=o.size>0||l.size>0;if(c||!u)return;const p=s(t),x=p>n||null!==r&&r>n;if(w(n-p),null===r||x){const r=function(e,t,n){const r=Intl.Segmenter;let o=0,i=0;if("function"==typeof r){const s=(new r).segment(e);for(const{segment:e}of s){const r=i+n(e);if(r>t)break;i=r,o+=e.length}}else{const r=Array.from(e),s=r.length;for(let e=0;e<s;e++){const s=r[e],l=i+n(s);if(l>t)break;i=l,o+=s.length}}return o}(t,n,s);e.update((()=>{!function(e){const t=a(),n=t.length;let r=0;for(let o=0;o<n;o+=1){const{node:n}=t[o],s=C(n)&&!f(n,i);if(i(n)){const t=r;if(r+n.getTextContentSize()<=e){const e=n.getParent(),t=n.getPreviousSibling(),r=n.getNextSibling();g(n);const o=d();!h(o)||o.anchor.getNode().isAttached()&&o.focus.getNode().isAttached()||(S(t)?t.select():S(r)?r.select():null!==e&&e.select())}else if(t<e){const r=n.getFirstDescendant(),o=t+(null!==r?r.getTextContentSize():0);(S(r)&&r.isSimpleText()||o<=e)&&g(n)}}else if(s){const t=r;if(r+=n.getTextContentSize(),r>e&&!i(n.getParent())){const r=d();let o;if(t<e&&S(n)&&n.isSimpleText()){const[,r]=n.splitText(e-t);o=y(r)}else o=y(n);null!==r&&v(r),N(o)}}}}(r)}),{tag:m})}r=p})),e.registerCommand(u,(e=>{const t=d();if(!h(t))return!1;const n=t.anchor.getNode().getParent(),r=n?n.getParent():null,o=r?r.getNextSibling():null;return t.deleteCharacter(e),r&&r.isEmpty()?r.remove():p(o)&&o.isEmpty()&&o.remove(),!0}),x))}),[e,n,w,s])}function y(e){const t=s();return e.replace(t),t.append(e),t}function N(e){const t=e.getPreviousSibling();if(!i(t))return;const n=e.getFirstChild(),r=t.getChildren(),o=r.length;if(null===n)e.append(...r);else for(let e=0;e<o;e++)n.insertBefore(r[e]);const s=d();if(h(s)){const n=s.anchor,r=n.getNode(),i=s.focus,l=n.getNode();r.is(t)?n.set(e.getKey(),n.offset,"element"):r.is(e)&&n.set(e.getKey(),o+n.offset,"element"),l.is(t)?i.set(e.getKey(),i.offset,"element"):l.is(e)&&i.set(e.getKey(),o+i.offset,"element")}t.remove()}let b=null;function E(e){const t=void 0===window.TextEncoder?null:(null===b&&(b=new window.TextEncoder),b);if(null===t){const t=encodeURIComponent(e).match(/%[89ABab]/g);return e.length+(t?t.length:0)}return t.encode(e).length}function L({remainingCharacters:e}){return w("span",{className:"characters-limit "+(e<0?"characters-limit-exceeded":""),children:e})}function U({charset:t="UTF-16",maxLength:o=5,renderer:i=L}){const[s]=e(),[l,c]=n(o);return T(s,o,r((()=>({remainingCharacters:c,strlen:e=>{if("UTF-8"===t)return E(e);if("UTF-16"===t)return e.length;throw new Error("Unrecognized charset")}})),[t])),i({remainingCharacters:l})}export{U as CharacterLimitPlugin};
+8
View File
@@ -0,0 +1,8 @@
/**
* 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.
*
*/
export declare function CheckListPlugin(): null;
+31
View File
@@ -0,0 +1,31 @@
/**
* 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.
*
*/
'use strict';
var list = require('@lexical/list');
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var react = require('react');
/**
* 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.
*
*/
function CheckListPlugin() {
const [editor] = LexicalComposerContext.useLexicalComposerContext();
react.useEffect(() => {
return list.registerCheckList(editor);
}, [editor]);
return null;
}
exports.CheckListPlugin = CheckListPlugin;
+29
View File
@@ -0,0 +1,29 @@
/**
* 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.
*
*/
import { registerCheckList } from '@lexical/list';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { useEffect } from 'react';
/**
* 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.
*
*/
function CheckListPlugin() {
const [editor] = useLexicalComposerContext();
useEffect(() => {
return registerCheckList(editor);
}, [editor]);
return null;
}
export { CheckListPlugin };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalCheckListPlugin = process.env.NODE_ENV !== 'production' ? require('./LexicalCheckListPlugin.dev.js') : require('./LexicalCheckListPlugin.prod.js');
module.exports = LexicalCheckListPlugin;
+10
View File
@@ -0,0 +1,10 @@
/**
* 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.
*
* @flow strict
*/
declare export function CheckListPlugin(): null;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
*/
import * as modDev from './LexicalCheckListPlugin.dev.mjs';
import * as modProd from './LexicalCheckListPlugin.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const CheckListPlugin = mod.CheckListPlugin;
+10
View File
@@ -0,0 +1,10 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalCheckListPlugin.dev.mjs') : import('./LexicalCheckListPlugin.prod.mjs'));
export const CheckListPlugin = mod.CheckListPlugin;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var e=require("@lexical/list"),r=require("@lexical/react/LexicalComposerContext"),t=require("react");exports.CheckListPlugin=function(){const[i]=r.useLexicalComposerContext();return t.useEffect((()=>e.registerCheckList(i)),[i]),null};
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{registerCheckList as o}from"@lexical/list";import{useLexicalComposerContext as r}from"@lexical/react/LexicalComposerContext";import{useEffect as t}from"react";function e(){const[e]=r();return t((()=>o(e)),[e]),null}export{e as CheckListPlugin};
+13
View File
@@ -0,0 +1,13 @@
/**
* 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.
*
*/
import type { JSX } from 'react';
type Props = Readonly<{
onClear?: () => void;
}>;
export declare function ClearEditorPlugin({ onClear }: Props): JSX.Element | null;
export {};
+76
View File
@@ -0,0 +1,76 @@
/**
* 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.
*
*/
'use strict';
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var lexical = require('lexical');
var react = require('react');
/**
* 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 CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
/**
* 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.
*
*/
// This workaround is no longer necessary in React 19,
// but we currently support React >=17.x
// https://github.com/facebook/react/pull/26395
const useLayoutEffectImpl = CAN_USE_DOM ? react.useLayoutEffect : react.useEffect;
/**
* 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.
*
*/
function ClearEditorPlugin({
onClear
}) {
const [editor] = LexicalComposerContext.useLexicalComposerContext();
useLayoutEffectImpl(() => {
return editor.registerCommand(lexical.CLEAR_EDITOR_COMMAND, payload => {
editor.update(() => {
if (onClear == null) {
const root = lexical.$getRoot();
const selection = lexical.$getSelection();
const paragraph = lexical.$createParagraphNode();
root.clear();
root.append(paragraph);
if (selection !== null) {
paragraph.select();
}
if (lexical.$isRangeSelection(selection)) {
selection.format = 0;
}
} else {
onClear();
}
});
return true;
}, lexical.COMMAND_PRIORITY_EDITOR);
}, [editor, onClear]);
return null;
}
exports.ClearEditorPlugin = ClearEditorPlugin;
+74
View File
@@ -0,0 +1,74 @@
/**
* 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.
*
*/
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { CLEAR_EDITOR_COMMAND, $getRoot, $getSelection, $createParagraphNode, $isRangeSelection, COMMAND_PRIORITY_EDITOR } from 'lexical';
import { useLayoutEffect, useEffect } from 'react';
/**
* 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 CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
/**
* 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.
*
*/
// This workaround is no longer necessary in React 19,
// but we currently support React >=17.x
// https://github.com/facebook/react/pull/26395
const useLayoutEffectImpl = CAN_USE_DOM ? useLayoutEffect : useEffect;
/**
* 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.
*
*/
function ClearEditorPlugin({
onClear
}) {
const [editor] = useLexicalComposerContext();
useLayoutEffectImpl(() => {
return editor.registerCommand(CLEAR_EDITOR_COMMAND, payload => {
editor.update(() => {
if (onClear == null) {
const root = $getRoot();
const selection = $getSelection();
const paragraph = $createParagraphNode();
root.clear();
root.append(paragraph);
if (selection !== null) {
paragraph.select();
}
if ($isRangeSelection(selection)) {
selection.format = 0;
}
} else {
onClear();
}
});
return true;
}, COMMAND_PRIORITY_EDITOR);
}, [editor, onClear]);
return null;
}
export { ClearEditorPlugin };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalClearEditorPlugin = process.env.NODE_ENV !== 'production' ? require('./LexicalClearEditorPlugin.dev.js') : require('./LexicalClearEditorPlugin.prod.js');
module.exports = LexicalClearEditorPlugin;
+14
View File
@@ -0,0 +1,14 @@
/**
* 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.
*
* @flow strict
*/
type Props = $ReadOnly<{
onClear?: () => void,
}>;
declare export function ClearEditorPlugin(Props): React.Node;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
*/
import * as modDev from './LexicalClearEditorPlugin.dev.mjs';
import * as modProd from './LexicalClearEditorPlugin.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const ClearEditorPlugin = mod.ClearEditorPlugin;
+10
View File
@@ -0,0 +1,10 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalClearEditorPlugin.dev.mjs') : import('./LexicalClearEditorPlugin.prod.mjs'));
export const ClearEditorPlugin = mod.ClearEditorPlugin;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var e=require("@lexical/react/LexicalComposerContext"),t=require("lexical"),o=require("react");const n="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?o.useLayoutEffect:o.useEffect;exports.ClearEditorPlugin=function({onClear:o}){const[r]=e.useLexicalComposerContext();return n((()=>r.registerCommand(t.CLEAR_EDITOR_COMMAND,(e=>(r.update((()=>{if(null==o){const e=t.$getRoot(),o=t.$getSelection(),n=t.$createParagraphNode();e.clear(),e.append(n),null!==o&&n.select(),t.$isRangeSelection(o)&&(o.format=0)}else o()})),!0)),t.COMMAND_PRIORITY_EDITOR)),[r,o]),null};
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{useLexicalComposerContext as e}from"@lexical/react/LexicalComposerContext";import{CLEAR_EDITOR_COMMAND as o,$getRoot as t,$getSelection as n,$createParagraphNode as r,$isRangeSelection as l,COMMAND_PRIORITY_EDITOR as i}from"lexical";import{useLayoutEffect as c,useEffect as m}from"react";const a="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?c:m;function d({onClear:c}){const[m]=e();return a((()=>m.registerCommand(o,(e=>(m.update((()=>{if(null==c){const e=t(),o=n(),i=r();e.clear(),e.append(i),null!==o&&i.select(),l(o)&&(o.format=0)}else c()})),!0)),i)),[m,c]),null}export{d as ClearEditorPlugin};
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
export declare function ClickableLinkPlugin({ newTab, disabled, }: {
newTab?: boolean;
disabled?: boolean;
}): null;
+103
View File
@@ -0,0 +1,103 @@
/**
* 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.
*
*/
'use strict';
var link = require('@lexical/link');
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var utils = require('@lexical/utils');
var lexical = require('lexical');
var react = require('react');
/**
* 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.
*
*/
function findMatchingDOM(startNode, predicate) {
let node = startNode;
while (node != null) {
if (predicate(node)) {
return node;
}
node = node.parentNode;
}
return null;
}
function ClickableLinkPlugin({
newTab = true,
disabled = false
}) {
const [editor] = LexicalComposerContext.useLexicalComposerContext();
react.useEffect(() => {
const onClick = event => {
const target = event.target;
if (!lexical.isDOMNode(target)) {
return;
}
const nearestEditor = lexical.getNearestEditorFromDOMNode(target);
if (nearestEditor === null) {
return;
}
let url = null;
let urlTarget = null;
nearestEditor.update(() => {
const clickedNode = lexical.$getNearestNodeFromDOMNode(target);
if (clickedNode !== null) {
const maybeLinkNode = utils.$findMatchingParent(clickedNode, lexical.$isElementNode);
if (!disabled) {
if (link.$isLinkNode(maybeLinkNode)) {
url = maybeLinkNode.sanitizeUrl(maybeLinkNode.getURL());
urlTarget = maybeLinkNode.getTarget();
} else {
const a = findMatchingDOM(target, utils.isHTMLAnchorElement);
if (a !== null) {
url = a.href;
urlTarget = a.target;
}
}
}
}
});
if (url === null || url === '') {
return;
}
// Allow user to select link text without following url
const selection = editor.getEditorState().read(lexical.$getSelection);
if (lexical.$isRangeSelection(selection) && !selection.isCollapsed()) {
event.preventDefault();
return;
}
const isMiddle = event.type === 'auxclick' && event.button === 1;
window.open(url, newTab || isMiddle || event.metaKey || event.ctrlKey || urlTarget === '_blank' ? '_blank' : '_self');
event.preventDefault();
};
const onMouseUp = event => {
if (event.button === 1) {
onClick(event);
}
};
return editor.registerRootListener((rootElement, prevRootElement) => {
if (prevRootElement !== null) {
prevRootElement.removeEventListener('click', onClick);
prevRootElement.removeEventListener('mouseup', onMouseUp);
}
if (rootElement !== null) {
rootElement.addEventListener('click', onClick);
rootElement.addEventListener('mouseup', onMouseUp);
}
});
}, [editor, newTab, disabled]);
return null;
}
exports.ClickableLinkPlugin = ClickableLinkPlugin;
+101
View File
@@ -0,0 +1,101 @@
/**
* 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.
*
*/
import { $isLinkNode } from '@lexical/link';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { $findMatchingParent, isHTMLAnchorElement } from '@lexical/utils';
import { isDOMNode, getNearestEditorFromDOMNode, $getNearestNodeFromDOMNode, $isElementNode, $getSelection, $isRangeSelection } from 'lexical';
import { useEffect } from 'react';
/**
* 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.
*
*/
function findMatchingDOM(startNode, predicate) {
let node = startNode;
while (node != null) {
if (predicate(node)) {
return node;
}
node = node.parentNode;
}
return null;
}
function ClickableLinkPlugin({
newTab = true,
disabled = false
}) {
const [editor] = useLexicalComposerContext();
useEffect(() => {
const onClick = event => {
const target = event.target;
if (!isDOMNode(target)) {
return;
}
const nearestEditor = getNearestEditorFromDOMNode(target);
if (nearestEditor === null) {
return;
}
let url = null;
let urlTarget = null;
nearestEditor.update(() => {
const clickedNode = $getNearestNodeFromDOMNode(target);
if (clickedNode !== null) {
const maybeLinkNode = $findMatchingParent(clickedNode, $isElementNode);
if (!disabled) {
if ($isLinkNode(maybeLinkNode)) {
url = maybeLinkNode.sanitizeUrl(maybeLinkNode.getURL());
urlTarget = maybeLinkNode.getTarget();
} else {
const a = findMatchingDOM(target, isHTMLAnchorElement);
if (a !== null) {
url = a.href;
urlTarget = a.target;
}
}
}
}
});
if (url === null || url === '') {
return;
}
// Allow user to select link text without following url
const selection = editor.getEditorState().read($getSelection);
if ($isRangeSelection(selection) && !selection.isCollapsed()) {
event.preventDefault();
return;
}
const isMiddle = event.type === 'auxclick' && event.button === 1;
window.open(url, newTab || isMiddle || event.metaKey || event.ctrlKey || urlTarget === '_blank' ? '_blank' : '_self');
event.preventDefault();
};
const onMouseUp = event => {
if (event.button === 1) {
onClick(event);
}
};
return editor.registerRootListener((rootElement, prevRootElement) => {
if (prevRootElement !== null) {
prevRootElement.removeEventListener('click', onClick);
prevRootElement.removeEventListener('mouseup', onMouseUp);
}
if (rootElement !== null) {
rootElement.addEventListener('click', onClick);
rootElement.addEventListener('mouseup', onMouseUp);
}
});
}, [editor, newTab, disabled]);
return null;
}
export { ClickableLinkPlugin };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalClickableLinkPlugin = process.env.NODE_ENV !== 'production' ? require('./LexicalClickableLinkPlugin.dev.js') : require('./LexicalClickableLinkPlugin.prod.js');
module.exports = LexicalClickableLinkPlugin;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
* @flow strict
*/
declare export function ClickableLinkPlugin({
newTab?: boolean,
}): null;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
*/
import * as modDev from './LexicalClickableLinkPlugin.dev.mjs';
import * as modProd from './LexicalClickableLinkPlugin.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const ClickableLinkPlugin = mod.ClickableLinkPlugin;
+10
View File
@@ -0,0 +1,10 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalClickableLinkPlugin.dev.mjs') : import('./LexicalClickableLinkPlugin.prod.mjs'));
export const ClickableLinkPlugin = mod.ClickableLinkPlugin;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var e=require("@lexical/link"),t=require("@lexical/react/LexicalComposerContext"),n=require("@lexical/utils"),r=require("lexical"),l=require("react");exports.ClickableLinkPlugin=function({newTab:i=!0,disabled:o=!1}){const[u]=t.useLexicalComposerContext();return l.useEffect((()=>{const t=t=>{const l=t.target;if(!r.isDOMNode(l))return;const s=r.getNearestEditorFromDOMNode(l);if(null===s)return;let a=null,c=null;if(s.update((()=>{const t=r.$getNearestNodeFromDOMNode(l);if(null!==t){const i=n.$findMatchingParent(t,r.$isElementNode);if(!o)if(e.$isLinkNode(i))a=i.sanitizeUrl(i.getURL()),c=i.getTarget();else{const e=function(e,t){let n=e;for(;null!=n;){if(t(n))return n;n=n.parentNode}return null}(l,n.isHTMLAnchorElement);null!==e&&(a=e.href,c=e.target)}}})),null===a||""===a)return;const d=u.getEditorState().read(r.$getSelection);if(r.$isRangeSelection(d)&&!d.isCollapsed())return void t.preventDefault();const f="auxclick"===t.type&&1===t.button;window.open(a,i||f||t.metaKey||t.ctrlKey||"_blank"===c?"_blank":"_self"),t.preventDefault()},l=e=>{1===e.button&&t(e)};return u.registerRootListener(((e,n)=>{null!==n&&(n.removeEventListener("click",t),n.removeEventListener("mouseup",l)),null!==e&&(e.addEventListener("click",t),e.addEventListener("mouseup",l))}))}),[u,i,o]),null};
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{$isLinkNode as e}from"@lexical/link";import{useLexicalComposerContext as t}from"@lexical/react/LexicalComposerContext";import{$findMatchingParent as n,isHTMLAnchorElement as r}from"@lexical/utils";import{isDOMNode as l,getNearestEditorFromDOMNode as o,$getNearestNodeFromDOMNode as i,$isElementNode as u,$getSelection as a,$isRangeSelection as c}from"lexical";import{useEffect as s}from"react";function f({newTab:f=!0,disabled:m=!1}){const[p]=t();return s((()=>{const t=t=>{const s=t.target;if(!l(s))return;const d=o(s);if(null===d)return;let v=null,x=null;if(d.update((()=>{const t=i(s);if(null!==t){const l=n(t,u);if(!m)if(e(l))v=l.sanitizeUrl(l.getURL()),x=l.getTarget();else{const e=function(e,t){let n=e;for(;null!=n;){if(t(n))return n;n=n.parentNode}return null}(s,r);null!==e&&(v=e.href,x=e.target)}}})),null===v||""===v)return;const g=p.getEditorState().read(a);if(c(g)&&!g.isCollapsed())return void t.preventDefault();const L="auxclick"===t.type&&1===t.button;window.open(v,f||L||t.metaKey||t.ctrlKey||"_blank"===x?"_blank":"_self"),t.preventDefault()},s=e=>{1===e.button&&t(e)};return p.registerRootListener(((e,n)=>{null!==n&&(n.removeEventListener("click",t),n.removeEventListener("mouseup",s)),null!==e&&(e.addEventListener("click",t),e.addEventListener("mouseup",s))}))}),[p,f,m]),null}export{f as ClickableLinkPlugin};
+18
View File
@@ -0,0 +1,18 @@
/**
* 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.
*
*/
/// <reference types="react" />
import type { Doc } from 'yjs';
export type CollaborationContextType = {
clientID: number;
color: string;
isCollabActive: boolean;
name: string;
yjsDocMap: Map<string, Doc>;
};
export declare const CollaborationContext: import("react").Context<CollaborationContextType>;
export declare function useCollaborationContext(username?: string, color?: string): CollaborationContextType;
+42
View File
@@ -0,0 +1,42 @@
/**
* 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.
*
*/
'use strict';
var react = require('react');
/**
* 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 entries = [['Cat', 'rgb(125, 50, 0)'], ['Dog', 'rgb(100, 0, 0)'], ['Rabbit', 'rgb(150, 0, 0)'], ['Frog', 'rgb(200, 0, 0)'], ['Fox', 'rgb(200, 75, 0)'], ['Hedgehog', 'rgb(0, 75, 0)'], ['Pigeon', 'rgb(0, 125, 0)'], ['Squirrel', 'rgb(75, 100, 0)'], ['Bear', 'rgb(125, 100, 0)'], ['Tiger', 'rgb(0, 0, 150)'], ['Leopard', 'rgb(0, 0, 200)'], ['Zebra', 'rgb(0, 0, 250)'], ['Wolf', 'rgb(0, 100, 150)'], ['Owl', 'rgb(0, 100, 100)'], ['Gull', 'rgb(100, 0, 100)'], ['Squid', 'rgb(150, 0, 150)']];
const randomEntry = entries[Math.floor(Math.random() * entries.length)];
const CollaborationContext = /*#__PURE__*/react.createContext({
clientID: 0,
color: randomEntry[1],
isCollabActive: false,
name: randomEntry[0],
yjsDocMap: new Map()
});
function useCollaborationContext(username, color) {
const collabContext = react.useContext(CollaborationContext);
if (username != null) {
collabContext.name = username;
}
if (color != null) {
collabContext.color = color;
}
return collabContext;
}
exports.CollaborationContext = CollaborationContext;
exports.useCollaborationContext = useCollaborationContext;
+39
View File
@@ -0,0 +1,39 @@
/**
* 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.
*
*/
import { createContext, useContext } from 'react';
/**
* 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 entries = [['Cat', 'rgb(125, 50, 0)'], ['Dog', 'rgb(100, 0, 0)'], ['Rabbit', 'rgb(150, 0, 0)'], ['Frog', 'rgb(200, 0, 0)'], ['Fox', 'rgb(200, 75, 0)'], ['Hedgehog', 'rgb(0, 75, 0)'], ['Pigeon', 'rgb(0, 125, 0)'], ['Squirrel', 'rgb(75, 100, 0)'], ['Bear', 'rgb(125, 100, 0)'], ['Tiger', 'rgb(0, 0, 150)'], ['Leopard', 'rgb(0, 0, 200)'], ['Zebra', 'rgb(0, 0, 250)'], ['Wolf', 'rgb(0, 100, 150)'], ['Owl', 'rgb(0, 100, 100)'], ['Gull', 'rgb(100, 0, 100)'], ['Squid', 'rgb(150, 0, 150)']];
const randomEntry = entries[Math.floor(Math.random() * entries.length)];
const CollaborationContext = /*#__PURE__*/createContext({
clientID: 0,
color: randomEntry[1],
isCollabActive: false,
name: randomEntry[0],
yjsDocMap: new Map()
});
function useCollaborationContext(username, color) {
const collabContext = useContext(CollaborationContext);
if (username != null) {
collabContext.name = username;
}
if (color != null) {
collabContext.color = color;
}
return collabContext;
}
export { CollaborationContext, useCollaborationContext };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalCollaborationContext = process.env.NODE_ENV !== 'production' ? require('./LexicalCollaborationContext.dev.js') : require('./LexicalCollaborationContext.prod.js');
module.exports = LexicalCollaborationContext;
+21
View File
@@ -0,0 +1,21 @@
/**
* 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.
*
* @flow strict
*/
import type {Doc} from 'yjs';
type CollaborationContextType = {
clientID: number,
color: string,
isCollabActive: boolean,
name: string,
yjsDocMap: Map<string, Doc>,
};
declare export var CollaborationContext: React.Context<CollaborationContextType>;
declare export function useCollaborationContext(): CollaborationContextType;
+13
View File
@@ -0,0 +1,13 @@
/**
* 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.
*
*/
import * as modDev from './LexicalCollaborationContext.dev.mjs';
import * as modProd from './LexicalCollaborationContext.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const CollaborationContext = mod.CollaborationContext;
export const useCollaborationContext = mod.useCollaborationContext;
+11
View File
@@ -0,0 +1,11 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalCollaborationContext.dev.mjs') : import('./LexicalCollaborationContext.prod.mjs'));
export const CollaborationContext = mod.CollaborationContext;
export const useCollaborationContext = mod.useCollaborationContext;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var r=require("react");const o=[["Cat","rgb(125, 50, 0)"],["Dog","rgb(100, 0, 0)"],["Rabbit","rgb(150, 0, 0)"],["Frog","rgb(200, 0, 0)"],["Fox","rgb(200, 75, 0)"],["Hedgehog","rgb(0, 75, 0)"],["Pigeon","rgb(0, 125, 0)"],["Squirrel","rgb(75, 100, 0)"],["Bear","rgb(125, 100, 0)"],["Tiger","rgb(0, 0, 150)"],["Leopard","rgb(0, 0, 200)"],["Zebra","rgb(0, 0, 250)"],["Wolf","rgb(0, 100, 150)"],["Owl","rgb(0, 100, 100)"],["Gull","rgb(100, 0, 100)"],["Squid","rgb(150, 0, 150)"]],e=o[Math.floor(Math.random()*o.length)],t=r.createContext({clientID:0,color:e[1],isCollabActive:!1,name:e[0],yjsDocMap:new Map});exports.CollaborationContext=t,exports.useCollaborationContext=function(o,e){const g=r.useContext(t);return null!=o&&(g.name=o),null!=e&&(g.color=e),g};
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{createContext as r,useContext as g}from"react";const o=[["Cat","rgb(125, 50, 0)"],["Dog","rgb(100, 0, 0)"],["Rabbit","rgb(150, 0, 0)"],["Frog","rgb(200, 0, 0)"],["Fox","rgb(200, 75, 0)"],["Hedgehog","rgb(0, 75, 0)"],["Pigeon","rgb(0, 125, 0)"],["Squirrel","rgb(75, 100, 0)"],["Bear","rgb(125, 100, 0)"],["Tiger","rgb(0, 0, 150)"],["Leopard","rgb(0, 0, 200)"],["Zebra","rgb(0, 0, 250)"],["Wolf","rgb(0, 100, 150)"],["Owl","rgb(0, 100, 100)"],["Gull","rgb(100, 0, 100)"],["Squid","rgb(150, 0, 150)"]],b=o[Math.floor(Math.random()*o.length)],e=r({clientID:0,color:b[1],isCollabActive:!1,name:b[0],yjsDocMap:new Map});function l(r,o){const b=g(e);return null!=r&&(b.name=r),null!=o&&(b.color=o),b}export{e as CollaborationContext,l as useCollaborationContext};
+26
View File
@@ -0,0 +1,26 @@
/**
* 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.
*
*/
import type { JSX } from 'react';
import type { Doc } from 'yjs';
import { ExcludedProperties, Provider, SyncCursorPositionsFn } from '@lexical/yjs';
import { InitialEditorStateType } from './LexicalComposer';
import { CursorsContainerRef } from './shared/useYjsCollaboration';
type Props = {
id: string;
providerFactory: (id: string, yjsDocMap: Map<string, Doc>) => Provider;
shouldBootstrap: boolean;
username?: string;
cursorColor?: string;
cursorsContainerRef?: CursorsContainerRef;
initialEditorState?: InitialEditorStateType;
excludedProperties?: ExcludedProperties;
awarenessData?: object;
syncCursorPositionsFn?: SyncCursorPositionsFn;
};
export declare function CollaborationPlugin({ id, providerFactory, shouldBootstrap, username, cursorColor, cursorsContainerRef, initialEditorState, excludedProperties, awarenessData, syncCursorPositionsFn, }: Props): JSX.Element;
export {};
+399
View File
@@ -0,0 +1,399 @@
/**
* 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.
*
*/
'use strict';
var LexicalCollaborationContext = require('@lexical/react/LexicalCollaborationContext');
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
var yjs = require('@lexical/yjs');
var React = require('react');
var utils = require('@lexical/utils');
var lexical = require('lexical');
var reactDom = require('react-dom');
var yjs$1 = require('yjs');
var jsxRuntime = require('react/jsx-runtime');
function _interopNamespaceDefault(e) {
var n = Object.create(null);
if (e) {
for (var k in e) {
n[k] = e[k];
}
}
n.default = e;
return n;
}
var React__namespace = /*#__PURE__*/_interopNamespaceDefault(React);
/**
* 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.
*
*/
function useYjsCollaboration(editor, id, provider, docMap, name, color, shouldBootstrap, binding, setDoc, cursorsContainerRef, initialEditorState, awarenessData, syncCursorPositionsFn = yjs.syncCursorPositions) {
const isReloadingDoc = React.useRef(false);
const connect = React.useCallback(() => provider.connect(), [provider]);
const disconnect = React.useCallback(() => {
try {
provider.disconnect();
} catch (e) {
// Do nothing
}
}, [provider]);
React.useEffect(() => {
const {
root
} = binding;
const {
awareness
} = provider;
const onStatus = ({
status
}) => {
editor.dispatchCommand(yjs.CONNECTED_COMMAND, status === 'connected');
};
const onSync = isSynced => {
if (shouldBootstrap && isSynced && root.isEmpty() && root._xmlText._length === 0 && isReloadingDoc.current === false) {
initializeEditor(editor, initialEditorState);
}
isReloadingDoc.current = false;
};
const onAwarenessUpdate = () => {
syncCursorPositionsFn(binding, provider);
};
const onYjsTreeChanges = (events, transaction) => {
const origin = transaction.origin;
if (origin !== binding) {
const isFromUndoManger = origin instanceof yjs$1.UndoManager;
yjs.syncYjsChangesToLexical(binding, provider, events, isFromUndoManger, syncCursorPositionsFn);
}
};
yjs.initLocalState(provider, name, color, document.activeElement === editor.getRootElement(), awarenessData || {});
const onProviderDocReload = ydoc => {
clearEditorSkipCollab(editor, binding);
setDoc(ydoc);
docMap.set(id, ydoc);
isReloadingDoc.current = true;
};
provider.on('reload', onProviderDocReload);
provider.on('status', onStatus);
provider.on('sync', onSync);
awareness.on('update', onAwarenessUpdate);
// This updates the local editor state when we receive updates from other clients
root.getSharedType().observeDeep(onYjsTreeChanges);
const removeListener = editor.registerUpdateListener(({
prevEditorState,
editorState,
dirtyLeaves,
dirtyElements,
normalizedNodes,
tags
}) => {
if (tags.has(lexical.SKIP_COLLAB_TAG) === false) {
yjs.syncLexicalUpdateToYjs(binding, provider, prevEditorState, editorState, dirtyElements, dirtyLeaves, normalizedNodes, tags);
}
});
const connectionPromise = connect();
return () => {
if (isReloadingDoc.current === false) {
if (connectionPromise) {
connectionPromise.then(disconnect);
} else {
// Workaround for race condition in StrictMode. It's possible there
// is a different race for the above case where connect returns a
// promise, but we don't have an example of that in-repo.
// It's possible that there is a similar issue with
// TOGGLE_CONNECT_COMMAND below when the provider connect returns a
// promise.
// https://github.com/facebook/lexical/issues/6640
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();
};
}, [binding, color, connect, disconnect, docMap, editor, id, initialEditorState, name, provider, shouldBootstrap, awarenessData, setDoc, syncCursorPositionsFn]);
const cursorsContainer = React.useMemo(() => {
const ref = element => {
binding.cursorsContainer = element;
};
return /*#__PURE__*/reactDom.createPortal(/*#__PURE__*/jsxRuntime.jsx("div", {
ref: ref
}), cursorsContainerRef && cursorsContainerRef.current || document.body);
}, [binding, cursorsContainerRef]);
React.useEffect(() => {
return editor.registerCommand(yjs.TOGGLE_CONNECT_COMMAND, payload => {
const shouldConnect = payload;
if (shouldConnect) {
// eslint-disable-next-line no-console
console.log('Collaboration connected!');
connect();
} else {
// eslint-disable-next-line no-console
console.log('Collaboration disconnected!');
disconnect();
}
return true;
}, lexical.COMMAND_PRIORITY_EDITOR);
}, [connect, disconnect, editor]);
return cursorsContainer;
}
function useYjsFocusTracking(editor, provider, name, color, awarenessData) {
React.useEffect(() => {
return utils.mergeRegister(editor.registerCommand(lexical.FOCUS_COMMAND, () => {
yjs.setLocalStateFocus(provider, name, color, true, awarenessData || {});
return false;
}, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.BLUR_COMMAND, () => {
yjs.setLocalStateFocus(provider, name, color, false, awarenessData || {});
return false;
}, lexical.COMMAND_PRIORITY_EDITOR));
}, [color, editor, name, provider, awarenessData]);
}
function useYjsHistory(editor, binding) {
const undoManager = React.useMemo(() => yjs.createUndoManager(binding, binding.root.getSharedType()), [binding]);
React.useEffect(() => {
const undo = () => {
undoManager.undo();
};
const redo = () => {
undoManager.redo();
};
return utils.mergeRegister(editor.registerCommand(lexical.UNDO_COMMAND, () => {
undo();
return true;
}, lexical.COMMAND_PRIORITY_EDITOR), editor.registerCommand(lexical.REDO_COMMAND, () => {
redo();
return true;
}, lexical.COMMAND_PRIORITY_EDITOR));
});
const clearHistory = React.useCallback(() => {
undoManager.clear();
}, [undoManager]);
// Exposing undo and redo states
React__namespace.useEffect(() => {
const updateUndoRedoStates = () => {
editor.dispatchCommand(lexical.CAN_UNDO_COMMAND, undoManager.undoStack.length > 0);
editor.dispatchCommand(lexical.CAN_REDO_COMMAND, undoManager.redoStack.length > 0);
};
undoManager.on('stack-item-added', updateUndoRedoStates);
undoManager.on('stack-item-popped', updateUndoRedoStates);
undoManager.on('stack-cleared', updateUndoRedoStates);
return () => {
undoManager.off('stack-item-added', updateUndoRedoStates);
undoManager.off('stack-item-popped', updateUndoRedoStates);
undoManager.off('stack-cleared', updateUndoRedoStates);
};
}, [editor, undoManager]);
return clearHistory;
}
function initializeEditor(editor, initialEditorState) {
editor.update(() => {
const root = lexical.$getRoot();
if (root.isEmpty()) {
if (initialEditorState) {
switch (typeof initialEditorState) {
case 'string':
{
const parsedEditorState = editor.parseEditorState(initialEditorState);
editor.setEditorState(parsedEditorState, {
tag: lexical.HISTORY_MERGE_TAG
});
break;
}
case 'object':
{
editor.setEditorState(initialEditorState, {
tag: lexical.HISTORY_MERGE_TAG
});
break;
}
case 'function':
{
editor.update(() => {
const root1 = lexical.$getRoot();
if (root1.isEmpty()) {
initialEditorState(editor);
}
}, {
tag: lexical.HISTORY_MERGE_TAG
});
break;
}
}
} else {
const paragraph = lexical.$createParagraphNode();
root.append(paragraph);
const {
activeElement
} = document;
if (lexical.$getSelection() !== null || activeElement !== null && activeElement === editor.getRootElement()) {
paragraph.select();
}
}
}
}, {
tag: lexical.HISTORY_MERGE_TAG
});
}
function clearEditorSkipCollab(editor, binding) {
// reset editor state
editor.update(() => {
const root = lexical.$getRoot();
root.clear();
root.select();
}, {
tag: lexical.SKIP_COLLAB_TAG
});
if (binding.cursors == null) {
return;
}
const cursors = binding.cursors;
if (cursors == null) {
return;
}
const cursorsContainer = binding.cursorsContainer;
if (cursorsContainer == null) {
return;
}
// reset cursors in dom
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]);
}
}
}
}
/**
* 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.
*
*/
function CollaborationPlugin({
id,
providerFactory,
shouldBootstrap,
username,
cursorColor,
cursorsContainerRef,
initialEditorState,
excludedProperties,
awarenessData,
syncCursorPositionsFn
}) {
const isBindingInitialized = React.useRef(false);
const isProviderInitialized = React.useRef(false);
const collabContext = LexicalCollaborationContext.useCollaborationContext(username, cursorColor);
const {
yjsDocMap,
name,
color
} = collabContext;
const [editor] = LexicalComposerContext.useLexicalComposerContext();
React.useEffect(() => {
collabContext.isCollabActive = true;
return () => {
// Resetting flag only when unmount top level editor collab plugin. Nested
// editors (e.g. image caption) should unmount without affecting it
if (editor._parentEditor == null) {
collabContext.isCollabActive = false;
}
};
}, [collabContext, editor]);
const [provider, setProvider] = React.useState();
const [doc, setDoc] = React.useState();
React.useEffect(() => {
if (isProviderInitialized.current) {
return;
}
isProviderInitialized.current = true;
const newProvider = providerFactory(id, yjsDocMap);
setProvider(newProvider);
setDoc(yjsDocMap.get(id));
return () => {
newProvider.disconnect();
};
}, [id, providerFactory, yjsDocMap]);
const [binding, setBinding] = React.useState();
React.useEffect(() => {
if (!provider) {
return;
}
if (isBindingInitialized.current) {
return;
}
isBindingInitialized.current = true;
const newBinding = yjs.createBinding(editor, provider, id, doc || yjsDocMap.get(id), yjsDocMap, excludedProperties);
setBinding(newBinding);
return () => {
newBinding.root.destroy(newBinding);
};
}, [editor, provider, id, yjsDocMap, doc, excludedProperties]);
if (!provider || !binding) {
return /*#__PURE__*/jsxRuntime.jsx(jsxRuntime.Fragment, {});
}
return /*#__PURE__*/jsxRuntime.jsx(YjsCollaborationCursors, {
awarenessData: awarenessData,
binding: binding,
collabContext: collabContext,
color: color,
cursorsContainerRef: cursorsContainerRef,
editor: editor,
id: id,
initialEditorState: initialEditorState,
name: name,
provider: provider,
setDoc: setDoc,
shouldBootstrap: shouldBootstrap,
yjsDocMap: yjsDocMap,
syncCursorPositionsFn: syncCursorPositionsFn
});
}
function YjsCollaborationCursors({
editor,
id,
provider,
yjsDocMap,
name,
color,
shouldBootstrap,
cursorsContainerRef,
initialEditorState,
awarenessData,
collabContext,
binding,
setDoc,
syncCursorPositionsFn
}) {
const cursors = useYjsCollaboration(editor, id, provider, yjsDocMap, name, color, shouldBootstrap, binding, setDoc, cursorsContainerRef, initialEditorState, awarenessData, syncCursorPositionsFn);
collabContext.clientID = binding.clientID;
useYjsHistory(editor, binding);
useYjsFocusTracking(editor, provider, name, color, awarenessData);
return cursors;
}
exports.CollaborationPlugin = CollaborationPlugin;
+385
View File
@@ -0,0 +1,385 @@
/**
* 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.
*
*/
import { useCollaborationContext } from '@lexical/react/LexicalCollaborationContext';
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { initLocalState, syncLexicalUpdateToYjs, TOGGLE_CONNECT_COMMAND, syncCursorPositions, setLocalStateFocus, createUndoManager, CONNECTED_COMMAND, syncYjsChangesToLexical, createBinding } from '@lexical/yjs';
import * as React from 'react';
import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
import { mergeRegister } from '@lexical/utils';
import { SKIP_COLLAB_TAG, COMMAND_PRIORITY_EDITOR, FOCUS_COMMAND, BLUR_COMMAND, UNDO_COMMAND, REDO_COMMAND, CAN_UNDO_COMMAND, CAN_REDO_COMMAND, $getRoot, HISTORY_MERGE_TAG, $createParagraphNode, $getSelection } from 'lexical';
import { createPortal } from 'react-dom';
import { UndoManager } from 'yjs';
import { jsx, Fragment } from '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.
*
*/
function useYjsCollaboration(editor, id, provider, docMap, name, color, shouldBootstrap, binding, setDoc, cursorsContainerRef, initialEditorState, awarenessData, syncCursorPositionsFn = syncCursorPositions) {
const isReloadingDoc = useRef(false);
const connect = useCallback(() => provider.connect(), [provider]);
const disconnect = useCallback(() => {
try {
provider.disconnect();
} catch (e) {
// Do nothing
}
}, [provider]);
useEffect(() => {
const {
root
} = binding;
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.current === false) {
initializeEditor(editor, initialEditorState);
}
isReloadingDoc.current = false;
};
const onAwarenessUpdate = () => {
syncCursorPositionsFn(binding, provider);
};
const onYjsTreeChanges = (events, transaction) => {
const origin = transaction.origin;
if (origin !== binding) {
const isFromUndoManger = origin instanceof UndoManager;
syncYjsChangesToLexical(binding, provider, events, isFromUndoManger, syncCursorPositionsFn);
}
};
initLocalState(provider, name, color, document.activeElement === editor.getRootElement(), awarenessData || {});
const onProviderDocReload = ydoc => {
clearEditorSkipCollab(editor, binding);
setDoc(ydoc);
docMap.set(id, ydoc);
isReloadingDoc.current = true;
};
provider.on('reload', onProviderDocReload);
provider.on('status', onStatus);
provider.on('sync', onSync);
awareness.on('update', onAwarenessUpdate);
// This updates the local editor state when we receive updates from other clients
root.getSharedType().observeDeep(onYjsTreeChanges);
const removeListener = editor.registerUpdateListener(({
prevEditorState,
editorState,
dirtyLeaves,
dirtyElements,
normalizedNodes,
tags
}) => {
if (tags.has(SKIP_COLLAB_TAG) === false) {
syncLexicalUpdateToYjs(binding, provider, prevEditorState, editorState, dirtyElements, dirtyLeaves, normalizedNodes, tags);
}
});
const connectionPromise = connect();
return () => {
if (isReloadingDoc.current === false) {
if (connectionPromise) {
connectionPromise.then(disconnect);
} else {
// Workaround for race condition in StrictMode. It's possible there
// is a different race for the above case where connect returns a
// promise, but we don't have an example of that in-repo.
// It's possible that there is a similar issue with
// TOGGLE_CONNECT_COMMAND below when the provider connect returns a
// promise.
// https://github.com/facebook/lexical/issues/6640
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();
};
}, [binding, color, connect, disconnect, docMap, editor, id, initialEditorState, name, provider, shouldBootstrap, awarenessData, setDoc, syncCursorPositionsFn]);
const cursorsContainer = useMemo(() => {
const ref = element => {
binding.cursorsContainer = element;
};
return /*#__PURE__*/createPortal(/*#__PURE__*/jsx("div", {
ref: ref
}), cursorsContainerRef && cursorsContainerRef.current || document.body);
}, [binding, cursorsContainerRef]);
useEffect(() => {
return editor.registerCommand(TOGGLE_CONNECT_COMMAND, payload => {
const shouldConnect = payload;
if (shouldConnect) {
// eslint-disable-next-line no-console
console.log('Collaboration connected!');
connect();
} else {
// eslint-disable-next-line no-console
console.log('Collaboration disconnected!');
disconnect();
}
return true;
}, COMMAND_PRIORITY_EDITOR);
}, [connect, disconnect, editor]);
return cursorsContainer;
}
function useYjsFocusTracking(editor, provider, name, color, awarenessData) {
useEffect(() => {
return mergeRegister(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));
}, [color, editor, name, provider, awarenessData]);
}
function useYjsHistory(editor, binding) {
const undoManager = useMemo(() => createUndoManager(binding, binding.root.getSharedType()), [binding]);
useEffect(() => {
const undo = () => {
undoManager.undo();
};
const redo = () => {
undoManager.redo();
};
return mergeRegister(editor.registerCommand(UNDO_COMMAND, () => {
undo();
return true;
}, COMMAND_PRIORITY_EDITOR), editor.registerCommand(REDO_COMMAND, () => {
redo();
return true;
}, COMMAND_PRIORITY_EDITOR));
});
const clearHistory = useCallback(() => {
undoManager.clear();
}, [undoManager]);
// Exposing undo and redo states
React.useEffect(() => {
const updateUndoRedoStates = () => {
editor.dispatchCommand(CAN_UNDO_COMMAND, undoManager.undoStack.length > 0);
editor.dispatchCommand(CAN_REDO_COMMAND, undoManager.redoStack.length > 0);
};
undoManager.on('stack-item-added', updateUndoRedoStates);
undoManager.on('stack-item-popped', updateUndoRedoStates);
undoManager.on('stack-cleared', updateUndoRedoStates);
return () => {
undoManager.off('stack-item-added', updateUndoRedoStates);
undoManager.off('stack-item-popped', updateUndoRedoStates);
undoManager.off('stack-cleared', updateUndoRedoStates);
};
}, [editor, undoManager]);
return clearHistory;
}
function initializeEditor(editor, initialEditorState) {
editor.update(() => {
const root = $getRoot();
if (root.isEmpty()) {
if (initialEditorState) {
switch (typeof initialEditorState) {
case 'string':
{
const parsedEditorState = editor.parseEditorState(initialEditorState);
editor.setEditorState(parsedEditorState, {
tag: HISTORY_MERGE_TAG
});
break;
}
case 'object':
{
editor.setEditorState(initialEditorState, {
tag: HISTORY_MERGE_TAG
});
break;
}
case 'function':
{
editor.update(() => {
const root1 = $getRoot();
if (root1.isEmpty()) {
initialEditorState(editor);
}
}, {
tag: HISTORY_MERGE_TAG
});
break;
}
}
} else {
const paragraph = $createParagraphNode();
root.append(paragraph);
const {
activeElement
} = document;
if ($getSelection() !== null || activeElement !== null && activeElement === editor.getRootElement()) {
paragraph.select();
}
}
}
}, {
tag: HISTORY_MERGE_TAG
});
}
function clearEditorSkipCollab(editor, binding) {
// reset editor state
editor.update(() => {
const root = $getRoot();
root.clear();
root.select();
}, {
tag: SKIP_COLLAB_TAG
});
if (binding.cursors == null) {
return;
}
const cursors = binding.cursors;
if (cursors == null) {
return;
}
const cursorsContainer = binding.cursorsContainer;
if (cursorsContainer == null) {
return;
}
// reset cursors in dom
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]);
}
}
}
}
/**
* 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.
*
*/
function CollaborationPlugin({
id,
providerFactory,
shouldBootstrap,
username,
cursorColor,
cursorsContainerRef,
initialEditorState,
excludedProperties,
awarenessData,
syncCursorPositionsFn
}) {
const isBindingInitialized = useRef(false);
const isProviderInitialized = useRef(false);
const collabContext = useCollaborationContext(username, cursorColor);
const {
yjsDocMap,
name,
color
} = collabContext;
const [editor] = useLexicalComposerContext();
useEffect(() => {
collabContext.isCollabActive = true;
return () => {
// Resetting flag only when unmount top level editor collab plugin. Nested
// editors (e.g. image caption) should unmount without affecting it
if (editor._parentEditor == null) {
collabContext.isCollabActive = false;
}
};
}, [collabContext, editor]);
const [provider, setProvider] = useState();
const [doc, setDoc] = useState();
useEffect(() => {
if (isProviderInitialized.current) {
return;
}
isProviderInitialized.current = true;
const newProvider = providerFactory(id, yjsDocMap);
setProvider(newProvider);
setDoc(yjsDocMap.get(id));
return () => {
newProvider.disconnect();
};
}, [id, providerFactory, yjsDocMap]);
const [binding, setBinding] = useState();
useEffect(() => {
if (!provider) {
return;
}
if (isBindingInitialized.current) {
return;
}
isBindingInitialized.current = true;
const newBinding = createBinding(editor, provider, id, doc || yjsDocMap.get(id), yjsDocMap, excludedProperties);
setBinding(newBinding);
return () => {
newBinding.root.destroy(newBinding);
};
}, [editor, provider, id, yjsDocMap, doc, excludedProperties]);
if (!provider || !binding) {
return /*#__PURE__*/jsx(Fragment, {});
}
return /*#__PURE__*/jsx(YjsCollaborationCursors, {
awarenessData: awarenessData,
binding: binding,
collabContext: collabContext,
color: color,
cursorsContainerRef: cursorsContainerRef,
editor: editor,
id: id,
initialEditorState: initialEditorState,
name: name,
provider: provider,
setDoc: setDoc,
shouldBootstrap: shouldBootstrap,
yjsDocMap: yjsDocMap,
syncCursorPositionsFn: syncCursorPositionsFn
});
}
function YjsCollaborationCursors({
editor,
id,
provider,
yjsDocMap,
name,
color,
shouldBootstrap,
cursorsContainerRef,
initialEditorState,
awarenessData,
collabContext,
binding,
setDoc,
syncCursorPositionsFn
}) {
const cursors = useYjsCollaboration(editor, id, provider, yjsDocMap, name, color, shouldBootstrap, binding, setDoc, cursorsContainerRef, initialEditorState, awarenessData, syncCursorPositionsFn);
collabContext.clientID = binding.clientID;
useYjsHistory(editor, binding);
useYjsFocusTracking(editor, provider, name, color, awarenessData);
return cursors;
}
export { CollaborationPlugin };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalCollaborationPlugin = process.env.NODE_ENV !== 'production' ? require('./LexicalCollaborationPlugin.dev.js') : require('./LexicalCollaborationPlugin.prod.js');
module.exports = LexicalCollaborationPlugin;
+50
View File
@@ -0,0 +1,50 @@
/**
* 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.
*
* @flow strict
*/
import type {InitialEditorStateType} from '@lexical/react/LexicalComposer';
import type {ExcludedProperties, ProviderAwareness} from '@lexical/yjs';
import type {Doc, RelativePosition} from 'yjs';
export interface Provider {
connect(): void | Promise<void>;
disconnect(): void;
awareness: ProviderAwareness;
on(type: 'sync', cb: (isSynced: boolean) => void): void;
on(type: 'status', cb: ({status: string}) => void): void;
// $FlowFixMe[unclear-type]: temp
on(type: 'update', cb: (any) => void): void;
on(type: 'reload', cb: (doc: Doc) => void): void;
off(type: 'sync', cb: (isSynced: boolean) => void): void;
// $FlowFixMe[unclear-type]: temp
off(type: 'update', cb: (any) => void): void;
off(type: 'status', cb: ({status: string}) => void): void;
off(type: 'reload', cb: (doc: Doc) => void): void;
}
export type ProviderFactory = (
id: string,
yjsDocMap: Map<string, Doc>,
) => Provider;
export type CursorsContainerRef = {current: null | HTMLElement};
declare export function CollaborationPlugin(arg0: {
id: string,
providerFactory: (
// eslint-disable-next-line no-shadow
id: string,
yjsDocMap: Map<string, Doc>,
) => Provider,
shouldBootstrap: boolean,
username?: string,
cursorColor?: string,
cursorsContainerRef?: CursorsContainerRef,
initialEditorState?: InitialEditorStateType,
excludedProperties?: ExcludedProperties,
}): React.Node;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
*/
import * as modDev from './LexicalCollaborationPlugin.dev.mjs';
import * as modProd from './LexicalCollaborationPlugin.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const CollaborationPlugin = mod.CollaborationPlugin;
+10
View File
@@ -0,0 +1,10 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalCollaborationPlugin.dev.mjs') : import('./LexicalCollaborationPlugin.prod.mjs'));
export const CollaborationPlugin = mod.CollaborationPlugin;
File diff suppressed because one or more lines are too long
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{useCollaborationContext as t}from"@lexical/react/LexicalCollaborationContext";import{useLexicalComposerContext as e}from"@lexical/react/LexicalComposerContext";import{initLocalState as o,syncLexicalUpdateToYjs as r,TOGGLE_CONNECT_COMMAND as n,syncCursorPositions as s,setLocalStateFocus as c,createUndoManager as a,CONNECTED_COMMAND as i,syncYjsChangesToLexical as l,createBinding as d}from"@lexical/yjs";import*as u from"react";import{useRef as m,useCallback as f,useEffect as p,useMemo as g,useState as C}from"react";import{mergeRegister as y}from"@lexical/utils";import{SKIP_COLLAB_TAG as h,COMMAND_PRIORITY_EDITOR as E,FOCUS_COMMAND as x,BLUR_COMMAND as b,UNDO_COMMAND as v,REDO_COMMAND as S,CAN_UNDO_COMMAND as D,CAN_REDO_COMMAND as k,$getRoot as j,HISTORY_MERGE_TAG as w,$createParagraphNode as R,$getSelection as F}from"lexical";import{createPortal as L}from"react-dom";import{UndoManager as P}from"yjs";import{jsx as T,Fragment as A}from"react/jsx-runtime";function B(t,e,c,a,d,u,C,y,x,b,v,S,D=s){const k=m(!1),A=f((()=>c.connect()),[c]),B=f((()=>{try{c.disconnect()}catch(t){}}),[c]);p((()=>{const{root:n}=y,{awareness:s}=c,m=({status:e})=>{t.dispatchCommand(i,"connected"===e)},f=e=>{C&&e&&n.isEmpty()&&0===n._xmlText._length&&!1===k.current&&function(t,e){t.update((()=>{const o=j();if(o.isEmpty())if(e)switch(typeof e){case"string":{const o=t.parseEditorState(e);t.setEditorState(o,{tag:w});break}case"object":t.setEditorState(e,{tag:w});break;case"function":t.update((()=>{j().isEmpty()&&e(t)}),{tag:w})}else{const e=R();o.append(e);const{activeElement:r}=document;(null!==F()||null!==r&&r===t.getRootElement())&&e.select()}}),{tag:w})}(t,v),k.current=!1},p=()=>{D(y,c)},g=(t,e)=>{const o=e.origin;if(o!==y){l(y,c,t,o instanceof P,D)}};o(c,d,u,document.activeElement===t.getRootElement(),S||{});const E=o=>{!function(t,e){if(t.update((()=>{const t=j();t.clear(),t.select()}),{tag:h}),null==e.cursors)return;const o=e.cursors;if(null==o)return;const r=e.cursorsContainer;if(null==r)return;const n=Array.from(o.values());for(let t=0;t<n.length;t++){const e=n[t].selection;if(e&&null!=e.selections){const o=e.selections;for(let e=0;e<o.length;e++)r.removeChild(o[t])}}}(t,y),x(o),a.set(e,o),k.current=!0};c.on("reload",E),c.on("status",m),c.on("sync",f),s.on("update",p),n.getSharedType().observeDeep(g);const b=t.registerUpdateListener((({prevEditorState:t,editorState:e,dirtyLeaves:o,dirtyElements:n,normalizedNodes:s,tags:a})=>{!1===a.has(h)&&r(y,c,t,e,n,o,s,a)})),L=A();return()=>{!1===k.current&&(L?L.then(B):B()),c.off("sync",f),c.off("status",m),c.off("reload",E),s.off("update",p),n.getSharedType().unobserveDeep(g),a.delete(e),b()}}),[y,u,A,B,a,t,e,v,d,c,C,S,x,D]);const M=g((()=>L(T("div",{ref:t=>{y.cursorsContainer=t}}),b&&b.current||document.body)),[y,b]);return p((()=>t.registerCommand(n,(t=>(t?(console.log("Collaboration connected!"),A()):(console.log("Collaboration disconnected!"),B()),!0)),E)),[A,B,t]),M}function M(t,e){const o=g((()=>a(e,e.root.getSharedType())),[e]);p((()=>y(t.registerCommand(v,(()=>(o.undo(),!0)),E),t.registerCommand(S,(()=>(o.redo(),!0)),E))));const r=f((()=>{o.clear()}),[o]);return u.useEffect((()=>{const e=()=>{t.dispatchCommand(D,o.undoStack.length>0),t.dispatchCommand(k,o.redoStack.length>0)};return o.on("stack-item-added",e),o.on("stack-item-popped",e),o.on("stack-cleared",e),()=>{o.off("stack-item-added",e),o.off("stack-item-popped",e),o.off("stack-cleared",e)}}),[t,o]),r}function _({id:o,providerFactory:r,shouldBootstrap:n,username:s,cursorColor:c,cursorsContainerRef:a,initialEditorState:i,excludedProperties:l,awarenessData:u,syncCursorPositionsFn:f}){const g=m(!1),y=m(!1),h=t(s,c),{yjsDocMap:E,name:x,color:b}=h,[v]=e();p((()=>(h.isCollabActive=!0,()=>{null==v._parentEditor&&(h.isCollabActive=!1)})),[h,v]);const[S,D]=C(),[k,j]=C();p((()=>{if(y.current)return;y.current=!0;const t=r(o,E);return D(t),j(E.get(o)),()=>{t.disconnect()}}),[o,r,E]);const[w,R]=C();return p((()=>{if(!S)return;if(g.current)return;g.current=!0;const t=d(v,S,o,k||E.get(o),E,l);return R(t),()=>{t.root.destroy(t)}}),[v,S,o,E,k,l]),S&&w?T(I,{awarenessData:u,binding:w,collabContext:h,color:b,cursorsContainerRef:a,editor:v,id:o,initialEditorState:i,name:x,provider:S,setDoc:j,shouldBootstrap:n,yjsDocMap:E,syncCursorPositionsFn:f}):T(A,{})}function I({editor:t,id:e,provider:o,yjsDocMap:r,name:n,color:s,shouldBootstrap:a,cursorsContainerRef:i,initialEditorState:l,awarenessData:d,collabContext:u,binding:m,setDoc:f,syncCursorPositionsFn:g}){const C=B(t,e,o,r,n,s,a,m,f,i,l,d,g);return u.clientID=m.clientID,M(t,m),function(t,e,o,r,n){p((()=>y(t.registerCommand(x,(()=>(c(e,o,r,!0,n||{}),!1)),E),t.registerCommand(b,(()=>(c(e,o,r,!1,n||{}),!1)),E))),[r,t,o,e,n])}(t,o,n,s,d),C}export{_ as CollaborationPlugin};
+25
View File
@@ -0,0 +1,25 @@
/**
* 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.
*
*/
import type { JSX } from 'react';
import { EditorState, EditorThemeClasses, HTMLConfig, Klass, LexicalEditor, LexicalNode, LexicalNodeReplacement } from 'lexical';
import * as React from 'react';
export type InitialEditorStateType = null | string | EditorState | ((editor: LexicalEditor) => void);
export type InitialConfigType = Readonly<{
namespace: string;
nodes?: ReadonlyArray<Klass<LexicalNode> | LexicalNodeReplacement>;
onError: (error: Error, editor: LexicalEditor) => void;
editable?: boolean;
theme?: EditorThemeClasses;
editorState?: InitialEditorStateType;
html?: HTMLConfig;
}>;
type Props = React.PropsWithChildren<{
initialConfig: InitialConfigType;
}>;
export declare function LexicalComposer({ initialConfig, children }: Props): JSX.Element;
export {};
+134
View File
@@ -0,0 +1,134 @@
/**
* 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.
*
*/
'use strict';
var LexicalComposerContext = require('@lexical/react/LexicalComposerContext');
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 CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
/**
* 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.
*
*/
// This workaround is no longer necessary in React 19,
// but we currently support React >=17.x
// https://github.com/facebook/react/pull/26395
const useLayoutEffectImpl = CAN_USE_DOM ? react.useLayoutEffect : react.useEffect;
/**
* 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 HISTORY_MERGE_OPTIONS = {
tag: lexical.HISTORY_MERGE_TAG
};
function LexicalComposer({
initialConfig,
children
}) {
const composerContext = react.useMemo(() => {
const {
theme,
namespace,
nodes,
onError,
editorState: initialEditorState,
html
} = initialConfig;
const context = LexicalComposerContext.createLexicalComposerContext(null, theme);
const editor = lexical.createEditor({
editable: initialConfig.editable,
html,
namespace,
nodes,
onError: error => onError(error, editor),
theme
});
initializeEditor(editor, initialEditorState);
return [editor, context];
},
// We only do this for init
// eslint-disable-next-line react-hooks/exhaustive-deps
[]);
useLayoutEffectImpl(() => {
const isEditable = initialConfig.editable;
const [editor] = composerContext;
editor.setEditable(isEditable !== undefined ? isEditable : true);
// We only do this for init
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return /*#__PURE__*/jsxRuntime.jsx(LexicalComposerContext.LexicalComposerContext.Provider, {
value: composerContext,
children: children
});
}
function initializeEditor(editor, initialEditorState) {
if (initialEditorState === null) {
return;
} else if (initialEditorState === undefined) {
editor.update(() => {
const root = lexical.$getRoot();
if (root.isEmpty()) {
const paragraph = lexical.$createParagraphNode();
root.append(paragraph);
const activeElement = CAN_USE_DOM ? document.activeElement : null;
if (lexical.$getSelection() !== null || activeElement !== null && activeElement === editor.getRootElement()) {
paragraph.select();
}
}
}, HISTORY_MERGE_OPTIONS);
} else if (initialEditorState !== null) {
switch (typeof initialEditorState) {
case 'string':
{
const parsedEditorState = editor.parseEditorState(initialEditorState);
editor.setEditorState(parsedEditorState, HISTORY_MERGE_OPTIONS);
break;
}
case 'object':
{
editor.setEditorState(initialEditorState, HISTORY_MERGE_OPTIONS);
break;
}
case 'function':
{
editor.update(() => {
const root = lexical.$getRoot();
if (root.isEmpty()) {
initialEditorState(editor);
}
}, HISTORY_MERGE_OPTIONS);
break;
}
}
}
}
exports.LexicalComposer = LexicalComposer;
+132
View File
@@ -0,0 +1,132 @@
/**
* 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.
*
*/
import { createLexicalComposerContext, LexicalComposerContext } from '@lexical/react/LexicalComposerContext';
import { createEditor, $getRoot, $createParagraphNode, $getSelection, HISTORY_MERGE_TAG } from 'lexical';
import { useLayoutEffect, useEffect, useMemo } from 'react';
import { jsx } from '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 CAN_USE_DOM = typeof window !== 'undefined' && typeof window.document !== 'undefined' && typeof window.document.createElement !== 'undefined';
/**
* 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.
*
*/
// This workaround is no longer necessary in React 19,
// but we currently support React >=17.x
// https://github.com/facebook/react/pull/26395
const useLayoutEffectImpl = CAN_USE_DOM ? useLayoutEffect : useEffect;
/**
* 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 HISTORY_MERGE_OPTIONS = {
tag: HISTORY_MERGE_TAG
};
function LexicalComposer({
initialConfig,
children
}) {
const composerContext = useMemo(() => {
const {
theme,
namespace,
nodes,
onError,
editorState: initialEditorState,
html
} = initialConfig;
const context = createLexicalComposerContext(null, theme);
const editor = createEditor({
editable: initialConfig.editable,
html,
namespace,
nodes,
onError: error => onError(error, editor),
theme
});
initializeEditor(editor, initialEditorState);
return [editor, context];
},
// We only do this for init
// eslint-disable-next-line react-hooks/exhaustive-deps
[]);
useLayoutEffectImpl(() => {
const isEditable = initialConfig.editable;
const [editor] = composerContext;
editor.setEditable(isEditable !== undefined ? isEditable : true);
// We only do this for init
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return /*#__PURE__*/jsx(LexicalComposerContext.Provider, {
value: composerContext,
children: children
});
}
function initializeEditor(editor, initialEditorState) {
if (initialEditorState === null) {
return;
} else if (initialEditorState === undefined) {
editor.update(() => {
const root = $getRoot();
if (root.isEmpty()) {
const paragraph = $createParagraphNode();
root.append(paragraph);
const activeElement = CAN_USE_DOM ? document.activeElement : null;
if ($getSelection() !== null || activeElement !== null && activeElement === editor.getRootElement()) {
paragraph.select();
}
}
}, HISTORY_MERGE_OPTIONS);
} else if (initialEditorState !== null) {
switch (typeof initialEditorState) {
case 'string':
{
const parsedEditorState = editor.parseEditorState(initialEditorState);
editor.setEditorState(parsedEditorState, HISTORY_MERGE_OPTIONS);
break;
}
case 'object':
{
editor.setEditorState(initialEditorState, HISTORY_MERGE_OPTIONS);
break;
}
case 'function':
{
editor.update(() => {
const root = $getRoot();
if (root.isEmpty()) {
initialEditorState(editor);
}
}, HISTORY_MERGE_OPTIONS);
break;
}
}
}
}
export { LexicalComposer };
+11
View File
@@ -0,0 +1,11 @@
/**
* 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.
*
*/
'use strict'
const LexicalComposer = process.env.NODE_ENV !== 'production' ? require('./LexicalComposer.dev.js') : require('./LexicalComposer.prod.js');
module.exports = LexicalComposer;
+40
View File
@@ -0,0 +1,40 @@
/**
* 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.
*
* @flow strict
*/
import type {
EditorThemeClasses,
LexicalEditor,
LexicalNode,
EditorState,
LexicalNodeReplacement,
HTMLConfig,
} from 'lexical';
export type InitialEditorStateType =
| null
| string
| EditorState
| ((editor: LexicalEditor) => void);
export type InitialConfigType = $ReadOnly<{
editable?: boolean,
namespace: string,
nodes?: $ReadOnlyArray<Class<LexicalNode> | LexicalNodeReplacement>,
theme?: EditorThemeClasses,
editorState?: InitialEditorStateType,
onError: (error: Error, editor: LexicalEditor) => void,
html?: HTMLConfig,
}>;
type Props = {
initialConfig: InitialConfigType,
children: React.Node,
};
declare export function LexicalComposer(Props): React.MixedElement;
+12
View File
@@ -0,0 +1,12 @@
/**
* 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.
*
*/
import * as modDev from './LexicalComposer.dev.mjs';
import * as modProd from './LexicalComposer.prod.mjs';
const mod = process.env.NODE_ENV !== 'production' ? modDev : modProd;
export const LexicalComposer = mod.LexicalComposer;
+10
View File
@@ -0,0 +1,10 @@
/**
* 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 mod = await (process.env.NODE_ENV !== 'production' ? import('./LexicalComposer.dev.mjs') : import('./LexicalComposer.prod.mjs'));
export const LexicalComposer = mod.LexicalComposer;
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
"use strict";var e=require("@lexical/react/LexicalComposerContext"),t=require("lexical"),o=require("react"),n=require("react/jsx-runtime");const r="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,i=r?o.useLayoutEffect:o.useEffect,a={tag:t.HISTORY_MERGE_TAG};exports.LexicalComposer=function({initialConfig:c,children:s}){const l=o.useMemo((()=>{const{theme:o,namespace:n,nodes:i,onError:s,editorState:l,html:d}=c,u=e.createLexicalComposerContext(null,o),m=t.createEditor({editable:c.editable,html:d,namespace:n,nodes:i,onError:e=>s(e,m),theme:o});return function(e,o){if(null===o)return;if(void 0===o)e.update((()=>{const o=t.$getRoot();if(o.isEmpty()){const n=t.$createParagraphNode();o.append(n);const i=r?document.activeElement:null;(null!==t.$getSelection()||null!==i&&i===e.getRootElement())&&n.select()}}),a);else if(null!==o)switch(typeof o){case"string":{const t=e.parseEditorState(o);e.setEditorState(t,a);break}case"object":e.setEditorState(o,a);break;case"function":e.update((()=>{t.$getRoot().isEmpty()&&o(e)}),a)}}(m,l),[m,u]}),[]);return i((()=>{const e=c.editable,[t]=l;t.setEditable(void 0===e||e)}),[]),n.jsx(e.LexicalComposerContext.Provider,{value:l,children:s})};
+9
View File
@@ -0,0 +1,9 @@
/**
* 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.
*
*/
import{createLexicalComposerContext as e,LexicalComposerContext as t}from"@lexical/react/LexicalComposerContext";import{createEditor as o,$getRoot as n,$createParagraphNode as i,$getSelection as r,HISTORY_MERGE_TAG as a}from"lexical";import{useLayoutEffect as c,useEffect as l,useMemo as d}from"react";import{jsx as s}from"react/jsx-runtime";const m="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement,u=m?c:l,p={tag:a};function f({initialConfig:a,children:c}){const l=d((()=>{const{theme:t,namespace:c,nodes:l,onError:d,editorState:s,html:u}=a,f=e(null,t),E=o({editable:a.editable,html:u,namespace:c,nodes:l,onError:e=>d(e,E),theme:t});return function(e,t){if(null===t)return;if(void 0===t)e.update((()=>{const t=n();if(t.isEmpty()){const o=i();t.append(o);const n=m?document.activeElement:null;(null!==r()||null!==n&&n===e.getRootElement())&&o.select()}}),p);else if(null!==t)switch(typeof t){case"string":{const o=e.parseEditorState(t);e.setEditorState(o,p);break}case"object":e.setEditorState(t,p);break;case"function":e.update((()=>{n().isEmpty()&&t(e)}),p)}}(E,s),[E,f]}),[]);return u((()=>{const e=a.editable,[t]=l;t.setEditable(void 0===e||e)}),[]),s(t.Provider,{value:l,children:c})}export{f as LexicalComposer};

Some files were not shown because too many files have changed in this diff Show More