@byline/richtext-lexical 3.4.0 → 3.5.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/field/extensions/admonition/admonition-commands.d.ts +25 -0
- package/dist/field/extensions/admonition/admonition-commands.js +4 -0
- package/dist/field/extensions/admonition/admonition-extension.d.ts +2 -3
- package/dist/field/extensions/admonition/admonition-extension.js +138 -24
- package/dist/field/extensions/admonition/admonition-node.css +112 -0
- package/dist/field/extensions/admonition/admonition-node.d.ts +26 -14
- package/dist/field/extensions/admonition/admonition-node.js +121 -72
- package/dist/field/extensions/admonition/index.js +1 -0
- package/dist/field/extensions/admonition/node-types.d.ts +10 -4
- package/dist/field/extensions/floating-text-format/index.d.ts +8 -1
- package/dist/field/extensions/floating-text-format/index.js +6 -4
- package/dist/field/markdown/transformers.js +15 -15
- package/package.json +5 -5
- package/src/field/extensions/admonition/admonition-commands.ts +31 -0
- package/src/field/extensions/admonition/admonition-extension.tsx +248 -37
- package/src/field/extensions/admonition/admonition-node.css +113 -0
- package/src/field/extensions/admonition/admonition-node.tsx +172 -93
- package/src/field/extensions/admonition/index.ts +4 -8
- package/src/field/extensions/admonition/node-types.ts +10 -10
- package/src/field/extensions/floating-text-format/index.tsx +19 -3
- package/src/field/markdown/admonition-roundtrip.test.node.ts +110 -0
- package/src/field/markdown/transformers.ts +51 -25
- package/dist/field/extensions/admonition/admonition-node-component.css +0 -119
- package/dist/field/extensions/admonition/admonition-node-component.d.ts +0 -17
- package/dist/field/extensions/admonition/admonition-node-component.js +0 -196
- package/dist/field/extensions/admonition/icons/danger-icon.d.ts +0 -7
- package/dist/field/extensions/admonition/icons/index.d.ts +0 -4
- package/dist/field/extensions/admonition/icons/note-icon.d.ts +0 -7
- package/dist/field/extensions/admonition/icons/tip-icon.d.ts +0 -7
- package/dist/field/extensions/admonition/icons/warning-icon.d.ts +0 -7
- package/src/field/extensions/admonition/admonition-node-component.css +0 -115
- package/src/field/extensions/admonition/admonition-node-component.tsx +0 -257
|
@@ -0,0 +1,25 @@
|
|
|
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
|
+
* Leaf module for the admonition Lexical commands. Lives apart from
|
|
10
|
+
* `admonition-extension.tsx` so the node class (`admonition-node.tsx`)
|
|
11
|
+
* can dispatch the "open modal" command from its `createDOM` chrome
|
|
12
|
+
* without importing the extension (which imports the node — a cycle).
|
|
13
|
+
*/
|
|
14
|
+
import { type LexicalCommand, type NodeKey } from 'lexical';
|
|
15
|
+
import type { AdmonitionAttributes } from './node-types';
|
|
16
|
+
/**
|
|
17
|
+
* Opens the admonition modal. Payload of `null` means "insert a new
|
|
18
|
+
* admonition"; a `{ nodeKey }` payload means "edit the existing node"
|
|
19
|
+
* — dispatched by the per-node Edit button rendered in `createDOM`.
|
|
20
|
+
*/
|
|
21
|
+
export declare const OPEN_ADMONITION_MODAL_COMMAND: LexicalCommand<{
|
|
22
|
+
nodeKey: NodeKey;
|
|
23
|
+
} | null>;
|
|
24
|
+
/** Inserts a new admonition (type + title come from the modal). */
|
|
25
|
+
export declare const INSERT_ADMONITION_COMMAND: LexicalCommand<AdmonitionAttributes>;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { createCommand } from "lexical";
|
|
2
|
+
const OPEN_ADMONITION_MODAL_COMMAND = createCommand('OPEN_ADMONITION_MODAL_COMMAND');
|
|
3
|
+
const INSERT_ADMONITION_COMMAND = createCommand('INSERT_ADMONITION_COMMAND');
|
|
4
|
+
export { INSERT_ADMONITION_COMMAND, OPEN_ADMONITION_MODAL_COMMAND };
|
|
@@ -6,10 +6,9 @@
|
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
8
|
import * as React from 'react';
|
|
9
|
-
import {
|
|
9
|
+
import { INSERT_ADMONITION_COMMAND, OPEN_ADMONITION_MODAL_COMMAND } from './admonition-commands';
|
|
10
10
|
import type { AdmonitionAttributes } from './node-types';
|
|
11
11
|
export type InsertAdmonitionPayload = Readonly<AdmonitionAttributes>;
|
|
12
|
-
export
|
|
13
|
-
export declare const INSERT_ADMONITION_COMMAND: LexicalCommand<AdmonitionAttributes>;
|
|
12
|
+
export { INSERT_ADMONITION_COMMAND, OPEN_ADMONITION_MODAL_COMMAND };
|
|
14
13
|
export declare function AdmonitionPlugin(): React.JSX.Element;
|
|
15
14
|
export declare const AdmonitionExtension: import("lexical").LexicalExtension<import("lexical").ExtensionConfigBase, "@byline/richtext-lexical/Admonition", unknown, unknown>;
|
|
@@ -2,57 +2,163 @@
|
|
|
2
2
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
3
3
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
4
4
|
import { ReactExtension } from "@lexical/react/ReactExtension";
|
|
5
|
-
import {
|
|
6
|
-
import { $
|
|
5
|
+
import { useOptionalExtensionDependency } from "@lexical/react/useExtensionComponent";
|
|
6
|
+
import { $findMatchingParent, $insertNodeToNearestRoot, mergeRegister } from "@lexical/utils";
|
|
7
|
+
import { $createParagraphNode, $createTextNode, $getNodeByKey, $getSelection, $isDecoratorNode, $isElementNode, $isLineBreakNode, $isParagraphNode, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_LOW, COMMAND_PRIORITY_NORMAL, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_LEFT_COMMAND, KEY_ARROW_RIGHT_COMMAND, KEY_ARROW_UP_COMMAND, configExtension, declarePeerDependency, defineExtension } from "lexical";
|
|
7
8
|
import { useToolbarActiveEditor } from "../../plugins/toolbar-plugin/toolbar-active-editor.js";
|
|
8
9
|
import { DropDownItem } from "../../ui/dropdown.js";
|
|
10
|
+
import { BylineFloatingUIExtension } from "../byline-floating-ui/byline-floating-ui-extension.js";
|
|
9
11
|
import { BylineToolbarExtension } from "../byline-toolbar/byline-toolbar-extension.js";
|
|
12
|
+
import { FloatingTextFormatExtension } from "../floating-text-format/floating-text-format-extension.js";
|
|
13
|
+
import { FloatingTextFormatToolbarPlugin } from "../floating-text-format/index.js";
|
|
14
|
+
import { INSERT_ADMONITION_COMMAND, OPEN_ADMONITION_MODAL_COMMAND } from "./admonition-commands.js";
|
|
10
15
|
import { AdmonitionModal } from "./admonition-modal.js";
|
|
11
|
-
import { $createAdmonitionNode, AdmonitionNode } from "./admonition-node.js";
|
|
16
|
+
import { $createAdmonitionNode, $isAdmonitionNode, AdmonitionNode } from "./admonition-node.js";
|
|
12
17
|
import * as __rspack_external_react from "react";
|
|
13
|
-
|
|
14
|
-
const
|
|
18
|
+
function $selectionInsideAdmonition() {
|
|
19
|
+
const selection = $getSelection();
|
|
20
|
+
if (!$isRangeSelection(selection)) return false;
|
|
21
|
+
return null != $findMatchingParent(selection.anchor.getNode(), $isAdmonitionNode);
|
|
22
|
+
}
|
|
23
|
+
function $flattenToParagraph(element) {
|
|
24
|
+
const paragraph = $createParagraphNode();
|
|
25
|
+
const children = element.getChildren();
|
|
26
|
+
const allInline = children.length > 0 && children.every((child)=>$isTextNode(child) || $isLineBreakNode(child) || child.isInline());
|
|
27
|
+
if (allInline) paragraph.append(...children);
|
|
28
|
+
else {
|
|
29
|
+
const text = element.getTextContent();
|
|
30
|
+
if (text.length > 0) paragraph.append($createTextNode(text));
|
|
31
|
+
}
|
|
32
|
+
element.replace(paragraph);
|
|
33
|
+
}
|
|
34
|
+
function $normalizeAdmonition(node) {
|
|
35
|
+
const parent = node.getParent();
|
|
36
|
+
const ancestorAdmonition = null != parent ? $findMatchingParent(parent, (candidate)=>$isAdmonitionNode(candidate)) : null;
|
|
37
|
+
if (null != ancestorAdmonition) {
|
|
38
|
+
for (const child of node.getChildren())node.insertBefore(child);
|
|
39
|
+
node.remove();
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (0 === node.getChildrenSize()) return void node.append($createParagraphNode());
|
|
43
|
+
for (const child of node.getChildren())if (!$isParagraphNode(child)) {
|
|
44
|
+
if ($isTextNode(child) || $isLineBreakNode(child) || child.isInline()) {
|
|
45
|
+
const paragraph = $createParagraphNode();
|
|
46
|
+
child.insertBefore(paragraph);
|
|
47
|
+
paragraph.append(child);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
if ($isDecoratorNode(child)) {
|
|
51
|
+
child.remove();
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if ($isElementNode(child)) $flattenToParagraph(child);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function $onEscapeUp() {
|
|
58
|
+
const selection = $getSelection();
|
|
59
|
+
if ($isRangeSelection(selection) && selection.isCollapsed() && 0 === selection.anchor.offset) {
|
|
60
|
+
const admonition = $findMatchingParent(selection.anchor.getNode(), $isAdmonitionNode);
|
|
61
|
+
if ($isAdmonitionNode(admonition)) {
|
|
62
|
+
const parent = admonition.getParent();
|
|
63
|
+
if (null != parent && parent.getFirstChild() === admonition && selection.anchor.key === admonition.getFirstDescendant()?.getKey()) admonition.insertBefore($createParagraphNode());
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
function $onEscapeDown() {
|
|
69
|
+
const selection = $getSelection();
|
|
70
|
+
if ($isRangeSelection(selection) && selection.isCollapsed()) {
|
|
71
|
+
const admonition = $findMatchingParent(selection.anchor.getNode(), $isAdmonitionNode);
|
|
72
|
+
if ($isAdmonitionNode(admonition)) {
|
|
73
|
+
const parent = admonition.getParent();
|
|
74
|
+
if (null != parent && parent.getLastChild() === admonition) {
|
|
75
|
+
const lastParagraph = admonition.getLastDescendant();
|
|
76
|
+
if (null != lastParagraph && selection.anchor.key === lastParagraph.getKey() && selection.anchor.offset === lastParagraph.getTextContentSize()) admonition.insertAfter($createParagraphNode());
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
15
82
|
function AdmonitionPlugin() {
|
|
16
83
|
const [editor] = useLexicalComposerContext();
|
|
17
84
|
const [open, setOpen] = __rspack_external_react.useState(false);
|
|
85
|
+
const [editNodeKey, setEditNodeKey] = __rspack_external_react.useState(null);
|
|
86
|
+
const [modalData, setModalData] = __rspack_external_react.useState({
|
|
87
|
+
title: '',
|
|
88
|
+
admonitionType: void 0
|
|
89
|
+
});
|
|
18
90
|
(0, __rspack_external_react.useEffect)(()=>{
|
|
19
91
|
if (!editor.hasNodes([
|
|
20
92
|
AdmonitionNode
|
|
21
93
|
])) throw new Error('AdmonitionPlugin: AdmonitionNode not registered on editor');
|
|
22
|
-
return mergeRegister(editor.registerCommand(OPEN_ADMONITION_MODAL_COMMAND, ()=>{
|
|
94
|
+
return mergeRegister(editor.registerCommand(OPEN_ADMONITION_MODAL_COMMAND, (payload)=>{
|
|
95
|
+
if (null == payload) {
|
|
96
|
+
setEditNodeKey(null);
|
|
97
|
+
setModalData({
|
|
98
|
+
title: '',
|
|
99
|
+
admonitionType: void 0
|
|
100
|
+
});
|
|
101
|
+
setOpen(true);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
const data = editor.getEditorState().read(()=>{
|
|
105
|
+
const node = $getNodeByKey(payload.nodeKey);
|
|
106
|
+
return $isAdmonitionNode(node) ? {
|
|
107
|
+
title: node.getTitle(),
|
|
108
|
+
admonitionType: node.getAdmonitionType()
|
|
109
|
+
} : null;
|
|
110
|
+
});
|
|
111
|
+
if (null == data) return false;
|
|
112
|
+
setEditNodeKey(payload.nodeKey);
|
|
113
|
+
setModalData(data);
|
|
23
114
|
setOpen(true);
|
|
24
115
|
return true;
|
|
25
116
|
}, COMMAND_PRIORITY_NORMAL), editor.registerCommand(INSERT_ADMONITION_COMMAND, (payload)=>{
|
|
26
117
|
const selection = $getSelection();
|
|
27
118
|
if (!$isRangeSelection(selection)) return false;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
119
|
+
if ($selectionInsideAdmonition()) return true;
|
|
120
|
+
const admonition = $createAdmonitionNode(payload);
|
|
121
|
+
admonition.append($createParagraphNode());
|
|
122
|
+
$insertNodeToNearestRoot(admonition);
|
|
123
|
+
admonition.selectStart();
|
|
33
124
|
return true;
|
|
34
|
-
}, COMMAND_PRIORITY_EDITOR));
|
|
125
|
+
}, COMMAND_PRIORITY_EDITOR), editor.registerNodeTransform(AdmonitionNode, $normalizeAdmonition), editor.registerCommand(KEY_ARROW_DOWN_COMMAND, $onEscapeDown, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ARROW_RIGHT_COMMAND, $onEscapeDown, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ARROW_UP_COMMAND, $onEscapeUp, COMMAND_PRIORITY_LOW), editor.registerCommand(KEY_ARROW_LEFT_COMMAND, $onEscapeUp, COMMAND_PRIORITY_LOW));
|
|
35
126
|
}, [
|
|
36
127
|
editor
|
|
37
128
|
]);
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
|
|
129
|
+
const handleSubmit = ({ admonitionType, title })=>{
|
|
130
|
+
setOpen(false);
|
|
131
|
+
if (null == admonitionType) return;
|
|
132
|
+
const key = editNodeKey;
|
|
133
|
+
if (null != key) editor.update(()=>{
|
|
134
|
+
const node = $getNodeByKey(key);
|
|
135
|
+
if ($isAdmonitionNode(node)) node.update({
|
|
41
136
|
admonitionType,
|
|
42
137
|
title
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
else editor.dispatchCommand(INSERT_ADMONITION_COMMAND, {
|
|
141
|
+
admonitionType,
|
|
142
|
+
title
|
|
143
|
+
});
|
|
144
|
+
setEditNodeKey(null);
|
|
47
145
|
};
|
|
48
146
|
return /*#__PURE__*/ jsx(AdmonitionModal, {
|
|
49
147
|
open: open,
|
|
50
|
-
data:
|
|
51
|
-
|
|
52
|
-
|
|
148
|
+
data: modalData,
|
|
149
|
+
onClose: ()=>{
|
|
150
|
+
setOpen(false);
|
|
151
|
+
setEditNodeKey(null);
|
|
53
152
|
},
|
|
54
|
-
|
|
55
|
-
|
|
153
|
+
onSubmit: handleSubmit
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
function AdmonitionFloatingTextFormat({ anchorElem }) {
|
|
157
|
+
const hasGlobalToolbar = void 0 !== useOptionalExtensionDependency(FloatingTextFormatExtension);
|
|
158
|
+
if (hasGlobalToolbar) return null;
|
|
159
|
+
return /*#__PURE__*/ jsx(FloatingTextFormatToolbarPlugin, {
|
|
160
|
+
anchorElem: anchorElem,
|
|
161
|
+
shouldShow: $selectionInsideAdmonition
|
|
56
162
|
});
|
|
57
163
|
}
|
|
58
164
|
function AdmonitionInsertItem() {
|
|
@@ -95,6 +201,14 @@ const AdmonitionExtension = defineExtension({
|
|
|
95
201
|
node: /*#__PURE__*/ jsx(AdmonitionInsertItem, {})
|
|
96
202
|
}
|
|
97
203
|
]
|
|
204
|
+
}),
|
|
205
|
+
declarePeerDependency(BylineFloatingUIExtension.name, {
|
|
206
|
+
items: [
|
|
207
|
+
{
|
|
208
|
+
id: '@byline/richtext-lexical/Admonition/floating-text-format',
|
|
209
|
+
Component: AdmonitionFloatingTextFormat
|
|
210
|
+
}
|
|
211
|
+
]
|
|
98
212
|
})
|
|
99
213
|
]
|
|
100
214
|
});
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
.LexicalEditor__admonition {
|
|
2
|
+
border: 1px solid #666;
|
|
3
|
+
border-left-width: 4px;
|
|
4
|
+
border-radius: 3px;
|
|
5
|
+
margin-top: .5rem;
|
|
6
|
+
margin-bottom: 1.5rem;
|
|
7
|
+
padding: 8px 12px 4px;
|
|
8
|
+
position: relative;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.LexicalEditor__admonition.admonition-note {
|
|
12
|
+
border-left-color: #4a90d9;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.LexicalEditor__admonition.admonition-tip {
|
|
16
|
+
border-left-color: #3fa45b;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.LexicalEditor__admonition.admonition-warning {
|
|
20
|
+
border-left-color: #d9a334;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.LexicalEditor__admonition.admonition-danger {
|
|
24
|
+
border-left-color: #d94a4a;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.LexicalEditor__admonition .AdmonitionNode__header {
|
|
28
|
+
text-transform: capitalize;
|
|
29
|
+
-webkit-user-select: none;
|
|
30
|
+
user-select: none;
|
|
31
|
+
align-items: center;
|
|
32
|
+
gap: 6px;
|
|
33
|
+
margin-bottom: .25rem;
|
|
34
|
+
font-size: 1rem;
|
|
35
|
+
font-weight: bold;
|
|
36
|
+
display: flex;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
.LexicalEditor__admonition .AdmonitionNode__icon {
|
|
40
|
+
align-items: center;
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.LexicalEditor__admonition .AdmonitionNode__icon svg {
|
|
45
|
+
fill: currentColor;
|
|
46
|
+
width: 20px;
|
|
47
|
+
height: 20px;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.LexicalEditor__admonition.admonition-note .AdmonitionNode__icon {
|
|
51
|
+
color: #4a90d9;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.LexicalEditor__admonition.admonition-tip .AdmonitionNode__icon {
|
|
55
|
+
color: #3fa45b;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.LexicalEditor__admonition.admonition-warning .AdmonitionNode__icon {
|
|
59
|
+
color: #d9a334;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.LexicalEditor__admonition.admonition-danger .AdmonitionNode__icon {
|
|
63
|
+
color: #d94a4a;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.LexicalEditor__admonition .AdmonitionNode__title {
|
|
67
|
+
flex: auto;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
.LexicalEditor__admonition .AdmonitionNode__edit-button {
|
|
71
|
+
color: inherit;
|
|
72
|
+
cursor: pointer;
|
|
73
|
+
-webkit-user-select: none;
|
|
74
|
+
user-select: none;
|
|
75
|
+
background-color: #0000000a;
|
|
76
|
+
border: 1px solid #0003;
|
|
77
|
+
border-radius: 5px;
|
|
78
|
+
flex: none;
|
|
79
|
+
padding: 2px 8px;
|
|
80
|
+
font-size: .8rem;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
.LexicalEditor__admonition .AdmonitionNode__edit-button:hover {
|
|
84
|
+
background-color: #3c84f42e;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.LexicalEditor__admonition .AdmonitionNode__content {
|
|
88
|
+
color: #4b5563;
|
|
89
|
+
font-size: .925em;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.LexicalEditor__admonition .AdmonitionNode__content > * {
|
|
93
|
+
margin-bottom: .25em;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.LexicalEditor__admonition .AdmonitionNode__content > :last-child {
|
|
97
|
+
margin-bottom: 0;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.dark .LexicalEditor__admonition {
|
|
101
|
+
border-color: #444;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.dark .LexicalEditor__admonition .AdmonitionNode__content {
|
|
105
|
+
color: #9ca3af;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
.dark .LexicalEditor__admonition .AdmonitionNode__edit-button {
|
|
109
|
+
background-color: #ffffff14;
|
|
110
|
+
border-color: #fff3;
|
|
111
|
+
}
|
|
112
|
+
|
|
@@ -5,30 +5,42 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Copyright (c) Infonomic Company Limited
|
|
7
7
|
*/
|
|
8
|
-
import
|
|
9
|
-
import
|
|
10
|
-
import { DecoratorNode } from 'lexical';
|
|
8
|
+
import type { DOMConversionMap, DOMExportOutput, EditorConfig, ElementDOMSlot, LexicalEditor, LexicalNode, LexicalUpdateJSON, NodeKey } from 'lexical';
|
|
9
|
+
import { ElementNode } from 'lexical';
|
|
11
10
|
import type { AdmonitionAttributes, AdmonitionType, SerializedAdmonitionNode } from './node-types';
|
|
12
|
-
|
|
11
|
+
import './admonition-node.css';
|
|
12
|
+
/**
|
|
13
|
+
* The admonition (callout) block. An `ElementNode` whose body is real
|
|
14
|
+
* children in the main editor tree — see the module note in
|
|
15
|
+
* `../../markdown/transformers.ts` for why this beats a nested editor for
|
|
16
|
+
* markdown round-tripping. `__admonitionType` / `__title` are attributes set
|
|
17
|
+
* from the Insert/Edit modal and rendered as non-editable chrome; the body
|
|
18
|
+
* (paragraphs + inline content) renders into the slot returned by
|
|
19
|
+
* `getDOMSlot`.
|
|
20
|
+
*/
|
|
21
|
+
export declare class AdmonitionNode extends ElementNode {
|
|
13
22
|
__admonitionType: AdmonitionType;
|
|
14
23
|
__title: string;
|
|
15
|
-
__content: LexicalEditor;
|
|
16
24
|
static getType(): string;
|
|
17
25
|
static clone(node: AdmonitionNode): AdmonitionNode;
|
|
18
26
|
static importJSON(serializedNode: SerializedAdmonitionNode): AdmonitionNode;
|
|
19
27
|
static importDOM(): DOMConversionMap | null;
|
|
20
|
-
constructor(admonitionType: AdmonitionType, title: string,
|
|
21
|
-
|
|
28
|
+
constructor(admonitionType: AdmonitionType, title: string, key?: NodeKey);
|
|
29
|
+
updateFromJSON(serializedNode: LexicalUpdateJSON<SerializedAdmonitionNode>): this;
|
|
22
30
|
exportJSON(): SerializedAdmonitionNode;
|
|
31
|
+
exportDOM(): DOMExportOutput;
|
|
32
|
+
isShadowRoot(): boolean;
|
|
23
33
|
isInline(): false;
|
|
34
|
+
canBeEmpty(): boolean;
|
|
35
|
+
canIndent(): false;
|
|
36
|
+
createDOM(config: EditorConfig, editor: LexicalEditor): HTMLElement;
|
|
37
|
+
getDOMSlot(element: HTMLElement): ElementDOMSlot<HTMLElement>;
|
|
38
|
+
updateDOM(prevNode: AdmonitionNode, dom: HTMLElement): boolean;
|
|
24
39
|
getTitle(): string;
|
|
25
|
-
setTitle(title: string):
|
|
40
|
+
setTitle(title: string): this;
|
|
26
41
|
getAdmonitionType(): AdmonitionType;
|
|
27
|
-
setAdmonitionType(admonitionType: AdmonitionType):
|
|
28
|
-
update(payload: AdmonitionAttributes): void;
|
|
29
|
-
createDOM(config: EditorConfig): HTMLElement;
|
|
30
|
-
updateDOM(prevNode: AdmonitionNode, dom: HTMLElement, config: EditorConfig): boolean;
|
|
31
|
-
decorate(): React.JSX.Element;
|
|
42
|
+
setAdmonitionType(admonitionType: AdmonitionType): this;
|
|
43
|
+
update(payload: Partial<Pick<AdmonitionAttributes, 'admonitionType' | 'title'>>): void;
|
|
32
44
|
}
|
|
33
|
-
export declare function $createAdmonitionNode({ admonitionType, title,
|
|
45
|
+
export declare function $createAdmonitionNode({ admonitionType, title, key, }: AdmonitionAttributes): AdmonitionNode;
|
|
34
46
|
export declare function $isAdmonitionNode(node: LexicalNode | null | undefined): node is AdmonitionNode;
|
|
@@ -1,127 +1,176 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
const
|
|
2
|
+
import { $applyNodeReplacement, ElementNode } from "lexical";
|
|
3
|
+
import { OPEN_ADMONITION_MODAL_COMMAND } from "./admonition-commands.js";
|
|
4
|
+
import "./admonition-node.css";
|
|
5
|
+
const ADMONITION_TYPES = new Set([
|
|
6
|
+
'note',
|
|
7
|
+
'tip',
|
|
8
|
+
'warning',
|
|
9
|
+
'danger'
|
|
10
|
+
]);
|
|
11
|
+
function isAdmonitionType(value) {
|
|
12
|
+
return 'string' == typeof value && ADMONITION_TYPES.has(value);
|
|
13
|
+
}
|
|
14
|
+
const ICON_SVG = {
|
|
15
|
+
note: '<svg focusable="false" aria-hidden="true" viewBox="0 0 24 24"><path d="M11,9H13V7H11M12,20C7.59,20 4,16.41 4,12C4,7.59 7.59,4 12,4C16.41,4 20,7.59 20,12C20,16.41 16.41,20 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2M11,17H13V11H11V17Z"></path></svg>',
|
|
16
|
+
tip: '<svg focusable="false" aria-hidden="true" viewBox="0 0 24 24"><path d="M20,12A8,8 0 0,1 12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4C12.76,4 13.5,4.11 14.2,4.31L15.77,2.74C14.61,2.26 13.34,2 12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12M7.91,10.08L6.5,11.5L11,16L21,6L19.59,4.58L11,13.17L7.91,10.08Z"></path></svg>',
|
|
17
|
+
warning: '<svg focusable="false" aria-hidden="true" viewBox="0 0 24 24"><path d="M12 5.99L19.53 19H4.47L12 5.99M12 2L1 21h22L12 2zm1 14h-2v2h2v-2zm0-6h-2v4h2v-4z"></path></svg>',
|
|
18
|
+
danger: '<svg focusable="false" aria-hidden="true" viewBox="0 0 24 24"><path d="M11 15h2v2h-2zm0-8h2v6h-2zm.99-5C6.47 2 2 6.48 2 12s4.47 10 9.99 10C17.52 22 22 17.52 22 12S17.52 2 11.99 2zM12 20c-4.42 0-8-3.58-8-8s3.58-8 8-8 8 3.58 8 8-3.58 8-8 8z"></path></svg>'
|
|
19
|
+
};
|
|
6
20
|
function convertAdmonitionElement(domNode) {
|
|
7
|
-
if (domNode instanceof HTMLDivElement) {
|
|
21
|
+
if (domNode instanceof HTMLDivElement && null != domNode.dataset.type) {
|
|
8
22
|
const type = domNode.dataset.type;
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
admonitionType: type,
|
|
12
|
-
title
|
|
13
|
-
});
|
|
23
|
+
if (!isAdmonitionType(type)) return null;
|
|
24
|
+
const title = domNode.dataset.title ?? '';
|
|
14
25
|
return {
|
|
15
|
-
node
|
|
26
|
+
node: $createAdmonitionNode({
|
|
27
|
+
admonitionType: type,
|
|
28
|
+
title
|
|
29
|
+
})
|
|
16
30
|
};
|
|
17
31
|
}
|
|
18
32
|
return null;
|
|
19
33
|
}
|
|
20
|
-
class AdmonitionNode extends
|
|
34
|
+
class AdmonitionNode extends ElementNode {
|
|
21
35
|
static getType() {
|
|
22
36
|
return 'admonition';
|
|
23
37
|
}
|
|
24
38
|
static clone(node) {
|
|
25
|
-
return new AdmonitionNode(node.__admonitionType, node.__title, node.
|
|
39
|
+
return new AdmonitionNode(node.__admonitionType, node.__title, node.__key);
|
|
26
40
|
}
|
|
27
41
|
static importJSON(serializedNode) {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
});
|
|
33
|
-
const nestedEditor = node.__content;
|
|
34
|
-
const editorState = nestedEditor.parseEditorState(content.editorState);
|
|
35
|
-
if (!editorState.isEmpty()) nestedEditor.setEditorState(editorState);
|
|
36
|
-
return node;
|
|
42
|
+
return $createAdmonitionNode({
|
|
43
|
+
admonitionType: serializedNode.admonitionType,
|
|
44
|
+
title: serializedNode.title
|
|
45
|
+
}).updateFromJSON(serializedNode);
|
|
37
46
|
}
|
|
38
47
|
static importDOM() {
|
|
39
48
|
return {
|
|
40
|
-
div: (
|
|
49
|
+
div: (node)=>{
|
|
50
|
+
if (null == node.dataset.type || !isAdmonitionType(node.dataset.type)) return null;
|
|
51
|
+
return {
|
|
41
52
|
conversion: convertAdmonitionElement,
|
|
42
|
-
priority:
|
|
43
|
-
}
|
|
53
|
+
priority: 1
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
updateFromJSON(serializedNode) {
|
|
59
|
+
return super.updateFromJSON(serializedNode).setAdmonitionType(serializedNode.admonitionType).setTitle(serializedNode.title);
|
|
60
|
+
}
|
|
61
|
+
exportJSON() {
|
|
62
|
+
return {
|
|
63
|
+
...super.exportJSON(),
|
|
64
|
+
admonitionType: this.__admonitionType,
|
|
65
|
+
title: this.__title
|
|
44
66
|
};
|
|
45
67
|
}
|
|
46
68
|
exportDOM() {
|
|
47
69
|
const element = document.createElement('div');
|
|
48
70
|
element.setAttribute('data-type', this.__admonitionType);
|
|
49
71
|
element.setAttribute('data-title', this.__title);
|
|
72
|
+
element.className = `admonition admonition-${this.__admonitionType}`;
|
|
50
73
|
return {
|
|
51
74
|
element
|
|
52
75
|
};
|
|
53
76
|
}
|
|
54
|
-
|
|
55
|
-
return
|
|
56
|
-
admonitionType: this.__admonitionType,
|
|
57
|
-
title: this.__title,
|
|
58
|
-
content: this.__content.toJSON(),
|
|
59
|
-
type: 'admonition',
|
|
60
|
-
version: 1
|
|
61
|
-
};
|
|
77
|
+
isShadowRoot() {
|
|
78
|
+
return true;
|
|
62
79
|
}
|
|
63
80
|
isInline() {
|
|
64
81
|
return false;
|
|
65
82
|
}
|
|
83
|
+
canBeEmpty() {
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
canIndent() {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
createDOM(config, editor) {
|
|
90
|
+
const key = this.getKey();
|
|
91
|
+
const dom = document.createElement('div');
|
|
92
|
+
dom.setAttribute('data-type', this.__admonitionType);
|
|
93
|
+
dom.setAttribute('data-title', this.__title);
|
|
94
|
+
dom.className = `${config.theme.admonition ?? ''} admonition-${this.__admonitionType}`.trim();
|
|
95
|
+
const header = document.createElement('div');
|
|
96
|
+
header.className = 'AdmonitionNode__header';
|
|
97
|
+
header.contentEditable = 'false';
|
|
98
|
+
header.setAttribute('data-lexical-admonition-chrome', 'true');
|
|
99
|
+
const icon = document.createElement('span');
|
|
100
|
+
icon.className = 'AdmonitionNode__icon';
|
|
101
|
+
icon.innerHTML = ICON_SVG[this.__admonitionType];
|
|
102
|
+
const titleEl = document.createElement('span');
|
|
103
|
+
titleEl.className = 'AdmonitionNode__title';
|
|
104
|
+
titleEl.textContent = this.__title;
|
|
105
|
+
const editButton = document.createElement('button');
|
|
106
|
+
editButton.type = 'button';
|
|
107
|
+
editButton.className = 'AdmonitionNode__edit-button';
|
|
108
|
+
editButton.textContent = 'Edit';
|
|
109
|
+
editButton.addEventListener('click', (event)=>{
|
|
110
|
+
event.preventDefault();
|
|
111
|
+
event.stopPropagation();
|
|
112
|
+
editor.dispatchCommand(OPEN_ADMONITION_MODAL_COMMAND, {
|
|
113
|
+
nodeKey: key
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
header.appendChild(icon);
|
|
117
|
+
header.appendChild(titleEl);
|
|
118
|
+
header.appendChild(editButton);
|
|
119
|
+
const content = document.createElement('div');
|
|
120
|
+
content.className = 'AdmonitionNode__content';
|
|
121
|
+
dom.appendChild(header);
|
|
122
|
+
dom.appendChild(content);
|
|
123
|
+
return dom;
|
|
124
|
+
}
|
|
125
|
+
getDOMSlot(element) {
|
|
126
|
+
const content = element.querySelector(':scope > .AdmonitionNode__content');
|
|
127
|
+
if (!(content instanceof HTMLElement)) throw new Error('AdmonitionNode: expected a .AdmonitionNode__content slot element');
|
|
128
|
+
return super.getDOMSlot(element).withElement(content);
|
|
129
|
+
}
|
|
130
|
+
updateDOM(prevNode, dom) {
|
|
131
|
+
const type = this.__admonitionType;
|
|
132
|
+
if (type !== prevNode.__admonitionType) {
|
|
133
|
+
dom.className = `${dom.className.replace(/admonition-\w+/, '').trim()} admonition-${type}`.trim();
|
|
134
|
+
dom.setAttribute('data-type', type);
|
|
135
|
+
const icon = dom.querySelector(':scope > .AdmonitionNode__header > .AdmonitionNode__icon');
|
|
136
|
+
if (null != icon) icon.innerHTML = ICON_SVG[type];
|
|
137
|
+
}
|
|
138
|
+
if (this.__title !== prevNode.__title) {
|
|
139
|
+
dom.setAttribute('data-title', this.__title);
|
|
140
|
+
const titleEl = dom.querySelector(':scope > .AdmonitionNode__header > .AdmonitionNode__title');
|
|
141
|
+
if (null != titleEl) titleEl.textContent = this.__title;
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
66
145
|
getTitle() {
|
|
67
|
-
return this.__title;
|
|
146
|
+
return this.getLatest().__title;
|
|
68
147
|
}
|
|
69
148
|
setTitle(title) {
|
|
70
149
|
const writable = this.getWritable();
|
|
71
150
|
writable.__title = title;
|
|
151
|
+
return writable;
|
|
72
152
|
}
|
|
73
153
|
getAdmonitionType() {
|
|
74
|
-
return this.__admonitionType;
|
|
154
|
+
return this.getLatest().__admonitionType;
|
|
75
155
|
}
|
|
76
156
|
setAdmonitionType(admonitionType) {
|
|
77
157
|
const writable = this.getWritable();
|
|
78
158
|
writable.__admonitionType = admonitionType;
|
|
159
|
+
return writable;
|
|
79
160
|
}
|
|
80
161
|
update(payload) {
|
|
81
162
|
const writable = this.getWritable();
|
|
82
|
-
|
|
83
|
-
if (null !=
|
|
84
|
-
if (null != title) writable.__title = title;
|
|
85
|
-
}
|
|
86
|
-
createDOM(config) {
|
|
87
|
-
const div = document.createElement('div');
|
|
88
|
-
div.setAttribute('data-type', this.__admonitionType);
|
|
89
|
-
div.setAttribute('data-title', this.__title);
|
|
90
|
-
const className = `${config.theme.admonition} admonition-${this.__admonitionType}`;
|
|
91
|
-
if (void 0 !== className) div.className = className;
|
|
92
|
-
return div;
|
|
93
|
-
}
|
|
94
|
-
updateDOM(prevNode, dom, config) {
|
|
95
|
-
const admonitionType = this.__admonitionType;
|
|
96
|
-
if (admonitionType !== prevNode.__admonitionType) {
|
|
97
|
-
const className = `${config.theme.admonition} admonition-${admonitionType}`;
|
|
98
|
-
if (void 0 !== className) dom.className = className;
|
|
99
|
-
dom.setAttribute('data-type', admonitionType);
|
|
100
|
-
return true;
|
|
101
|
-
}
|
|
102
|
-
if (this.__title !== prevNode.__title) {
|
|
103
|
-
dom.setAttribute('data-title', this.__title);
|
|
104
|
-
return true;
|
|
105
|
-
}
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
decorate() {
|
|
109
|
-
return /*#__PURE__*/ jsx(AdmonitionNodeComponent, {
|
|
110
|
-
admonitionType: this.__admonitionType,
|
|
111
|
-
title: this.__title,
|
|
112
|
-
content: this.__content,
|
|
113
|
-
nodeKey: this.getKey()
|
|
114
|
-
});
|
|
163
|
+
if (null != payload.admonitionType) writable.__admonitionType = payload.admonitionType;
|
|
164
|
+
if (null != payload.title) writable.__title = payload.title;
|
|
115
165
|
}
|
|
116
|
-
constructor(admonitionType, title,
|
|
166
|
+
constructor(admonitionType, title, key){
|
|
117
167
|
super(key);
|
|
118
168
|
this.__admonitionType = admonitionType;
|
|
119
169
|
this.__title = title;
|
|
120
|
-
this.__content = content ?? createEditor();
|
|
121
170
|
}
|
|
122
171
|
}
|
|
123
|
-
function $createAdmonitionNode({ admonitionType, title,
|
|
124
|
-
return $applyNodeReplacement(new AdmonitionNode(admonitionType, title,
|
|
172
|
+
function $createAdmonitionNode({ admonitionType, title, key }) {
|
|
173
|
+
return $applyNodeReplacement(new AdmonitionNode(admonitionType, title, key));
|
|
125
174
|
}
|
|
126
175
|
function $isAdmonitionNode(node) {
|
|
127
176
|
return node instanceof AdmonitionNode;
|