@byline/richtext-lexical 5.2.0 → 6.0.1

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.
Files changed (123) hide show
  1. package/dist/config.d.ts +1 -1
  2. package/dist/field/apply-value-plugin.js +31 -6
  3. package/dist/field/capabilities/filter-transformers.d.ts +29 -0
  4. package/dist/field/capabilities/filter-transformers.js +8 -0
  5. package/dist/field/capabilities/registered-node-types.d.ts +29 -0
  6. package/dist/field/capabilities/registered-node-types.js +7 -0
  7. package/dist/field/config/built-in-extension-names.d.ts +42 -0
  8. package/dist/field/config/built-in-extension-names.js +12 -2
  9. package/dist/field/config/default-extensions.js +4 -0
  10. package/dist/field/config/default.js +8 -8
  11. package/dist/field/config/editor-config-context.d.ts +2 -2
  12. package/dist/field/config/editor-config-context.js +10 -13
  13. package/dist/field/config/resolve-editor-config.js +3 -3
  14. package/dist/field/config/root-dependencies.d.ts +26 -0
  15. package/dist/field/config/root-dependencies.js +8 -0
  16. package/dist/field/config/types.d.ts +61 -8
  17. package/dist/field/editor-context.js +44 -27
  18. package/dist/field/editor.js +15 -8
  19. package/dist/field/extensions/core-nodes/core-nodes-extension.d.ts +24 -0
  20. package/dist/field/extensions/core-nodes/core-nodes-extension.js +11 -0
  21. package/dist/field/extensions/heading/heading-extension.d.ts +20 -0
  22. package/dist/field/extensions/heading/heading-extension.js +9 -0
  23. package/dist/field/extensions/quote/quote-extension.d.ts +16 -0
  24. package/dist/field/extensions/quote/quote-extension.js +9 -0
  25. package/dist/field/hooks/use-markdown-toggle.d.ts +15 -0
  26. package/dist/field/hooks/use-markdown-toggle.js +12 -4
  27. package/dist/field/hooks/use-platform-modifier.d.ts +16 -0
  28. package/dist/field/hooks/use-platform-modifier.js +8 -0
  29. package/dist/field/markdown/transformers.d.ts +1 -0
  30. package/dist/field/markdown/transformers.js +5 -4
  31. package/dist/field/nodes/index.d.ts +11 -1
  32. package/dist/field/nodes/index.js +2 -2
  33. package/dist/field/normalize/adapted-notice.css +34 -0
  34. package/dist/field/normalize/adapted-notice.d.ts +29 -0
  35. package/dist/field/normalize/adapted-notice.js +35 -0
  36. package/dist/field/normalize/content-labels.d.ts +15 -0
  37. package/dist/field/normalize/content-labels.js +19 -0
  38. package/dist/field/normalize/declared-conversions.d.ts +131 -0
  39. package/dist/field/normalize/declared-conversions.js +93 -0
  40. package/dist/field/normalize/normalization-status.d.ts +33 -0
  41. package/dist/field/normalize/normalization-status.js +28 -0
  42. package/dist/field/normalize/normalize-value.d.ts +38 -0
  43. package/dist/field/normalize/normalize-value.js +132 -0
  44. package/dist/field/normalize/use-notice-text.d.ts +11 -0
  45. package/dist/field/normalize/use-notice-text.js +34 -0
  46. package/dist/field/plugins/toolbar-plugin/index.d.ts +23 -0
  47. package/dist/field/plugins/toolbar-plugin/index.js +104 -78
  48. package/dist/field/test-support/build-test-editor.js +49 -0
  49. package/dist/field/test-support/capture-editor.js +7 -0
  50. package/dist/field/test-support/frame-control.js +40 -0
  51. package/dist/field/test-support/jsdom-setup.js +21 -0
  52. package/dist/index.d.ts +7 -1
  53. package/dist/index.js +6 -1
  54. package/dist/scan/capability-manifest.d.ts +30 -0
  55. package/dist/scan/capability-manifest.js +43 -0
  56. package/dist/scan/scan-documents.d.ts +29 -0
  57. package/dist/scan/scan-documents.js +39 -0
  58. package/dist/scan/types.d.ts +63 -0
  59. package/dist/scan/types.js +1 -0
  60. package/dist/scan.d.ts +25 -0
  61. package/dist/scan.js +1 -0
  62. package/package.json +14 -5
  63. package/src/config.ts +1 -1
  64. package/src/field/apply-value-plugin.tsx +71 -5
  65. package/src/field/capabilities/filter-transformers.test.tsx +71 -0
  66. package/src/field/capabilities/filter-transformers.ts +42 -0
  67. package/src/field/capabilities/markdown-behaviour.test.tsx +188 -0
  68. package/src/field/capabilities/no-blanket-registration.test.tsx +86 -0
  69. package/src/field/capabilities/registered-node-types.test.tsx +29 -0
  70. package/src/field/capabilities/registered-node-types.ts +36 -0
  71. package/src/field/config/built-in-extension-names.test.node.ts +38 -14
  72. package/src/field/config/built-in-extension-names.ts +29 -1
  73. package/src/field/config/default-extensions.ts +6 -0
  74. package/src/field/config/default.ts +8 -8
  75. package/src/field/config/editor-config-context.tsx +14 -9
  76. package/src/field/config/list-removal.test.tsx +44 -0
  77. package/src/field/config/resolve-editor-config.test.node.ts +27 -6
  78. package/src/field/config/resolve-editor-config.ts +3 -3
  79. package/src/field/config/root-dependencies.ts +32 -0
  80. package/src/field/config/types.ts +61 -17
  81. package/src/field/editor-component.test.tsx +18 -3
  82. package/src/field/editor-context.regression.test.tsx +124 -0
  83. package/src/field/editor-context.tsx +90 -22
  84. package/src/field/editor.tsx +22 -12
  85. package/src/field/extensions/core-nodes/core-nodes-extension.test.tsx +13 -0
  86. package/src/field/extensions/core-nodes/core-nodes-extension.ts +32 -0
  87. package/src/field/extensions/heading/heading-extension.test.tsx +32 -0
  88. package/src/field/extensions/heading/heading-extension.ts +27 -0
  89. package/src/field/extensions/quote/quote-extension.ts +23 -0
  90. package/src/field/hooks/markdown-source-mode.test.tsx +221 -0
  91. package/src/field/hooks/use-markdown-toggle.ts +39 -4
  92. package/src/field/hooks/use-platform-modifier.test.tsx +81 -0
  93. package/src/field/hooks/use-platform-modifier.ts +41 -0
  94. package/src/field/markdown/transformers.ts +18 -4
  95. package/src/field/nodes/index.ts +11 -1
  96. package/src/field/normalize/adapted-notice.css +52 -0
  97. package/src/field/normalize/adapted-notice.tsx +63 -0
  98. package/src/field/normalize/clipboard-equivalence.test.tsx +330 -0
  99. package/src/field/normalize/content-labels.ts +44 -0
  100. package/src/field/normalize/declared-conversions.test.node.ts +67 -0
  101. package/src/field/normalize/declared-conversions.ts +160 -0
  102. package/src/field/normalize/emission-gate.test.tsx +302 -0
  103. package/src/field/normalize/normalization-status.tsx +48 -0
  104. package/src/field/normalize/normalize-load.test.tsx +371 -0
  105. package/src/field/normalize/normalize-value.test.node.ts +530 -0
  106. package/src/field/normalize/normalize-value.ts +180 -0
  107. package/src/field/normalize/notice-i18n.test.tsx +128 -0
  108. package/src/field/normalize/use-notice-text.ts +82 -0
  109. package/src/field/normalize/wiring.test.tsx +348 -0
  110. package/src/field/plugins/toolbar-plugin/block-format-capabilities.test.tsx +57 -0
  111. package/src/field/plugins/toolbar-plugin/index.tsx +131 -68
  112. package/src/field/test-support/build-test-editor.test.tsx +17 -0
  113. package/src/field/test-support/build-test-editor.ts +99 -0
  114. package/src/field/test-support/capture-editor.tsx +20 -0
  115. package/src/field/test-support/frame-control.ts +91 -0
  116. package/src/field/test-support/jsdom-setup.ts +41 -0
  117. package/src/index.ts +12 -1
  118. package/src/scan/capability-manifest.test.tsx +82 -0
  119. package/src/scan/capability-manifest.ts +81 -0
  120. package/src/scan/scan-documents.test.node.ts +125 -0
  121. package/src/scan/scan-documents.ts +78 -0
  122. package/src/scan/types.ts +66 -0
  123. package/src/scan.ts +27 -0
