@byline/richtext-lexical 5.2.0 → 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +1 -1
- package/dist/field/apply-value-plugin.js +31 -6
- package/dist/field/capabilities/filter-transformers.d.ts +29 -0
- package/dist/field/capabilities/filter-transformers.js +8 -0
- package/dist/field/capabilities/registered-node-types.d.ts +29 -0
- package/dist/field/capabilities/registered-node-types.js +7 -0
- package/dist/field/config/built-in-extension-names.d.ts +42 -0
- package/dist/field/config/built-in-extension-names.js +12 -2
- package/dist/field/config/default-extensions.js +4 -0
- package/dist/field/config/default.js +8 -8
- package/dist/field/config/editor-config-context.d.ts +2 -2
- package/dist/field/config/editor-config-context.js +10 -13
- package/dist/field/config/resolve-editor-config.js +3 -3
- package/dist/field/config/root-dependencies.d.ts +26 -0
- package/dist/field/config/root-dependencies.js +8 -0
- package/dist/field/config/types.d.ts +61 -8
- package/dist/field/editor-context.js +44 -27
- package/dist/field/editor.js +15 -8
- package/dist/field/extensions/core-nodes/core-nodes-extension.d.ts +24 -0
- package/dist/field/extensions/core-nodes/core-nodes-extension.js +11 -0
- package/dist/field/extensions/heading/heading-extension.d.ts +20 -0
- package/dist/field/extensions/heading/heading-extension.js +9 -0
- package/dist/field/extensions/quote/quote-extension.d.ts +16 -0
- package/dist/field/extensions/quote/quote-extension.js +9 -0
- package/dist/field/hooks/use-markdown-toggle.d.ts +15 -0
- package/dist/field/hooks/use-markdown-toggle.js +12 -4
- package/dist/field/hooks/use-platform-modifier.d.ts +16 -0
- package/dist/field/hooks/use-platform-modifier.js +8 -0
- package/dist/field/markdown/transformers.d.ts +1 -0
- package/dist/field/markdown/transformers.js +5 -4
- package/dist/field/nodes/index.d.ts +11 -1
- package/dist/field/nodes/index.js +2 -2
- package/dist/field/normalize/adapted-notice.css +34 -0
- package/dist/field/normalize/adapted-notice.d.ts +29 -0
- package/dist/field/normalize/adapted-notice.js +35 -0
- package/dist/field/normalize/content-labels.d.ts +15 -0
- package/dist/field/normalize/content-labels.js +19 -0
- package/dist/field/normalize/declared-conversions.d.ts +131 -0
- package/dist/field/normalize/declared-conversions.js +93 -0
- package/dist/field/normalize/normalization-status.d.ts +33 -0
- package/dist/field/normalize/normalization-status.js +28 -0
- package/dist/field/normalize/normalize-value.d.ts +38 -0
- package/dist/field/normalize/normalize-value.js +132 -0
- package/dist/field/normalize/use-notice-text.d.ts +11 -0
- package/dist/field/normalize/use-notice-text.js +34 -0
- package/dist/field/plugins/toolbar-plugin/index.d.ts +23 -0
- package/dist/field/plugins/toolbar-plugin/index.js +104 -78
- package/dist/field/test-support/build-test-editor.js +49 -0
- package/dist/field/test-support/capture-editor.js +7 -0
- package/dist/field/test-support/frame-control.js +40 -0
- package/dist/field/test-support/jsdom-setup.js +21 -0
- package/dist/index.d.ts +7 -1
- package/dist/index.js +6 -1
- package/dist/scan/capability-manifest.d.ts +30 -0
- package/dist/scan/capability-manifest.js +43 -0
- package/dist/scan/scan-documents.d.ts +29 -0
- package/dist/scan/scan-documents.js +39 -0
- package/dist/scan/types.d.ts +63 -0
- package/dist/scan/types.js +1 -0
- package/dist/scan.d.ts +25 -0
- package/dist/scan.js +1 -0
- package/package.json +14 -5
- package/src/config.ts +1 -1
- package/src/field/apply-value-plugin.tsx +71 -5
- package/src/field/capabilities/filter-transformers.test.tsx +71 -0
- package/src/field/capabilities/filter-transformers.ts +42 -0
- package/src/field/capabilities/markdown-behaviour.test.tsx +188 -0
- package/src/field/capabilities/no-blanket-registration.test.tsx +86 -0
- package/src/field/capabilities/registered-node-types.test.tsx +29 -0
- package/src/field/capabilities/registered-node-types.ts +36 -0
- package/src/field/config/built-in-extension-names.test.node.ts +38 -14
- package/src/field/config/built-in-extension-names.ts +29 -1
- package/src/field/config/default-extensions.ts +6 -0
- package/src/field/config/default.ts +8 -8
- package/src/field/config/editor-config-context.tsx +14 -9
- package/src/field/config/list-removal.test.tsx +44 -0
- package/src/field/config/resolve-editor-config.test.node.ts +27 -6
- package/src/field/config/resolve-editor-config.ts +3 -3
- package/src/field/config/root-dependencies.ts +32 -0
- package/src/field/config/types.ts +61 -17
- package/src/field/editor-component.test.tsx +18 -3
- package/src/field/editor-context.regression.test.tsx +124 -0
- package/src/field/editor-context.tsx +90 -22
- package/src/field/editor.tsx +22 -12
- package/src/field/extensions/core-nodes/core-nodes-extension.test.tsx +13 -0
- package/src/field/extensions/core-nodes/core-nodes-extension.ts +32 -0
- package/src/field/extensions/heading/heading-extension.test.tsx +32 -0
- package/src/field/extensions/heading/heading-extension.ts +27 -0
- package/src/field/extensions/quote/quote-extension.ts +23 -0
- package/src/field/hooks/markdown-source-mode.test.tsx +221 -0
- package/src/field/hooks/use-markdown-toggle.ts +39 -4
- package/src/field/hooks/use-platform-modifier.test.tsx +81 -0
- package/src/field/hooks/use-platform-modifier.ts +41 -0
- package/src/field/markdown/transformers.ts +18 -4
- package/src/field/nodes/index.ts +11 -1
- package/src/field/normalize/adapted-notice.css +52 -0
- package/src/field/normalize/adapted-notice.tsx +63 -0
- package/src/field/normalize/clipboard-equivalence.test.tsx +330 -0
- package/src/field/normalize/content-labels.ts +44 -0
- package/src/field/normalize/declared-conversions.test.node.ts +67 -0
- package/src/field/normalize/declared-conversions.ts +160 -0
- package/src/field/normalize/emission-gate.test.tsx +302 -0
- package/src/field/normalize/normalization-status.tsx +48 -0
- package/src/field/normalize/normalize-load.test.tsx +371 -0
- package/src/field/normalize/normalize-value.test.node.ts +530 -0
- package/src/field/normalize/normalize-value.ts +180 -0
- package/src/field/normalize/notice-i18n.test.tsx +128 -0
- package/src/field/normalize/use-notice-text.ts +82 -0
- package/src/field/normalize/wiring.test.tsx +348 -0
- package/src/field/plugins/toolbar-plugin/block-format-capabilities.test.tsx +57 -0
- package/src/field/plugins/toolbar-plugin/index.tsx +131 -68
- package/src/field/test-support/build-test-editor.test.tsx +17 -0
- package/src/field/test-support/build-test-editor.ts +99 -0
- package/src/field/test-support/capture-editor.tsx +20 -0
- package/src/field/test-support/frame-control.ts +91 -0
- package/src/field/test-support/jsdom-setup.ts +41 -0
- package/src/index.ts +12 -1
- package/src/scan/capability-manifest.test.tsx +82 -0
- package/src/scan/capability-manifest.ts +81 -0
- package/src/scan/scan-documents.test.node.ts +125 -0
- package/src/scan/scan-documents.ts +78 -0
- package/src/scan/types.ts +66 -0
- package/src/scan.ts +27 -0
package/dist/config.d.ts
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
* registration factory (which dynamic-imports the editor runtime on first
|
|
13
13
|
* mount), the config types, the built-in extension **names**, and the
|
|
14
14
|
* light toolbar-authoring primitives — but NOT `RichTextField` /
|
|
15
|
-
* `EditorField` / `
|
|
15
|
+
* `EditorField` / `READABLE_NODES` or the heavy *content* extension classes
|
|
16
16
|
* (Table, InlineImage, Admonition, …), all of which statically pull React
|
|
17
17
|
* plugins, nodes, and the Lexical core. Import from here at registration
|
|
18
18
|
* sites (e.g. an admin/admin config that you want to evaluate eagerly)
|
|
@@ -1,16 +1,22 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
2
|
+
import { useLayoutEffect, useRef } from "react";
|
|
3
3
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
4
|
+
import { registeredNodeTypes } from "./capabilities/registered-node-types.js";
|
|
4
5
|
import { APPLY_VALUE_TAG } from "./constants.js";
|
|
6
|
+
import { useNormalizationStatus } from "./normalize/normalization-status.js";
|
|
7
|
+
import { normalizeValue } from "./normalize/normalize-value.js";
|
|
5
8
|
import { hashSerializedState } from "./utils/hashSerializedState.js";
|
|
6
9
|
function ApplyValuePlugin({ value, incomingHash, lastEmittedHashRef, normalizedIncomingHashRef, hasNormalizedBaselineRef }) {
|
|
7
10
|
const [editor] = useLexicalComposerContext();
|
|
11
|
+
const { setStatus } = useNormalizationStatus();
|
|
8
12
|
const lastAppliedHashRef = useRef(void 0);
|
|
13
|
+
const isRefusedRef = useRef(false);
|
|
9
14
|
const cancelWaiterRef = useRef(void 0);
|
|
10
|
-
|
|
15
|
+
useLayoutEffect(()=>{
|
|
11
16
|
if (null == value) return;
|
|
12
17
|
const nextRawHash = incomingHash;
|
|
13
|
-
|
|
18
|
+
const refused = isRefusedRef.current;
|
|
19
|
+
if (!refused && nextRawHash === lastEmittedHashRef.current) {
|
|
14
20
|
if (true !== hasNormalizedBaselineRef.current) {
|
|
15
21
|
hasNormalizedBaselineRef.current = true;
|
|
16
22
|
if (cancelWaiterRef.current) {
|
|
@@ -20,13 +26,31 @@ function ApplyValuePlugin({ value, incomingHash, lastEmittedHashRef, normalizedI
|
|
|
20
26
|
}
|
|
21
27
|
return;
|
|
22
28
|
}
|
|
23
|
-
if (nextRawHash === lastAppliedHashRef.current) return;
|
|
29
|
+
if (!refused && nextRawHash === lastAppliedHashRef.current) return;
|
|
24
30
|
if (cancelWaiterRef.current) {
|
|
25
31
|
cancelWaiterRef.current();
|
|
26
32
|
cancelWaiterRef.current = void 0;
|
|
27
33
|
}
|
|
28
34
|
hasNormalizedBaselineRef.current = false;
|
|
29
|
-
const
|
|
35
|
+
const normalized = normalizeValue(value, registeredNodeTypes(editor));
|
|
36
|
+
if ('refused' === normalized.status) {
|
|
37
|
+
setStatus({
|
|
38
|
+
kind: 'refused',
|
|
39
|
+
unsupportedTypes: normalized.unsupportedTypes
|
|
40
|
+
});
|
|
41
|
+
lastAppliedHashRef.current = void 0;
|
|
42
|
+
isRefusedRef.current = true;
|
|
43
|
+
hasNormalizedBaselineRef.current = false;
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
isRefusedRef.current = false;
|
|
47
|
+
setStatus('adapted' === normalized.status ? {
|
|
48
|
+
kind: 'adapted',
|
|
49
|
+
convertedTypes: normalized.convertedTypes
|
|
50
|
+
} : {
|
|
51
|
+
kind: 'ok'
|
|
52
|
+
});
|
|
53
|
+
const nextState = editor.parseEditorState('adapted' === normalized.status ? JSON.stringify(normalized.value) : value);
|
|
30
54
|
editor.setEditorState(nextState, {
|
|
31
55
|
tag: APPLY_VALUE_TAG
|
|
32
56
|
});
|
|
@@ -51,7 +75,8 @@ function ApplyValuePlugin({ value, incomingHash, lastEmittedHashRef, normalizedI
|
|
|
51
75
|
incomingHash,
|
|
52
76
|
lastEmittedHashRef,
|
|
53
77
|
normalizedIncomingHashRef,
|
|
54
|
-
hasNormalizedBaselineRef
|
|
78
|
+
hasNormalizedBaselineRef,
|
|
79
|
+
setStatus
|
|
55
80
|
]);
|
|
56
81
|
return null;
|
|
57
82
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { Transformer } from '@lexical/markdown';
|
|
9
|
+
import type { LexicalEditor } from 'lexical';
|
|
10
|
+
/**
|
|
11
|
+
* The transformers that can safely be used with this editor.
|
|
12
|
+
*
|
|
13
|
+
* Markdown is a second route into node creation, and it fails harder
|
|
14
|
+
* than paste does. `registerMarkdownShortcuts` throws when a transformer
|
|
15
|
+
* names a node class the editor has not registered, and it throws for
|
|
16
|
+
* the whole pipeline rather than skipping that one transformer — so a
|
|
17
|
+
* field with the table extension removed would lose Markdown entirely,
|
|
18
|
+
* not just tables. The same applies to every `$convertFromMarkdownString`
|
|
19
|
+
* call, which creates nodes directly.
|
|
20
|
+
*
|
|
21
|
+
* The membership test is `editor.hasNode`, which is exactly what
|
|
22
|
+
* `registerMarkdownShortcuts` uses for its own guard, so this filter
|
|
23
|
+
* cannot disagree with the check it exists to satisfy.
|
|
24
|
+
*
|
|
25
|
+
* Takes the list as a parameter rather than importing it: the Markdown
|
|
26
|
+
* module imports this one, so reaching back for `BYLINE_TRANSFORMERS`
|
|
27
|
+
* here would be a cycle.
|
|
28
|
+
*/
|
|
29
|
+
export declare function transformersFor(editor: LexicalEditor, transformers: ReadonlyArray<Transformer>): Array<Transformer>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
function transformersFor(editor, transformers) {
|
|
2
|
+
return transformers.filter((transformer)=>{
|
|
3
|
+
const dependencies = 'dependencies' in transformer ? transformer.dependencies : void 0;
|
|
4
|
+
if (null == dependencies) return true;
|
|
5
|
+
return dependencies.every((klass)=>editor.hasNode(klass));
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
export { transformersFor };
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { LexicalEditor } from 'lexical';
|
|
9
|
+
/**
|
|
10
|
+
* The set of node type strings this editor accepts.
|
|
11
|
+
*
|
|
12
|
+
* This is the ONLY place in Byline that reads `editor._nodes`. Lexical
|
|
13
|
+
* declares the property on the public `LexicalEditor` type but exposes
|
|
14
|
+
* no way to enumerate registered type strings — `hasNode` answers only
|
|
15
|
+
* for a class you already hold, while the normalizer works from
|
|
16
|
+
* serialized JSON where all it has is the type string. Keeping the
|
|
17
|
+
* access here gives a future Lexical change one site to fix.
|
|
18
|
+
*/
|
|
19
|
+
export declare function registeredNodeTypes(editor: LexicalEditor): ReadonlySet<string>;
|
|
20
|
+
/**
|
|
21
|
+
* Whether this editor accepts `type`.
|
|
22
|
+
*
|
|
23
|
+
* Node registration is what decides whether a structure can exist in the
|
|
24
|
+
* field at all — it governs the legacy paste path, `parseEditorState`
|
|
25
|
+
* and every command that creates a node — so this is the honest answer
|
|
26
|
+
* to "does this field support X", not whether some extension appears in
|
|
27
|
+
* a configuration list.
|
|
28
|
+
*/
|
|
29
|
+
export declare function supportsNodeType(editor: LexicalEditor, type: string): boolean;
|
|
@@ -31,6 +31,44 @@
|
|
|
31
31
|
* against the live extension `name`s is exercised by the editor's
|
|
32
32
|
* jsdom/integration coverage.
|
|
33
33
|
*/
|
|
34
|
+
export declare const bylineExtensionNames: {
|
|
35
|
+
readonly Admonition: '@byline/richtext-lexical/Admonition';
|
|
36
|
+
readonly AutoEmbed: '@byline/richtext-lexical/AutoEmbed';
|
|
37
|
+
readonly AutoLink: '@byline/richtext-lexical/AutoLink';
|
|
38
|
+
readonly CodeHighlight: '@byline/richtext-lexical/CodeHighlight';
|
|
39
|
+
readonly FloatingTextFormat: '@byline/richtext-lexical/FloatingTextFormat';
|
|
40
|
+
readonly FloatingUI: '@byline/richtext-lexical/FloatingUI';
|
|
41
|
+
readonly Heading: '@byline/richtext-lexical/Heading';
|
|
42
|
+
readonly HorizontalRule: '@byline/richtext-lexical/HorizontalRule';
|
|
43
|
+
readonly InlineImage: '@byline/richtext-lexical/InlineImage';
|
|
44
|
+
readonly Layout: '@byline/richtext-lexical/Layout';
|
|
45
|
+
readonly Link: '@byline/richtext-lexical/Link';
|
|
46
|
+
readonly Quote: '@byline/richtext-lexical/Quote';
|
|
47
|
+
readonly Table: '@byline/richtext-lexical/Table';
|
|
48
|
+
readonly Toolbar: '@byline/richtext-lexical/Toolbar';
|
|
49
|
+
readonly Vimeo: '@byline/richtext-lexical/Vimeo';
|
|
50
|
+
readonly YouTube: '@byline/richtext-lexical/YouTube';
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Names owned by upstream Lexical packages.
|
|
54
|
+
*
|
|
55
|
+
* These deliberately break the `@byline/richtext-lexical/<Key>`
|
|
56
|
+
* convention because Byline does not own the names — the extensions ship
|
|
57
|
+
* from `@lexical/list`. They are carried here so a site can remove lists
|
|
58
|
+
* without importing `@lexical/list` directly and taking a
|
|
59
|
+
* version-pinned dependency on `lexical` of its own.
|
|
60
|
+
*/
|
|
61
|
+
export declare const upstreamExtensionNames: {
|
|
62
|
+
readonly List: '@lexical/list/List';
|
|
63
|
+
readonly CheckList: '@lexical/list/CheckList';
|
|
64
|
+
};
|
|
65
|
+
/**
|
|
66
|
+
* Every extension a site may remove, replace or reorder.
|
|
67
|
+
*
|
|
68
|
+
* Note what is absent: `CoreNodesExtension` is injected by the editor
|
|
69
|
+
* root rather than configured here, because `MarkNode` and
|
|
70
|
+
* `OverflowNode` belong to no switchable feature.
|
|
71
|
+
*/
|
|
34
72
|
export declare const builtInExtensions: {
|
|
35
73
|
readonly Admonition: '@byline/richtext-lexical/Admonition';
|
|
36
74
|
readonly AutoEmbed: '@byline/richtext-lexical/AutoEmbed';
|
|
@@ -38,14 +76,18 @@ export declare const builtInExtensions: {
|
|
|
38
76
|
readonly CodeHighlight: '@byline/richtext-lexical/CodeHighlight';
|
|
39
77
|
readonly FloatingTextFormat: '@byline/richtext-lexical/FloatingTextFormat';
|
|
40
78
|
readonly FloatingUI: '@byline/richtext-lexical/FloatingUI';
|
|
79
|
+
readonly Heading: '@byline/richtext-lexical/Heading';
|
|
41
80
|
readonly HorizontalRule: '@byline/richtext-lexical/HorizontalRule';
|
|
42
81
|
readonly InlineImage: '@byline/richtext-lexical/InlineImage';
|
|
43
82
|
readonly Layout: '@byline/richtext-lexical/Layout';
|
|
44
83
|
readonly Link: '@byline/richtext-lexical/Link';
|
|
84
|
+
readonly Quote: '@byline/richtext-lexical/Quote';
|
|
45
85
|
readonly Table: '@byline/richtext-lexical/Table';
|
|
46
86
|
readonly Toolbar: '@byline/richtext-lexical/Toolbar';
|
|
47
87
|
readonly Vimeo: '@byline/richtext-lexical/Vimeo';
|
|
48
88
|
readonly YouTube: '@byline/richtext-lexical/YouTube';
|
|
89
|
+
readonly List: '@lexical/list/List';
|
|
90
|
+
readonly CheckList: '@lexical/list/CheckList';
|
|
49
91
|
};
|
|
50
92
|
/** Union of the built-in extension `name` strings. */
|
|
51
93
|
export type BuiltInExtensionName = (typeof builtInExtensions)[keyof typeof builtInExtensions];
|
|
@@ -1,17 +1,27 @@
|
|
|
1
|
-
const
|
|
1
|
+
const bylineExtensionNames = {
|
|
2
2
|
Admonition: '@byline/richtext-lexical/Admonition',
|
|
3
3
|
AutoEmbed: '@byline/richtext-lexical/AutoEmbed',
|
|
4
4
|
AutoLink: '@byline/richtext-lexical/AutoLink',
|
|
5
5
|
CodeHighlight: '@byline/richtext-lexical/CodeHighlight',
|
|
6
6
|
FloatingTextFormat: '@byline/richtext-lexical/FloatingTextFormat',
|
|
7
7
|
FloatingUI: '@byline/richtext-lexical/FloatingUI',
|
|
8
|
+
Heading: '@byline/richtext-lexical/Heading',
|
|
8
9
|
HorizontalRule: '@byline/richtext-lexical/HorizontalRule',
|
|
9
10
|
InlineImage: '@byline/richtext-lexical/InlineImage',
|
|
10
11
|
Layout: '@byline/richtext-lexical/Layout',
|
|
11
12
|
Link: '@byline/richtext-lexical/Link',
|
|
13
|
+
Quote: '@byline/richtext-lexical/Quote',
|
|
12
14
|
Table: '@byline/richtext-lexical/Table',
|
|
13
15
|
Toolbar: '@byline/richtext-lexical/Toolbar',
|
|
14
16
|
Vimeo: '@byline/richtext-lexical/Vimeo',
|
|
15
17
|
YouTube: '@byline/richtext-lexical/YouTube'
|
|
16
18
|
};
|
|
17
|
-
|
|
19
|
+
const upstreamExtensionNames = {
|
|
20
|
+
List: '@lexical/list/List',
|
|
21
|
+
CheckList: '@lexical/list/CheckList'
|
|
22
|
+
};
|
|
23
|
+
const builtInExtensions = {
|
|
24
|
+
...bylineExtensionNames,
|
|
25
|
+
...upstreamExtensionNames
|
|
26
|
+
};
|
|
27
|
+
export { builtInExtensions, bylineExtensionNames, upstreamExtensionNames };
|
|
@@ -6,10 +6,12 @@ import { BylineFloatingUIExtension } from "../extensions/byline-floating-ui/byli
|
|
|
6
6
|
import { BylineToolbarExtension } from "../extensions/byline-toolbar/byline-toolbar-extension.js";
|
|
7
7
|
import { CodeHighlightExtension } from "../extensions/code-highlight/code-highlight-extension.js";
|
|
8
8
|
import { FloatingTextFormatExtension } from "../extensions/floating-text-format/floating-text-format-extension.js";
|
|
9
|
+
import { HeadingExtension } from "../extensions/heading/heading-extension.js";
|
|
9
10
|
import { HorizontalRuleExtension } from "../extensions/horizontal-rule/horizontal-rule-extension.js";
|
|
10
11
|
import { InlineImageExtension } from "../extensions/inline-image/inline-image-extension.js";
|
|
11
12
|
import { LayoutExtension } from "../extensions/layout/layout-extension.js";
|
|
12
13
|
import { LinkExtension } from "../extensions/link/link-extension.js";
|
|
14
|
+
import { QuoteExtension } from "../extensions/quote/quote-extension.js";
|
|
13
15
|
import { TableExtension } from "../extensions/table/table-extension.js";
|
|
14
16
|
import { VimeoExtension } from "../extensions/vimeo/vimeo-extension.js";
|
|
15
17
|
import { YouTubeExtension } from "../extensions/youtube/youtube-extension.js";
|
|
@@ -21,6 +23,8 @@ function defaultExtensionsArray() {
|
|
|
21
23
|
TabIndentationExtension,
|
|
22
24
|
BylineToolbarExtension,
|
|
23
25
|
BylineFloatingUIExtension,
|
|
26
|
+
HeadingExtension,
|
|
27
|
+
QuoteExtension,
|
|
24
28
|
ListExtension,
|
|
25
29
|
CheckListExtension,
|
|
26
30
|
HorizontalRuleExtension,
|
|
@@ -4,17 +4,17 @@ const defaultEditorLexicalConfig = {
|
|
|
4
4
|
theme: theme
|
|
5
5
|
};
|
|
6
6
|
const DEFAULT_EDITOR_SETTINGS = {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
mode: 'richText',
|
|
8
|
+
markdownShortcuts: false,
|
|
9
|
+
controls: {
|
|
10
|
+
blockFormat: true,
|
|
11
|
+
inlineCode: true,
|
|
12
|
+
undoRedo: true,
|
|
10
13
|
textAlignment: true,
|
|
11
|
-
markdownShortcutPlugin: false,
|
|
12
14
|
markdownToggle: false,
|
|
13
|
-
|
|
14
|
-
textStyle: true,
|
|
15
|
-
inlineCode: true,
|
|
16
|
-
debug: false
|
|
15
|
+
treeView: false
|
|
17
16
|
},
|
|
17
|
+
debug: false,
|
|
18
18
|
inlineImageUploadCollection: 'media',
|
|
19
19
|
placeholderText: 'Enter some rich text...'
|
|
20
20
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type * as React from 'react';
|
|
2
|
-
import type {
|
|
2
|
+
import type { EditorControls, EditorSettings } from './types.js';
|
|
3
3
|
interface ContextType {
|
|
4
|
-
|
|
4
|
+
setControl: (name: keyof EditorControls, value: boolean) => void;
|
|
5
5
|
config: EditorSettings;
|
|
6
6
|
uuid: string;
|
|
7
7
|
}
|
|
@@ -6,31 +6,28 @@ function generateQuickGuid() {
|
|
|
6
6
|
return Math.random().toString(36).substring(2, 12) + Math.random().toString(36).substring(2, 12);
|
|
7
7
|
}
|
|
8
8
|
const Context = /*#__PURE__*/ createContext({
|
|
9
|
-
|
|
9
|
+
setControl: (_name, _value)=>{},
|
|
10
10
|
config: DEFAULT_EDITOR_SETTINGS,
|
|
11
11
|
uuid: generateQuickGuid()
|
|
12
12
|
});
|
|
13
13
|
const EditorConfigContext = ({ children, config: configFromProps })=>{
|
|
14
14
|
const [config, setConfig] = useState(configFromProps ?? DEFAULT_EDITOR_SETTINGS);
|
|
15
15
|
const [uuid] = useState(()=>generateQuickGuid());
|
|
16
|
-
const
|
|
17
|
-
setConfig((config)=>{
|
|
18
|
-
const options = {
|
|
19
|
-
...config.options,
|
|
20
|
-
[option]: value
|
|
21
|
-
};
|
|
22
|
-
return {
|
|
16
|
+
const setControl = useCallback((control, value)=>{
|
|
17
|
+
setConfig((config)=>({
|
|
23
18
|
...config,
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
19
|
+
controls: {
|
|
20
|
+
...config.controls,
|
|
21
|
+
[control]: value
|
|
22
|
+
}
|
|
23
|
+
}));
|
|
27
24
|
}, []);
|
|
28
25
|
const editorContext = useMemo(()=>({
|
|
29
|
-
|
|
26
|
+
setControl,
|
|
30
27
|
config,
|
|
31
28
|
uuid
|
|
32
29
|
}), [
|
|
33
|
-
|
|
30
|
+
setControl,
|
|
34
31
|
config,
|
|
35
32
|
uuid
|
|
36
33
|
]);
|
|
@@ -4,9 +4,9 @@ function resolveEditorConfig(fieldConfig, registeredConfig) {
|
|
|
4
4
|
settings: {
|
|
5
5
|
...registeredConfig.settings,
|
|
6
6
|
...fieldConfig.settings,
|
|
7
|
-
|
|
8
|
-
...registeredConfig.settings.
|
|
9
|
-
...fieldConfig.settings?.
|
|
7
|
+
controls: {
|
|
8
|
+
...registeredConfig.settings.controls,
|
|
9
|
+
...fieldConfig.settings?.controls
|
|
10
10
|
}
|
|
11
11
|
},
|
|
12
12
|
lexical: fieldConfig.lexical ?? registeredConfig.lexical,
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This Source Code is subject to the terms of the Mozilla Public
|
|
3
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
4
|
+
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
5
|
+
*
|
|
6
|
+
* Copyright (c) Infonomic Company Limited
|
|
7
|
+
*/
|
|
8
|
+
import type { AnyLexicalExtensionArgument } from 'lexical';
|
|
9
|
+
import type { ExtensionsList } from './extensions-list.js';
|
|
10
|
+
/**
|
|
11
|
+
* The complete dependency list for an editor root.
|
|
12
|
+
*
|
|
13
|
+
* `editorConfig.extensions` carries the OPTIONAL features a site
|
|
14
|
+
* configures. What an editor actually registers is that list plus
|
|
15
|
+
* mandatory infrastructure, and `CoreNodesExtension` is injected here
|
|
16
|
+
* rather than living in the configurable list because `MarkNode` and
|
|
17
|
+
* `OverflowNode` belong to no switchable feature — `ExtensionsList.remove()`
|
|
18
|
+
* matches by name and accepts the extension object, so an entry in the
|
|
19
|
+
* list could be removed by site code.
|
|
20
|
+
*
|
|
21
|
+
* Everything that builds an editor goes through this: the live editor,
|
|
22
|
+
* the test builders, and the capability manifest. A manifest that
|
|
23
|
+
* disagreed with the runtime about which nodes exist would be worse than
|
|
24
|
+
* no manifest, and this is the seam that keeps them identical.
|
|
25
|
+
*/
|
|
26
|
+
export declare function rootDependencies(extensions: ExtensionsList): AnyLexicalExtensionArgument[];
|
|
@@ -1,15 +1,65 @@
|
|
|
1
1
|
import type { EditorConfig as LexicalEditorConfig } from 'lexical';
|
|
2
2
|
import type { ExtensionsList } from './extensions-list.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Interface preferences — which controls the editing surface shows.
|
|
5
|
+
*
|
|
6
|
+
* Every flag here hides an affordance and NOTHING else. None of them
|
|
7
|
+
* restricts what the field accepts: content still arrives by paste, by
|
|
8
|
+
* Markdown, and from storage regardless. What a field supports is
|
|
9
|
+
* decided by its resolved editor — the extensions it is configured with,
|
|
10
|
+
* everything those pull in transitively, and mandatory core nodes.
|
|
11
|
+
*
|
|
12
|
+
* If you want to stop a structure existing in a field, remove the
|
|
13
|
+
* extension that owns it:
|
|
14
|
+
* `lexicalEditor((c) => c.extensions.remove(builtInExtensions.Heading))`.
|
|
9
15
|
*/
|
|
10
|
-
export
|
|
16
|
+
export interface EditorControls {
|
|
17
|
+
/**
|
|
18
|
+
* Show the block-format dropdown. Its entries are further limited to
|
|
19
|
+
* the formats the editor can actually produce, so this hides a control
|
|
20
|
+
* that is already capability-aware.
|
|
21
|
+
*/
|
|
22
|
+
blockFormat: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Show the inline-code button.
|
|
25
|
+
*
|
|
26
|
+
* This does NOT prevent inline code. Inline code is a `TextNode`
|
|
27
|
+
* format rather than a node type, so it has no node to unregister and
|
|
28
|
+
* still arrives by paste. Node-level configuration cannot constrain
|
|
29
|
+
* it, and neither can this flag.
|
|
30
|
+
*/
|
|
31
|
+
inlineCode: boolean;
|
|
32
|
+
/** Show the undo and redo buttons. Keyboard undo keeps working either way. */
|
|
33
|
+
undoRedo: boolean;
|
|
34
|
+
/** Show the text-alignment controls. */
|
|
35
|
+
textAlignment: boolean;
|
|
36
|
+
/** Show the Markdown source-mode toggle. */
|
|
37
|
+
markdownToggle: boolean;
|
|
38
|
+
/** Show the Lexical tree view beneath the editor. */
|
|
39
|
+
treeView: boolean;
|
|
40
|
+
}
|
|
11
41
|
export interface EditorSettings {
|
|
12
|
-
|
|
42
|
+
/**
|
|
43
|
+
* Which editing surface to mount: `RichTextPlugin` or
|
|
44
|
+
* `PlainTextPlugin`. Plain-text mode also hides the toolbar.
|
|
45
|
+
*
|
|
46
|
+
* This selects the plugin and its input handling — it does NOT change
|
|
47
|
+
* what the field accepts. Switching to `plainText` unregisters no node
|
|
48
|
+
* class, so structural content already in a stored value still loads,
|
|
49
|
+
* and a paste is still governed by the editor's registered nodes. To
|
|
50
|
+
* stop a structure existing in the field, remove the extension that
|
|
51
|
+
* owns it.
|
|
52
|
+
*/
|
|
53
|
+
mode: 'richText' | 'plainText';
|
|
54
|
+
/**
|
|
55
|
+
* Enable Markdown input shortcuts (`# `, `- `, `> ` as you type). The
|
|
56
|
+
* active transformer set follows the editor's registered nodes, so a
|
|
57
|
+
* shortcut for a structure the field does not support never fires.
|
|
58
|
+
*/
|
|
59
|
+
markdownShortcuts: boolean;
|
|
60
|
+
controls: EditorControls;
|
|
61
|
+
/** Render debug output and the tree view. */
|
|
62
|
+
debug: boolean;
|
|
13
63
|
/**
|
|
14
64
|
* Upload collection passed to the inline-image picker. Forwarded to
|
|
15
65
|
* `InlineImageExtension`'s config when the extensions list is built;
|
|
@@ -20,7 +70,10 @@ export interface EditorSettings {
|
|
|
20
70
|
placeholderText: string;
|
|
21
71
|
}
|
|
22
72
|
export interface EditorSettingsOverride {
|
|
23
|
-
|
|
73
|
+
mode?: EditorSettings['mode'];
|
|
74
|
+
markdownShortcuts?: boolean;
|
|
75
|
+
controls?: Partial<EditorControls>;
|
|
76
|
+
debug?: boolean;
|
|
24
77
|
inlineImageUploadCollection?: string;
|
|
25
78
|
placeholderText?: string;
|
|
26
79
|
}
|
|
@@ -1,16 +1,18 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useMemo } from "react";
|
|
4
4
|
import { LexicalExtensionComposer } from "@lexical/react/LexicalExtensionComposer";
|
|
5
5
|
import { defineExtension } from "lexical";
|
|
6
6
|
import { defaultExtensionsList } from "./config/default-extensions.js";
|
|
7
7
|
import { EditorConfigContext } from "./config/editor-config-context.js";
|
|
8
|
+
import { rootDependencies } from "./config/root-dependencies.js";
|
|
8
9
|
import { MarkdownModeProvider } from "./context/markdown-mode-context.js";
|
|
9
10
|
import { SharedHistoryContext } from "./context/shared-history-context.js";
|
|
10
11
|
import { SharedOnChangeContext } from "./context/shared-on-change-context.js";
|
|
11
12
|
import { Editor } from "./editor.js";
|
|
12
13
|
import { InlineImageExtension } from "./extensions/inline-image/inline-image-extension.js";
|
|
13
|
-
import {
|
|
14
|
+
import { AdaptedNotice, UnsupportedContentNotice } from "./normalize/adapted-notice.js";
|
|
15
|
+
import { NormalizationStatusProvider, useNormalizationStatus } from "./normalize/normalization-status.js";
|
|
14
16
|
function EditorContext(props) {
|
|
15
17
|
const { composerKey, editorConfig, onChange, readOnly, value, beforeEditor, afterEditor, children } = props;
|
|
16
18
|
const editable = true !== readOnly;
|
|
@@ -20,16 +22,12 @@ function EditorContext(props) {
|
|
|
20
22
|
if (configured.has(InlineImageExtension)) configured.configure(InlineImageExtension, {
|
|
21
23
|
collection: editorConfig.settings.inlineImageUploadCollection
|
|
22
24
|
});
|
|
23
|
-
const dependencies = configured
|
|
25
|
+
const dependencies = rootDependencies(configured);
|
|
24
26
|
return defineExtension({
|
|
25
27
|
name: '[root]',
|
|
26
28
|
namespace: editorConfig.lexical.namespace,
|
|
27
|
-
nodes: [
|
|
28
|
-
...Nodes
|
|
29
|
-
],
|
|
30
29
|
theme: editorConfig.lexical.theme,
|
|
31
30
|
editable,
|
|
32
|
-
$initialEditorState: null != value ? JSON.stringify(value) : void 0,
|
|
33
31
|
onError: (error)=>{
|
|
34
32
|
throw error;
|
|
35
33
|
},
|
|
@@ -38,29 +36,48 @@ function EditorContext(props) {
|
|
|
38
36
|
}, []);
|
|
39
37
|
return /*#__PURE__*/ jsx(EditorConfigContext, {
|
|
40
38
|
config: editorConfig.settings,
|
|
41
|
-
children: /*#__PURE__*/ jsx(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
children: /*#__PURE__*/ jsx(
|
|
45
|
-
children: /*#__PURE__*/ jsx(
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
]
|
|
59
|
-
})
|
|
60
|
-
}, composerKey + editable)
|
|
39
|
+
children: /*#__PURE__*/ jsx(NormalizationStatusProvider, {
|
|
40
|
+
children: /*#__PURE__*/ jsx(SharedOnChangeContext, {
|
|
41
|
+
onChange: onChange,
|
|
42
|
+
children: /*#__PURE__*/ jsx(SharedHistoryContext, {
|
|
43
|
+
children: /*#__PURE__*/ jsx(MarkdownModeProvider, {
|
|
44
|
+
children: /*#__PURE__*/ jsx(LexicalExtensionComposer, {
|
|
45
|
+
extension: rootExtension,
|
|
46
|
+
contentEditable: null,
|
|
47
|
+
children: /*#__PURE__*/ jsx(EditorShell, {
|
|
48
|
+
beforeEditor: beforeEditor,
|
|
49
|
+
afterEditor: afterEditor,
|
|
50
|
+
minHeight: props.minHeight,
|
|
51
|
+
maxHeight: props.maxHeight,
|
|
52
|
+
children: children
|
|
53
|
+
})
|
|
54
|
+
}, composerKey + editable)
|
|
55
|
+
})
|
|
61
56
|
})
|
|
62
57
|
})
|
|
63
58
|
})
|
|
64
59
|
});
|
|
65
60
|
}
|
|
61
|
+
function EditorShell({ beforeEditor, afterEditor, minHeight, maxHeight, children }) {
|
|
62
|
+
const { status } = useNormalizationStatus();
|
|
63
|
+
return /*#__PURE__*/ jsxs("div", {
|
|
64
|
+
className: "editor-shell",
|
|
65
|
+
children: [
|
|
66
|
+
'refused' === status.kind ? /*#__PURE__*/ jsx(UnsupportedContentNotice, {
|
|
67
|
+
unsupportedTypes: status.unsupportedTypes
|
|
68
|
+
}) : /*#__PURE__*/ jsxs(Fragment, {
|
|
69
|
+
children: [
|
|
70
|
+
'adapted' === status.kind && /*#__PURE__*/ jsx(AdaptedNotice, {}),
|
|
71
|
+
beforeEditor,
|
|
72
|
+
/*#__PURE__*/ jsx(Editor, {
|
|
73
|
+
minHeight: minHeight,
|
|
74
|
+
maxHeight: maxHeight
|
|
75
|
+
}),
|
|
76
|
+
afterEditor
|
|
77
|
+
]
|
|
78
|
+
}),
|
|
79
|
+
children
|
|
80
|
+
]
|
|
81
|
+
});
|
|
82
|
+
}
|
|
66
83
|
export { EditorContext };
|