@@ -1,13 +1,16 @@
1
1
  "use client";
2
2
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
3
  import { memo, useCallback, useEffect, useMemo, useState } from "react";
4
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
4
5
  import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
5
6
  import { HistoryPlugin } from "@lexical/react/LexicalHistoryPlugin";
6
7
  import { MarkdownShortcutPlugin } from "@lexical/react/LexicalMarkdownShortcutPlugin";
7
8
  import { OnChangePlugin } from "@lexical/react/LexicalOnChangePlugin";
8
9
  import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
9
10
  import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
10
- import { useExtensionDependency, useOptionalExtensionDependency } from "@lexical/react/useExtensionComponent";
11
+ import { useExtensionDependency } from "@lexical/react/useExtensionComponent";
12
+ import { transformersFor } from "./capabilities/filter-transformers.js";
13
+ import { supportsNodeType } from "./capabilities/registered-node-types.js";
11
14
  import { useEditorConfig } from "./config/editor-config-context.js";
12
15
  import { ContentEditable } from "./content-editable.js";
13
16
  import { useMarkdownMode } from "./context/markdown-mode-context.js";
@@ -15,7 +18,6 @@ import { useSharedHistoryContext } from "./context/shared-history-context.js";
15
18
  import { useSharedOnChange } from "./context/shared-on-change-context.js";
16
19
  import { Debug } from "./debug.js";
17
20
  import { BylineFloatingUIExtension, selectFloatingUIItems } from "./extensions/byline-floating-ui/byline-floating-ui-extension.js";
18
- import { TableExtension } from "./extensions/table/table-extension.js";
19
21
  import { BYLINE_TRANSFORMERS } from "./markdown/transformers.js";
20
22
  import { TablePlugin } from "./plugins/table-plugin/index.js";
21
23
  import { ToolbarPlugin } from "./plugins/toolbar-plugin/index.js";
@@ -33,8 +35,13 @@ const editor_Editor = /*#__PURE__*/ memo(function({ minHeight, maxHeight }) {
33
35
  const { onChange } = useSharedOnChange();
34
36
  const { historyState } = useSharedHistoryContext();
35
37
  const { markdownModeRef } = useMarkdownMode();
36
- const { config: { options: { debug, richText, showTreeView, markdownShortcutPlugin }, placeholderText } } = useEditorConfig();
37
- const hasTableExtension = void 0 !== useOptionalExtensionDependency(TableExtension);
38
+ const { config: { mode, markdownShortcuts, controls: { treeView }, debug, placeholderText } } = useEditorConfig();
39
+ const richText = 'richText' === mode;
40
+ const [editor] = useLexicalComposerContext();
41
+ const hasTableNode = supportsNodeType(editor, 'table');
42
+ const activeTransformers = useMemo(()=>transformersFor(editor, BYLINE_TRANSFORMERS), [
43
+ editor
44
+ ]);
38
45
  const floatingUIItems = selectFloatingUIItems(useExtensionDependency(BylineFloatingUIExtension).config.items);
39
46
  const onRef = useCallback((_floatingAnchorElem)=>{
40
47
  if (null != _floatingAnchorElem) setFloatingAnchorElem(_floatingAnchorElem);
@@ -84,10 +91,10 @@ const editor_Editor = /*#__PURE__*/ memo(function({ minHeight, maxHeight }) {
84
91
  ]);
85
92
  const content = /*#__PURE__*/ jsxs(Fragment, {
86
93
  children: [
87
- hasTableExtension && /*#__PURE__*/ jsx(TablePlugin, {}),
94
+ hasTableNode && /*#__PURE__*/ jsx(TablePlugin, {}),
88
95
  richText && /*#__PURE__*/ jsx(ToolbarPlugin, {}),
89
96
  /*#__PURE__*/ jsxs("div", {
90
- className: `editor-container ${showTreeView ? 'tree-view' : ''} ${!richText ? 'plain-text' : ''}`,
97
+ className: `editor-container ${treeView ? 'tree-view' : ''} ${!richText ? 'plain-text' : ''}`,
91
98
  children: [
92
99
  /*#__PURE__*/ jsx(OnChangePlugin, {
93
100
  ignoreSelectionChange: true,
@@ -111,8 +118,8 @@ const editor_Editor = /*#__PURE__*/ memo(function({ minHeight, maxHeight }) {
111
118
  /*#__PURE__*/ jsx(HistoryPlugin, {
112
119
  externalHistoryState: historyState
113
120
  }),
114
- markdownShortcutPlugin && /*#__PURE__*/ jsx(MarkdownShortcutPlugin, {
115
- transformers: BYLINE_TRANSFORMERS
121
+ markdownShortcuts && /*#__PURE__*/ jsx(MarkdownShortcutPlugin, {
122
+ transformers: activeTransformers
116
123
  }),
117
124
  null != floatingAnchorElem && !isSmallWidthViewport && floatingUIItems.map(({ id, Component })=>/*#__PURE__*/ jsx(Component, {
118
125
  anchorElem: floatingAnchorElem
@@ -0,0 +1,24 @@
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
+ /**
9
+ * Owns the node classes that belong to no switchable feature.
10
+ *
11
+ * `MarkNode` and `OverflowNode` ship without an upstream extension, and
12
+ * neither corresponds to something a field can turn off. Without an
13
+ * owner both would lose their only registration once the editor root
14
+ * stopped registering a blanket node list.
15
+ *
16
+ * `EditorContext` injects this extension directly into the root's
17
+ * dependencies, outside `editorConfig.extensions`. Leaving it out of
18
+ * `builtInExtensions` would NOT have made it non-removable: this module
19
+ * is publicly exported and `ExtensionsList.remove()` accepts an
20
+ * extension object as readily as a name string, so site code could have
21
+ * removed it from the configurable list. Injection at the root is what
22
+ * enforces its presence.
23
+ */
24
+ export declare const CoreNodesExtension: import("lexical").LexicalExtension<import("lexical").ExtensionConfigBase, "@byline/richtext-lexical/CoreNodes", unknown, unknown>;
@@ -0,0 +1,11 @@
1
+ import { MarkNode } from "@lexical/mark";
2
+ import { OverflowNode } from "@lexical/overflow";
3
+ import { defineExtension } from "lexical";
4
+ const CoreNodesExtension = defineExtension({
5
+ name: '@byline/richtext-lexical/CoreNodes',
6
+ nodes: ()=>[
7
+ MarkNode,
8
+ OverflowNode
9
+ ]
10
+ });
11
+ export { CoreNodesExtension };
@@ -0,0 +1,20 @@
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
+ /**
9
+ * Owns `HeadingNode`.
10
+ *
11
+ * Byline deliberately does not adopt `@lexical/rich-text`'s
12
+ * `RichTextExtension` for this. That extension binds heading and quote
13
+ * registration to general rich-text behaviour, so adopting it would
14
+ * register headings in every rich-text field — and a field configured
15
+ * without headings would still accept one from a paste, which is the
16
+ * defect this package had. Owning the node here keeps headings
17
+ * removable on their own, independently of quotes and of rich-text
18
+ * behaviour itself.
19
+ */
20
+ export declare const HeadingExtension: import("lexical").LexicalExtension<import("lexical").ExtensionConfigBase, "@byline/richtext-lexical/Heading", unknown, unknown>;
@@ -0,0 +1,9 @@
1
+ import { HeadingNode } from "@lexical/rich-text";
2
+ import { defineExtension } from "lexical";
3
+ const HeadingExtension = defineExtension({
4
+ name: '@byline/richtext-lexical/Heading',
5
+ nodes: ()=>[
6
+ HeadingNode
7
+ ]
8
+ });
9
+ export { HeadingExtension };
@@ -0,0 +1,16 @@
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
+ /**
9
+ * Owns `QuoteNode`.
10
+ *
11
+ * Separate from {@link HeadingExtension} on purpose: a field may want
12
+ * headings without block quotes, or neither. See the heading extension
13
+ * for why Byline owns these nodes rather than adopting
14
+ * `RichTextExtension`.
15
+ */
16
+ export declare const QuoteExtension: import("lexical").LexicalExtension<import("lexical").ExtensionConfigBase, "@byline/richtext-lexical/Quote", unknown, unknown>;
@@ -0,0 +1,9 @@
1
+ import { QuoteNode } from "@lexical/rich-text";
2
+ import { defineExtension } from "lexical";
3
+ const QuoteExtension = defineExtension({
4
+ name: '@byline/richtext-lexical/Quote',
5
+ nodes: ()=>[
6
+ QuoteNode
7
+ ]
8
+ });
9
+ export { QuoteExtension };
@@ -1,3 +1,4 @@
1
+ import { type LexicalEditor } from 'lexical';
1
2
  /**
2
3
  * Document-level "view as markdown source" toggle for a single editor.
3
4
  *
@@ -13,6 +14,20 @@
13
14
  * - Edits made in markdown produce a single conversion back to rich
14
15
  * nodes on exit, emitting one field value change → **one patch**.
15
16
  */
17
+ /**
18
+ * Whether this editor can offer Markdown source mode.
19
+ *
20
+ * Source mode holds the Markdown text in a `CodeNode`, so it needs that
21
+ * class registered — `$createCodeNode()` throws
22
+ * "Attempted to create node CodeNode that was not configured to be used
23
+ * on the editor" otherwise. A field with the code-highlight extension
24
+ * removed therefore cannot offer the toggle.
25
+ *
26
+ * The fix is to withhold the control, NOT to register `CodeNode` for the
27
+ * toggle's benefit: registering it would let code blocks back in through
28
+ * paste and storage, which is the acceptance hole this work closed.
29
+ */
30
+ export declare function canUseMarkdownSourceMode(editor: LexicalEditor): boolean;
16
31
  export declare function useMarkdownToggle(): {
17
32
  isMarkdown: boolean;
18
33
  toggleMarkdown: () => void;
@@ -1,9 +1,10 @@
1
1
  "use client";
2
2
  import { useCallback, useEffect, useRef } from "react";
3
- import { $createCodeNode, $isCodeNode } from "@lexical/code";
3
+ import { $createCodeNode, $isCodeNode, CodeNode } from "@lexical/code";
4
4
  import { $convertFromMarkdownString, $convertToMarkdownString } from "@lexical/markdown";
5
5
  import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
6
6
  import { $getRoot, RootNode } from "lexical";
7
+ import { transformersFor } from "../capabilities/filter-transformers.js";
7
8
  import { APPLY_VALUE_TAG } from "../constants.js";
8
9
  import { useMarkdownMode } from "../context/markdown-mode-context.js";
9
10
  import { BYLINE_TRANSFORMERS } from "../markdown/transformers.js";
@@ -11,6 +12,11 @@ const MARKDOWN_LANGUAGE = 'markdown';
11
12
  function normalize(markdown) {
12
13
  return markdown.replace(/\n+$/, '');
13
14
  }
15
+ function canUseMarkdownSourceMode(editor) {
16
+ return editor.hasNodes([
17
+ CodeNode
18
+ ]);
19
+ }
14
20
  function useMarkdownToggle() {
15
21
  const [editor] = useLexicalComposerContext();
16
22
  const { isMarkdown, setIsMarkdown, markdownModeRef } = useMarkdownMode();
@@ -18,6 +24,7 @@ function useMarkdownToggle() {
18
24
  const originalMarkdownRef = useRef('');
19
25
  const unregisterTransformRef = useRef(null);
20
26
  const registerRootGuard = useCallback(()=>{
27
+ if (!canUseMarkdownSourceMode(editor)) return;
21
28
  unregisterTransformRef.current?.();
22
29
  unregisterTransformRef.current = editor.registerNodeTransform(RootNode, (rootNode)=>{
23
30
  let codeNode = rootNode.getChildren().find($isCodeNode);
@@ -38,10 +45,11 @@ function useMarkdownToggle() {
38
45
  unregisterTransformRef.current = null;
39
46
  }, []);
40
47
  const enterMarkdown = useCallback(()=>{
48
+ if (!canUseMarkdownSourceMode(editor)) return;
41
49
  originalEditorStateRef.current = editor.getEditorState();
42
50
  markdownModeRef.current = true;
43
51
  editor.update(()=>{
44
- const markdown = $convertToMarkdownString(BYLINE_TRANSFORMERS, void 0, true);
52
+ const markdown = $convertToMarkdownString(transformersFor(editor, BYLINE_TRANSFORMERS), void 0, true);
45
53
  originalMarkdownRef.current = markdown;
46
54
  const codeNode = $createCodeNode(MARKDOWN_LANGUAGE);
47
55
  $getRoot().clear().append(codeNode);
@@ -66,7 +74,7 @@ function useMarkdownToggle() {
66
74
  if (edited || null == originalEditorStateRef.current) {
67
75
  markdownModeRef.current = false;
68
76
  editor.update(()=>{
69
- $convertFromMarkdownString(currentMarkdown, BYLINE_TRANSFORMERS, void 0, true);
77
+ $convertFromMarkdownString(currentMarkdown, transformersFor(editor, BYLINE_TRANSFORMERS), void 0, true);
70
78
  });
71
79
  } else {
72
80
  editor.setEditorState(originalEditorStateRef.current, {
@@ -99,4 +107,4 @@ function useMarkdownToggle() {
99
107
  toggleMarkdown
100
108
  };
101
109
  }
102
- export { useMarkdownToggle };
110
+ export { canUseMarkdownSourceMode, useMarkdownToggle };
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Whether to label keyboard shortcuts the Apple way.
3
+ *
4
+ * `IS_APPLE` is resolved at module scope from `navigator.platform`, which
5
+ * is false on the server and true on a Mac client. Reading it directly
6
+ * during render therefore produced markup the server and client
7
+ * disagreed about — React reported a hydration mismatch on the toolbar's
8
+ * `title` and `aria-label` attributes and, as it warns, did not patch
9
+ * them up: a Mac user could be shown `Ctrl+B`, and a screen reader could
10
+ * announce it.
11
+ *
12
+ * `useSyncExternalStore` uses its server snapshot for the initial client
13
+ * render as well as for SSR, so the first client render matches the
14
+ * server exactly and the real platform is adopted immediately after.
15
+ */
16
+ export declare function useIsApplePlatform(): boolean;
@@ -0,0 +1,8 @@
1
+ "use client";
2
+ import { useSyncExternalStore } from "react";
3
+ import { IS_APPLE } from "../shared/environment.js";
4
+ const subscribe = ()=>()=>{};
5
+ function useIsApplePlatform() {
6
+ return useSyncExternalStore(subscribe, ()=>IS_APPLE, ()=>false);
7
+ }
8
+ export { useIsApplePlatform };
@@ -20,6 +20,7 @@
20
20
  */
21
21
  import { type ElementTransformer, type MultilineElementTransformer, type Transformer } from '@lexical/markdown';
22
22
  export declare const TABLE: ElementTransformer;
23
+ export declare const ADMONITION_BODY_TRANSFORMERS: Array<Transformer>;
23
24
  export declare const ADMONITION: MultilineElementTransformer;
24
25
  /**
25
26
  * Stock transformers plus Byline's custom-node handlers. Self-referenced by
@@ -1,6 +1,7 @@
1
1
  import { $convertFromMarkdownString, $convertToMarkdownString, LINK, TEXT_FORMAT_TRANSFORMERS, TRANSFORMERS } from "@lexical/markdown";
2
2
  import { $createTableCellNode, $createTableNode, $createTableRowNode, $isTableCellNode, $isTableNode, $isTableRowNode, TableCellHeaderStates, TableCellNode, TableNode, TableRowNode } from "@lexical/table";
3
- import { $createParagraphNode, $isParagraphNode, $isTextNode } from "lexical";
3
+ import { $createParagraphNode, $getEditor, $isParagraphNode, $isTextNode } from "lexical";
4
+ import { transformersFor } from "../capabilities/filter-transformers.js";
4
5
  import { $createAdmonitionNode, $isAdmonitionNode, AdmonitionNode } from "../extensions/admonition/admonition-node.js";
5
6
  const TABLE_ROW_REG_EXP = /^(?:\|)(.+)(?:\|)\s?$/;
6
7
  const TABLE_ROW_DIVIDER_REG_EXP = /^(\| ?:?-+:? ?)+\|?\s?$/;
@@ -86,7 +87,7 @@ function getTableColumnsSize(table) {
86
87
  const $createTableCell = (textContent)=>{
87
88
  const content = textContent.replace(/\\n/g, '\n');
88
89
  const cell = $createTableCellNode(TableCellHeaderStates.NO_STATUS);
89
- $convertFromMarkdownString(content, BYLINE_TRANSFORMERS, cell);
90
+ $convertFromMarkdownString(content, transformersFor($getEditor(), BYLINE_TRANSFORMERS), cell);
90
91
  return cell;
91
92
  };
92
93
  const mapToTableCells = (textContent)=>{
@@ -135,7 +136,7 @@ const ADMONITION = {
135
136
  if (null != children) node.append(...children);
136
137
  else if (null != linesInBetween) {
137
138
  const body = linesInBetween.join('\n').trim();
138
- if (body) $convertFromMarkdownString(body, ADMONITION_BODY_TRANSFORMERS, node);
139
+ if (body) $convertFromMarkdownString(body, transformersFor($getEditor(), ADMONITION_BODY_TRANSFORMERS), node);
139
140
  }
140
141
  if (0 === node.getChildrenSize()) node.append($createParagraphNode());
141
142
  rootNode.append(node);
@@ -148,4 +149,4 @@ const BYLINE_TRANSFORMERS = [
148
149
  ADMONITION,
149
150
  ...TRANSFORMERS
150
151
  ];
151
- export { ADMONITION, BYLINE_TRANSFORMERS, TABLE };
152
+ export { ADMONITION, ADMONITION_BODY_TRANSFORMERS, BYLINE_TRANSFORMERS, TABLE };
@@ -6,4 +6,14 @@
6
6
  * Copyright (c) Infonomic Company Limited
7
7
  */
8
8
  import type { Klass, LexicalNode } from 'lexical';
9
- export declare const Nodes: Array<Klass<LexicalNode>>;
9
+ /**
10
+ * Every node class Byline knows how to READ.
11
+ *
12
+ * This is not a registration list — registration belongs to the
13
+ * extension that owns each node, so that removing an extension removes
14
+ * what the field accepts as well as what it offers. This array is the
15
+ * vocabulary the normalizer and the capability manifest work from, so a
16
+ * stored document can still be inspected by a field that supports far
17
+ * less than the document contains.
18
+ */
19
+ export declare const READABLE_NODES: Array<Klass<LexicalNode>>;
@@ -12,7 +12,7 @@ import { LayoutItemNode } from "../extensions/layout/layout-item-node.js";
12
12
  import { AutoLinkNode, LinkNode } from "../extensions/link/index.js";
13
13
  import { VimeoNode } from "../extensions/vimeo/vimeo-node.js";
14
14
  import { YouTubeNode } from "../extensions/youtube/youtube-node.js";
15
- const Nodes = [
15
+ const READABLE_NODES = [
16
16
  HeadingNode,
17
17
  ListNode,
18
18
  ListItemNode,
@@ -34,4 +34,4 @@ const Nodes = [
34
34
  LayoutContainerNode,
35
35
  LayoutItemNode
36
36
  ];
37
- export { Nodes };
37
+ export { READABLE_NODES };
@@ -0,0 +1,34 @@
1
+ @layer base, byline-base, theme, byline-theme, byline-functional, byline-typography, components;
2
+
3
+ @layer byline-components {
4
+ .byline-richtext-notice {
5
+ border-radius: var(--border-radius-sm, 4px);
6
+ font-size: var(--font-size-sm, .8125rem);
7
+ margin-bottom: 8px;
8
+ padding: 8px 10px;
9
+ line-height: 1.4;
10
+ }
11
+
12
+ .byline-richtext-notice p {
13
+ margin: 0;
14
+ }
15
+
16
+ .byline-richtext-notice p + p {
17
+ margin-top: 4px;
18
+ }
19
+
20
+ .byline-richtext-notice--adapted {
21
+ background-color: var(--fill-warning-weak);
22
+ border: 1px solid var(--stroke-warning);
23
+ color: var(--text-on-warning-weak);
24
+ }
25
+
26
+ .byline-richtext-notice--refused {
27
+ background-color: var(--fill-danger-weak);
28
+ border: 1px solid var(--stroke-danger);
29
+ color: var(--text-on-danger-weak);
30
+ }
31
+ }
32
+
33
+ @layer utilities, byline-utilities;
34
+
@@ -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 * as React from 'react';
9
+ import './adapted-notice.css';
10
+ /**
11
+ * Shown when stored content held a structure this field no longer
12
+ * supports and it was adapted for editing.
13
+ *
14
+ * Inline and non-blocking: the content is editable, and the adaptation
15
+ * only reaches storage if the reader actually saves an edit.
16
+ */
17
+ export declare function AdaptedNotice(): React.JSX.Element;
18
+ /**
19
+ * Shown when stored content holds something with no safe conversion —
20
+ * an image, an embed, or a node type this build does not know.
21
+ *
22
+ * Those carry information no structural rewrite can preserve: an inline
23
+ * image has a media relation and a caption held in a nested editor.
24
+ * Discarding them silently would be worse than declining to edit, so the
25
+ * field opens read-only rather than adapting.
26
+ */
27
+ export declare function UnsupportedContentNotice({ unsupportedTypes, }: {
28
+ unsupportedTypes: string[];
29
+ }): React.JSX.Element;
@@ -0,0 +1,35 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { contentLabelKey, joinLabels } from "./content-labels.js";
4
+ import { useNoticeText } from "./use-notice-text.js";
5
+ import "./adapted-notice.css";
6
+ function AdaptedNotice() {
7
+ const { t } = useNoticeText();
8
+ return /*#__PURE__*/ jsx("div", {
9
+ className: "byline-richtext-notice byline-richtext-notice--adapted",
10
+ role: "status",
11
+ children: t('richtext.adapted.notice')
12
+ });
13
+ }
14
+ function UnsupportedContentNotice({ unsupportedTypes }) {
15
+ const { t, locale } = useNoticeText();
16
+ const labels = unsupportedTypes.map((type)=>{
17
+ const key = contentLabelKey(type);
18
+ return null != key ? t(key) : type;
19
+ });
20
+ return /*#__PURE__*/ jsxs("div", {
21
+ className: "byline-richtext-notice byline-richtext-notice--refused",
22
+ role: "alert",
23
+ children: [
24
+ /*#__PURE__*/ jsx("p", {
25
+ children: t('richtext.unsupported.notice', {
26
+ items: joinLabels(labels, locale)
27
+ })
28
+ }),
29
+ /*#__PURE__*/ jsx("p", {
30
+ children: t('richtext.unsupported.action')
31
+ })
32
+ ]
33
+ });
34
+ }
35
+ export { AdaptedNotice, UnsupportedContentNotice };
@@ -0,0 +1,15 @@
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
+ export declare function contentLabelKey(type: string): string | undefined;
9
+ /**
10
+ * Join labels the way the active locale writes a list.
11
+ *
12
+ * `Intl.ListFormat` is not available in every runtime this may render
13
+ * in, so fall back to a comma join rather than throwing over a notice.
14
+ */
15
+ export declare function joinLabels(labels: string[], locale: string): string;
@@ -0,0 +1,19 @@
1
+ const CONTENT_LABEL_KEYS = {
2
+ 'inline-image': 'richtext.content.image',
3
+ youtube: 'richtext.content.youtube',
4
+ vimeo: 'richtext.content.vimeo'
5
+ };
6
+ function contentLabelKey(type) {
7
+ return CONTENT_LABEL_KEYS[type];
8
+ }
9
+ function joinLabels(labels, locale) {
10
+ try {
11
+ return new Intl.ListFormat(locale, {
12
+ style: 'long',
13
+ type: 'conjunction'
14
+ }).format(labels);
15
+ } catch {
16
+ return labels.join(', ');
17
+ }
18
+ }
19
+ export { contentLabelKey, joinLabels };
@@ -0,0 +1,131 @@
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
+ /** A serialized Lexical node, as stored. */
9
+ export interface SerializedNode {
10
+ type: string;
11
+ children?: SerializedNode[];
12
+ [key: string]: unknown;
13
+ }
14
+ /**
15
+ * What a convertible structure CONTAINS, which is what decides a
16
+ * structurally valid replacement for it.
17
+ *
18
+ * - `to-paragraph` — a block holding inline content. Becomes exactly one
19
+ * paragraph, so two adjacent headings stay two paragraphs and an empty
20
+ * heading stays an empty paragraph instead of vanishing.
21
+ * - `lift-blocks` — a block holding blocks. Its children are lifted in
22
+ * place, already block-level and already valid where the parent sat.
23
+ * - `unwrap-inline` — inline content. Its children are lifted inline and
24
+ * stay inside the paragraph that held them.
25
+ * - `to-text` — a text-carrying leaf whose class is unsupported, such as
26
+ * a syntax-highlighted code token. Becomes a plain text node with its
27
+ * text and inline formats intact.
28
+ * - `drop` — removed entirely; neighbours are untouched.
29
+ */
30
+ export type ConversionKind = 'to-paragraph' | 'lift-blocks' | 'unwrap-inline' | 'to-text' | 'drop';
31
+ /**
32
+ * Serialized types Byline knows to be block-level.
33
+ *
34
+ * Needed because a converted node's children are not always inline: a
35
+ * `listitem` may hold a nested `list`, so turning every item straight
36
+ * into a paragraph would nest a block inside a paragraph.
37
+ */
38
+ export declare const BLOCK_TYPES: ReadonlySet<string>;
39
+ export declare function isBlockType(type: string): boolean;
40
+ /**
41
+ * Serialized types Byline knows to be inline.
42
+ *
43
+ * The counterpart to {@link BLOCK_TYPES}, and the reason both lists
44
+ * exist rather than one: a node's structural role cannot be recovered
45
+ * from its serialized shape. An inline `link` and a block `quote` are
46
+ * both elements with `children`, `direction`, `format` and `indent`, so
47
+ * there is no reliable metadata to infer the role from.
48
+ *
49
+ * A SUPPORTED node can reach this test — it arrives as the child of an
50
+ * unsupported parent being converted — so "not a known block" cannot be
51
+ * read as "inline". A custom block from a downstream site would be
52
+ * wrapped into a paragraph and produce a tree Lexical rejects. When a
53
+ * type appears in neither list its role is unknown, and normalization
54
+ * refuses the document rather than guessing at it.
55
+ */
56
+ export declare const INLINE_TYPES: ReadonlySet<string>;
57
+ export declare function isInlineType(type: string): boolean;
58
+ export interface DeclaredConversion {
59
+ kind: ConversionKind;
60
+ /**
61
+ * Information carried on the node's own properties rather than in its
62
+ * children, which would otherwise be lost silently. Emitted as a
63
+ * leading paragraph for `lift-blocks` (an admonition title becomes the
64
+ * first line) and appended to the text for `unwrap-inline` (a link URL
65
+ * follows its text).
66
+ */
67
+ preserveText?: (node: SerializedNode) => string | undefined;
68
+ }
69
+ declare const urlText: (node: SerializedNode) => string | undefined;
70
+ /**
71
+ * How each convertible structure degrades when a field does not support
72
+ * it. Type strings are the node classes' own `getType()` values.
73
+ *
74
+ * A type absent from this table has NO conversion and is never guessed
75
+ * at — normalization refuses the document instead. That covers custom
76
+ * nodes from downstream sites as well as inline images and embeds, whose
77
+ * media relations and nested-editor captions cannot survive any
78
+ * structural rewrite.
79
+ */
80
+ export declare const DECLARED_CONVERSIONS: {
81
+ readonly heading: {
82
+ readonly kind: 'to-paragraph';
83
+ };
84
+ readonly quote: {
85
+ readonly kind: 'to-paragraph';
86
+ };
87
+ readonly code: {
88
+ readonly kind: 'to-paragraph';
89
+ };
90
+ readonly listitem: {
91
+ readonly kind: 'to-paragraph';
92
+ };
93
+ readonly table: {
94
+ readonly kind: 'lift-blocks';
95
+ };
96
+ readonly tablerow: {
97
+ readonly kind: 'lift-blocks';
98
+ };
99
+ readonly tablecell: {
100
+ readonly kind: 'lift-blocks';
101
+ };
102
+ readonly list: {
103
+ readonly kind: 'lift-blocks';
104
+ };
105
+ readonly 'layout-container': {
106
+ readonly kind: 'lift-blocks';
107
+ };
108
+ readonly 'layout-item': {
109
+ readonly kind: 'lift-blocks';
110
+ };
111
+ readonly admonition: {
112
+ readonly kind: 'lift-blocks';
113
+ readonly preserveText: (node: SerializedNode) => string | undefined;
114
+ };
115
+ readonly 'code-highlight': {
116
+ readonly kind: 'to-text';
117
+ };
118
+ readonly link: {
119
+ readonly kind: 'unwrap-inline';
120
+ readonly preserveText: typeof urlText;
121
+ };
122
+ readonly autolink: {
123
+ readonly kind: 'unwrap-inline';
124
+ readonly preserveText: typeof urlText;
125
+ };
126
+ readonly horizontalrule: {
127
+ readonly kind: 'drop';
128
+ };
129
+ };
130
+ export declare function conversionFor(type: string): DeclaredConversion | undefined;
131
+ export {};