@use-kona/editor 0.1.5 → 0.1.7
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/core/createEditable.d.ts +1 -1
- package/dist/core/createEditable.js +13 -1
- package/dist/core/createEditor.js +8 -8
- package/dist/core/queries.d.ts +5 -2
- package/dist/core/queries.js +5 -2
- package/dist/plugins/CodeBlockPlugin/CodeBlockPlugin.d.ts +8 -2
- package/dist/plugins/CodeBlockPlugin/CodeBlockPlugin.js +12 -0
- package/dist/plugins/ListsPlugin/ListsPlugin.d.ts +1 -0
- package/dist/plugins/ListsPlugin/ListsPlugin.js +42 -5
- package/dist/plugins/ShortcutsPlugin/ShortcutsPlugin.js +4 -4
- package/package.json +1 -1
- package/src/core/createEditable.tsx +25 -1
- package/src/core/createEditor.ts +8 -8
- package/src/core/deserialize.ts +5 -3
- package/src/core/queries.ts +8 -2
- package/src/plugins/CodeBlockPlugin/CodeBlockPlugin.tsx +16 -0
- package/src/plugins/LinksPlugin/LinksPlugin.tsx +0 -1
- package/src/plugins/ListsPlugin/ListsPlugin.tsx +64 -9
- package/src/plugins/ShortcutsPlugin/ShortcutsPlugin.tsx +4 -7
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
2
2
|
import is_hotkey from "is-hotkey";
|
|
3
3
|
import react, { isValidElement, useCallback } from "react";
|
|
4
|
+
import { Transforms } from "slate";
|
|
4
5
|
import { Editable, useReadOnly } from "slate-react";
|
|
5
6
|
import { BaseElement } from "../elements/BaseElement.js";
|
|
7
|
+
import { deserialize } from "./deserialize.js";
|
|
6
8
|
import styles_module from "./styles.module.js";
|
|
7
9
|
const SUPPORTED_HANDLERS = [
|
|
8
10
|
'onDrop',
|
|
@@ -37,6 +39,7 @@ const createEditable = (editor, plugins)=>({ readOnly })=>{
|
|
|
37
39
|
return result;
|
|
38
40
|
};
|
|
39
41
|
const handleHotkey = (event)=>{
|
|
42
|
+
if ('Tab' === event.key) event.preventDefault();
|
|
40
43
|
for (const plugin of plugins)if (plugin.hotkeys) {
|
|
41
44
|
for (const hotkey of plugin.hotkeys)if (is_hotkey(hotkey[0], event) && hotkey[1]) {
|
|
42
45
|
event.preventDefault();
|
|
@@ -44,6 +47,14 @@ const createEditable = (editor, plugins)=>({ readOnly })=>{
|
|
|
44
47
|
}
|
|
45
48
|
}
|
|
46
49
|
};
|
|
50
|
+
const handlePaste = (event)=>{
|
|
51
|
+
const html = event.clipboardData?.getData('text/html');
|
|
52
|
+
if (!html) return;
|
|
53
|
+
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
54
|
+
const value = deserialize(plugins)(parsed.body);
|
|
55
|
+
event.preventDefault();
|
|
56
|
+
Transforms.insertNodes(editor, value);
|
|
57
|
+
};
|
|
47
58
|
const decorate = (entry)=>{
|
|
48
59
|
const decorators = plugins.filter((plugin)=>!!plugin.decorate);
|
|
49
60
|
return decorators.reduce((decorations, plugin)=>{
|
|
@@ -66,7 +77,8 @@ const createEditable = (editor, plugins)=>({ readOnly })=>{
|
|
|
66
77
|
});
|
|
67
78
|
return handlers;
|
|
68
79
|
}, {
|
|
69
|
-
onKeyDown: handleHotkey
|
|
80
|
+
onKeyDown: handleHotkey,
|
|
81
|
+
onPaste: handlePaste
|
|
70
82
|
});
|
|
71
83
|
const Ui = useCallback(({ children })=>{
|
|
72
84
|
const ui = plugins.filter((p)=>Boolean(p.ui));
|
|
@@ -74,7 +74,7 @@ const createEditor_createEditor = (plugins)=>()=>{
|
|
|
74
74
|
const result = matchedBlock.onBeforeDelete ? await matchedBlock?.onBeforeDelete?.([
|
|
75
75
|
node
|
|
76
76
|
]) : true;
|
|
77
|
-
if (result) {
|
|
77
|
+
if (result && Editor.isVoid(editorWithPlugins, node)) {
|
|
78
78
|
Transforms.removeNodes(editorWithPlugins, {
|
|
79
79
|
at: path
|
|
80
80
|
});
|
|
@@ -92,19 +92,19 @@ const createEditor_createEditor = (plugins)=>()=>{
|
|
|
92
92
|
const { selection } = editorWithPlugins;
|
|
93
93
|
if (!selection) return;
|
|
94
94
|
if (Range.isCollapsed(selection)) {
|
|
95
|
-
const
|
|
95
|
+
const currentEntry = Editor.above(editorWithPlugins, {
|
|
96
96
|
at: selection.anchor,
|
|
97
97
|
match: (n)=>Editor.isBlock(editorWithPlugins, n),
|
|
98
98
|
mode: 'lowest'
|
|
99
99
|
});
|
|
100
|
-
const
|
|
100
|
+
const previousEntry = Editor.above(editorWithPlugins, {
|
|
101
101
|
at: Editor.before(editorWithPlugins, selection.anchor),
|
|
102
102
|
match: (n)=>Editor.isBlock(editorWithPlugins, n),
|
|
103
103
|
mode: 'lowest'
|
|
104
104
|
});
|
|
105
|
-
if (!
|
|
106
|
-
const [_, currentPath] =
|
|
107
|
-
const [node, path] =
|
|
105
|
+
if (!currentEntry || !previousEntry) return;
|
|
106
|
+
const [_, currentPath] = currentEntry;
|
|
107
|
+
const [node, path] = previousEntry;
|
|
108
108
|
if (node && Editor.isStart(editorWithPlugins, selection.anchor, currentPath)) {
|
|
109
109
|
const matchedBlock = plugins.reduce((block, plugin)=>{
|
|
110
110
|
const match = plugin.blocks?.find((b)=>b.type === node.type);
|
|
@@ -114,15 +114,15 @@ const createEditor_createEditor = (plugins)=>()=>{
|
|
|
114
114
|
const result = matchedBlock.onBeforeDelete ? await matchedBlock.onBeforeDelete([
|
|
115
115
|
node
|
|
116
116
|
]) : true;
|
|
117
|
-
if (result) {
|
|
117
|
+
if (result && Editor.isVoid(editorWithPlugins, node)) {
|
|
118
118
|
Transforms.removeNodes(editorWithPlugins, {
|
|
119
119
|
at: path
|
|
120
120
|
});
|
|
121
121
|
matchedBlock.onDelete?.([
|
|
122
122
|
node
|
|
123
123
|
]);
|
|
124
|
+
return;
|
|
124
125
|
}
|
|
125
|
-
return;
|
|
126
126
|
}
|
|
127
127
|
}
|
|
128
128
|
}
|
package/dist/core/queries.d.ts
CHANGED
|
@@ -1,2 +1,5 @@
|
|
|
1
|
-
import { Editor, NodeEntry
|
|
2
|
-
|
|
1
|
+
import { Editor, type NodeEntry } from 'slate';
|
|
2
|
+
/**
|
|
3
|
+
* @deprecated
|
|
4
|
+
*/
|
|
5
|
+
export declare const getPrev: (editor: Editor, node: NodeEntry) => NodeEntry<import("slate").Ancestor> | null | undefined;
|
package/dist/core/queries.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Editor, Path } from "slate";
|
|
2
2
|
const getPrev = (editor, node)=>{
|
|
3
3
|
try {
|
|
4
4
|
const [, path] = node;
|
|
5
|
-
return
|
|
5
|
+
return Editor.above(editor, {
|
|
6
|
+
at: Path.previous(path),
|
|
7
|
+
mode: 'lowest'
|
|
8
|
+
});
|
|
6
9
|
} catch (e) {
|
|
7
10
|
return null;
|
|
8
11
|
}
|
|
@@ -4,6 +4,7 @@ import { type RenderElementProps, type RenderLeafProps } from 'slate-react';
|
|
|
4
4
|
import type { IPlugin } from '../../types';
|
|
5
5
|
import type { CodeElement } from './types';
|
|
6
6
|
import 'prismjs/themes/prism.css';
|
|
7
|
+
import type { CustomElement } from '../../../types';
|
|
7
8
|
import 'prismjs/components/prism-javascript';
|
|
8
9
|
import 'prismjs/components/prism-typescript';
|
|
9
10
|
import 'prismjs/components/prism-json';
|
|
@@ -43,10 +44,15 @@ export declare class CodeBlockPlugin implements IPlugin {
|
|
|
43
44
|
} & {
|
|
44
45
|
token: boolean;
|
|
45
46
|
})[];
|
|
46
|
-
blocks: {
|
|
47
|
+
blocks: ({
|
|
47
48
|
type: string;
|
|
48
49
|
render: (props: RenderElementProps, editor: Editor) => import("react/jsx-runtime").JSX.Element;
|
|
49
|
-
|
|
50
|
+
deserialize: (el: HTMLElement, children: any) => CustomElement | undefined;
|
|
51
|
+
} | {
|
|
52
|
+
type: string;
|
|
53
|
+
render: (props: RenderElementProps) => import("react/jsx-runtime").JSX.Element;
|
|
54
|
+
deserialize?: undefined;
|
|
55
|
+
})[];
|
|
50
56
|
leafs: {
|
|
51
57
|
render: (props: RenderLeafProps) => import("react/jsx-runtime").JSX.Element;
|
|
52
58
|
}[];
|
|
@@ -26,6 +26,7 @@ import "prismjs/components/prism-bash";
|
|
|
26
26
|
import "prismjs/components/prism-sql";
|
|
27
27
|
import "prismjs/components/prism-yaml";
|
|
28
28
|
import "prismjs/components/prism-markdown";
|
|
29
|
+
import { jsx as external_slate_hyperscript_jsx } from "slate-hyperscript";
|
|
29
30
|
class CodeBlockPlugin {
|
|
30
31
|
options;
|
|
31
32
|
constructor(options){
|
|
@@ -155,6 +156,17 @@ class CodeBlockPlugin {
|
|
|
155
156
|
Content
|
|
156
157
|
})
|
|
157
158
|
});
|
|
159
|
+
},
|
|
160
|
+
deserialize: (el, children)=>{
|
|
161
|
+
const { nodeName } = el;
|
|
162
|
+
if ('PRE' === nodeName) {
|
|
163
|
+
const lines = children.join('').split('\n');
|
|
164
|
+
return external_slate_hyperscript_jsx('element', {
|
|
165
|
+
type: CodeBlockPlugin.CODE_ELEMENT
|
|
166
|
+
}, lines.map((line)=>external_slate_hyperscript_jsx('element', {
|
|
167
|
+
type: CodeBlockPlugin.CODE_LINE_ELEMENT
|
|
168
|
+
}, line)));
|
|
169
|
+
}
|
|
158
170
|
}
|
|
159
171
|
},
|
|
160
172
|
{
|
|
@@ -30,6 +30,7 @@ export declare class ListsPlugin implements IPlugin {
|
|
|
30
30
|
private isListItem;
|
|
31
31
|
static toggleList: (editor: Editor, listType: string, listItemType?: string) => void;
|
|
32
32
|
private getListItemDepth;
|
|
33
|
+
private getListDepth;
|
|
33
34
|
private getListItem;
|
|
34
35
|
}
|
|
35
36
|
export {};
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { Editor, Element, Node, Transforms } from "slate";
|
|
3
3
|
import { jsx as external_slate_hyperscript_jsx } from "slate-hyperscript";
|
|
4
|
-
import { getPrev } from "../../core/queries.js";
|
|
5
4
|
import styles_module from "./styles.module.js";
|
|
6
5
|
class ListsPlugin {
|
|
7
6
|
options;
|
|
@@ -48,6 +47,24 @@ class ListsPlugin {
|
|
|
48
47
|
split: true
|
|
49
48
|
});
|
|
50
49
|
}
|
|
50
|
+
if (this.isList(editor, node)) {
|
|
51
|
+
const prevListItemPath = Editor.before(editor, path, {
|
|
52
|
+
unit: 'block'
|
|
53
|
+
});
|
|
54
|
+
const prevList = Editor.above(editor, {
|
|
55
|
+
at: prevListItemPath,
|
|
56
|
+
match: (n)=>this.isList(editor, n)
|
|
57
|
+
});
|
|
58
|
+
if (prevList) {
|
|
59
|
+
const currentDepth = this.getListDepth(editor, path);
|
|
60
|
+
const prevDepth = this.getListDepth(editor, prevList[1]);
|
|
61
|
+
if (this.isList(editor, prevList[0]) && currentDepth === prevDepth) try {
|
|
62
|
+
Transforms.mergeNodes(editor, {
|
|
63
|
+
match: (n)=>this.isList(editor, n)
|
|
64
|
+
});
|
|
65
|
+
} catch (e) {}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
51
68
|
normalizeNode(entry);
|
|
52
69
|
};
|
|
53
70
|
editor.insertBreak = ()=>{
|
|
@@ -152,9 +169,15 @@ class ListsPlugin {
|
|
|
152
169
|
else if (!event.shiftKey) {
|
|
153
170
|
const currentListItem = this.getListItem(editor);
|
|
154
171
|
if (!currentListItem) return;
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
172
|
+
const prevListItemPath = Editor.before(editor, currentListItem[1], {
|
|
173
|
+
unit: 'block'
|
|
174
|
+
});
|
|
175
|
+
if (currentListItem && prevListItemPath) {
|
|
176
|
+
const prevListItem = Editor.above(editor, {
|
|
177
|
+
at: prevListItemPath,
|
|
178
|
+
match: (n)=>this.isListItem(editor, n)
|
|
179
|
+
});
|
|
180
|
+
if (!prevListItem) return;
|
|
158
181
|
const currentDepth = this.getListItemDepth(editor, currentListItem[1]);
|
|
159
182
|
const prevDepth = this.getListItemDepth(editor, prevListItem[1]);
|
|
160
183
|
if (prevListItem && prevDepth >= currentDepth) Transforms.wrapNodes(editor, {
|
|
@@ -220,9 +243,23 @@ class ListsPlugin {
|
|
|
220
243
|
if (item) {
|
|
221
244
|
let count = 0;
|
|
222
245
|
let parent = Editor.parent(editor, item[1]);
|
|
223
|
-
|
|
246
|
+
do {
|
|
224
247
|
parent = Editor.parent(editor, parent[1]);
|
|
225
248
|
count++;
|
|
249
|
+
}while (null !== parent && this.isList(editor, parent[0]));
|
|
250
|
+
return count;
|
|
251
|
+
}
|
|
252
|
+
return 0;
|
|
253
|
+
};
|
|
254
|
+
getListDepth = (editor, path)=>{
|
|
255
|
+
if (path) {
|
|
256
|
+
const node = Node.get(editor, path);
|
|
257
|
+
if (!this.isList(editor, node)) return 0;
|
|
258
|
+
let count = 0;
|
|
259
|
+
let parent = Editor.parent(editor, path);
|
|
260
|
+
while(parent && this.isList(editor, parent[0])){
|
|
261
|
+
count++;
|
|
262
|
+
parent = Editor.parent(editor, parent[1]);
|
|
226
263
|
}
|
|
227
264
|
return count;
|
|
228
265
|
}
|
|
@@ -25,12 +25,12 @@ class ShortcutsPlugin {
|
|
|
25
25
|
const currentPoint = Range.start(selection);
|
|
26
26
|
const [currentNode] = Editor.node(editor, currentPoint.path);
|
|
27
27
|
const textBeforeCursor = Node.string(currentNode).slice(0, currentPoint.offset);
|
|
28
|
-
matchBefore = new RegExp(shortcut.before.source
|
|
28
|
+
matchBefore = new RegExp(`${shortcut.before.source}`, 'g').exec(textBeforeCursor);
|
|
29
29
|
}
|
|
30
30
|
if (shortcut.after && matchBefore) {
|
|
31
31
|
const currentPoint = Range.start(selection);
|
|
32
32
|
const [currentNode] = Editor.node(editor, currentPoint.path);
|
|
33
|
-
const textBetweenMatches = Node.string(currentNode).slice(matchBefore.index, currentPoint.offset);
|
|
33
|
+
const textBetweenMatches = Node.string(currentNode).slice(matchBefore.index + matchBefore[0].length, currentPoint.offset);
|
|
34
34
|
matchAfter = new RegExp(shortcut.after.source + '$', 'g').exec(textBetweenMatches);
|
|
35
35
|
}
|
|
36
36
|
if (matchBefore && !shortcut.after) return void shortcut.change(editor, {
|
|
@@ -42,8 +42,8 @@ class ShortcutsPlugin {
|
|
|
42
42
|
if (matchBefore && matchAfter && shortcut.before && shortcut.after) return void shortcut.change(editor, {
|
|
43
43
|
before: matchBefore,
|
|
44
44
|
after: matchAfter,
|
|
45
|
-
text:
|
|
46
|
-
cleanText: matchBefore.input.slice(matchBefore.index + matchBefore[0].length, matchBefore.
|
|
45
|
+
text: matchBefore.input.slice(matchBefore.index),
|
|
46
|
+
cleanText: matchBefore.input.slice(matchBefore.index + matchBefore[0].length, matchBefore.input.length - matchAfter[0].length)
|
|
47
47
|
});
|
|
48
48
|
}
|
|
49
49
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import isHotkey from 'is-hotkey';
|
|
2
2
|
import React, {
|
|
3
|
+
type ClipboardEvent,
|
|
3
4
|
isValidElement,
|
|
4
5
|
type JSX,
|
|
5
6
|
type KeyboardEvent,
|
|
@@ -7,7 +8,13 @@ import React, {
|
|
|
7
8
|
type ReactNode,
|
|
8
9
|
useCallback,
|
|
9
10
|
} from 'react';
|
|
10
|
-
import
|
|
11
|
+
import {
|
|
12
|
+
DecoratedRange,
|
|
13
|
+
Descendant,
|
|
14
|
+
Editor,
|
|
15
|
+
NodeEntry,
|
|
16
|
+
Transforms,
|
|
17
|
+
} from 'slate';
|
|
11
18
|
import {
|
|
12
19
|
Editable,
|
|
13
20
|
type RenderElementProps,
|
|
@@ -16,6 +23,7 @@ import {
|
|
|
16
23
|
} from 'slate-react';
|
|
17
24
|
import { BaseElement } from '../elements/BaseElement';
|
|
18
25
|
import type { IPlugin } from '../types';
|
|
26
|
+
import { deserialize } from './deserialize';
|
|
19
27
|
import styles from './styles.module.css';
|
|
20
28
|
|
|
21
29
|
const SUPPORTED_HANDLERS = ['onDrop', 'onKeyDown', 'onPaste'];
|
|
@@ -72,6 +80,10 @@ export const createEditable =
|
|
|
72
80
|
};
|
|
73
81
|
|
|
74
82
|
const handleHotkey = (event: KeyboardEvent) => {
|
|
83
|
+
if (event.key === 'Tab') {
|
|
84
|
+
event.preventDefault();
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
for (const plugin of plugins) {
|
|
76
88
|
if (plugin.hotkeys) {
|
|
77
89
|
for (const hotkey of plugin.hotkeys) {
|
|
@@ -84,6 +96,17 @@ export const createEditable =
|
|
|
84
96
|
}
|
|
85
97
|
};
|
|
86
98
|
|
|
99
|
+
const handlePaste = (event: ClipboardEvent<HTMLDivElement>) => {
|
|
100
|
+
const html = event.clipboardData?.getData('text/html');
|
|
101
|
+
if (!html) return;
|
|
102
|
+
|
|
103
|
+
const parsed = new DOMParser().parseFromString(html, 'text/html');
|
|
104
|
+
const value = deserialize(plugins)(parsed.body);
|
|
105
|
+
event.preventDefault();
|
|
106
|
+
|
|
107
|
+
Transforms.insertNodes(editor, value as Descendant);
|
|
108
|
+
};
|
|
109
|
+
|
|
87
110
|
const decorate = (entry: NodeEntry) => {
|
|
88
111
|
const decorators: IPlugin[] = plugins.filter(
|
|
89
112
|
(plugin) => !!plugin.decorate,
|
|
@@ -117,6 +140,7 @@ export const createEditable =
|
|
|
117
140
|
},
|
|
118
141
|
{
|
|
119
142
|
onKeyDown: handleHotkey,
|
|
143
|
+
onPaste: handlePaste,
|
|
120
144
|
},
|
|
121
145
|
);
|
|
122
146
|
|
package/src/core/createEditor.ts
CHANGED
|
@@ -119,7 +119,7 @@ export const createEditor = (plugins: IPlugin[]) => () => {
|
|
|
119
119
|
const result = matchedBlock.onBeforeDelete
|
|
120
120
|
? await matchedBlock?.onBeforeDelete?.([node])
|
|
121
121
|
: true;
|
|
122
|
-
if (result) {
|
|
122
|
+
if (result && Editor.isVoid(editorWithPlugins, node)) {
|
|
123
123
|
Transforms.removeNodes(editorWithPlugins, { at: path });
|
|
124
124
|
matchedBlock.onDelete?.([node]);
|
|
125
125
|
}
|
|
@@ -139,23 +139,23 @@ export const createEditor = (plugins: IPlugin[]) => () => {
|
|
|
139
139
|
}
|
|
140
140
|
|
|
141
141
|
if (Range.isCollapsed(selection)) {
|
|
142
|
-
const
|
|
142
|
+
const currentEntry = Editor.above<CustomElement>(editorWithPlugins, {
|
|
143
143
|
at: selection.anchor,
|
|
144
144
|
match: (n) => Editor.isBlock(editorWithPlugins, n as CustomElement),
|
|
145
145
|
mode: 'lowest',
|
|
146
146
|
});
|
|
147
|
-
const
|
|
147
|
+
const previousEntry = Editor.above<CustomElement>(editorWithPlugins, {
|
|
148
148
|
at: Editor.before(editorWithPlugins, selection.anchor),
|
|
149
149
|
match: (n) => Editor.isBlock(editorWithPlugins, n as CustomElement),
|
|
150
150
|
mode: 'lowest',
|
|
151
151
|
});
|
|
152
152
|
|
|
153
|
-
if (!
|
|
153
|
+
if (!currentEntry || !previousEntry) {
|
|
154
154
|
return;
|
|
155
155
|
}
|
|
156
156
|
|
|
157
|
-
const [_, currentPath] =
|
|
158
|
-
const [node, path] =
|
|
157
|
+
const [_, currentPath] = currentEntry;
|
|
158
|
+
const [node, path] = previousEntry;
|
|
159
159
|
|
|
160
160
|
if (
|
|
161
161
|
node &&
|
|
@@ -170,11 +170,11 @@ export const createEditor = (plugins: IPlugin[]) => () => {
|
|
|
170
170
|
const result = matchedBlock.onBeforeDelete
|
|
171
171
|
? await matchedBlock.onBeforeDelete([node])
|
|
172
172
|
: true;
|
|
173
|
-
if (result) {
|
|
173
|
+
if (result && Editor.isVoid(editorWithPlugins, node)) {
|
|
174
174
|
Transforms.removeNodes(editorWithPlugins, { at: path });
|
|
175
175
|
matchedBlock.onDelete?.([node]);
|
|
176
|
+
return;
|
|
176
177
|
}
|
|
177
|
-
return;
|
|
178
178
|
}
|
|
179
179
|
}
|
|
180
180
|
}
|
package/src/core/deserialize.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import { type Descendant, Element
|
|
1
|
+
import { type Descendant, Element } from 'slate';
|
|
2
2
|
import { jsx } from 'slate-hyperscript';
|
|
3
|
-
import { CustomElement, CustomText } from '../../types';
|
|
4
3
|
import type { IPlugin } from '../types';
|
|
5
4
|
|
|
6
5
|
export const deserialize =
|
|
@@ -36,7 +35,8 @@ export const deserialize =
|
|
|
36
35
|
return jsx('element', { type: 'paragraph' }, children);
|
|
37
36
|
}
|
|
38
37
|
|
|
39
|
-
let result:
|
|
38
|
+
let result: (Descendant | string)[] | string | Descendant | null = null;
|
|
39
|
+
|
|
40
40
|
for (const plugin of plugins) {
|
|
41
41
|
if (plugin.blocks?.some((b) => b.deserialize)) {
|
|
42
42
|
plugin.blocks.forEach((e) => {
|
|
@@ -61,7 +61,9 @@ export const deserialize =
|
|
|
61
61
|
| string
|
|
62
62
|
| Descendant
|
|
63
63
|
)[];
|
|
64
|
+
|
|
64
65
|
const newResult = e.deserialize(element, childrenAsDescendants);
|
|
66
|
+
|
|
65
67
|
if (newResult) {
|
|
66
68
|
result = newResult;
|
|
67
69
|
}
|
package/src/core/queries.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
|
-
import { Editor, NodeEntry,
|
|
1
|
+
import { Editor, type NodeEntry, Path } from 'slate';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated
|
|
5
|
+
*/
|
|
3
6
|
export const getPrev = (editor: Editor, node: NodeEntry) => {
|
|
4
7
|
try {
|
|
5
8
|
const [, path] = node;
|
|
6
|
-
return
|
|
9
|
+
return Editor.above(editor, {
|
|
10
|
+
at: Path.previous(path),
|
|
11
|
+
mode: 'lowest',
|
|
12
|
+
});
|
|
7
13
|
} catch (e) {
|
|
8
14
|
return null;
|
|
9
15
|
}
|
|
@@ -42,6 +42,7 @@ import 'prismjs/components/prism-bash';
|
|
|
42
42
|
import 'prismjs/components/prism-sql';
|
|
43
43
|
import 'prismjs/components/prism-yaml';
|
|
44
44
|
import 'prismjs/components/prism-markdown';
|
|
45
|
+
import { jsx } from 'slate-hyperscript';
|
|
45
46
|
|
|
46
47
|
type Options = {
|
|
47
48
|
renderCodeBlock: (
|
|
@@ -211,6 +212,21 @@ export class CodeBlockPlugin implements IPlugin {
|
|
|
211
212
|
</>
|
|
212
213
|
);
|
|
213
214
|
},
|
|
215
|
+
deserialize: (el: HTMLElement, children) => {
|
|
216
|
+
const { nodeName } = el;
|
|
217
|
+
|
|
218
|
+
if (nodeName === 'PRE') {
|
|
219
|
+
const lines = children.join('').split('\n');
|
|
220
|
+
|
|
221
|
+
return jsx(
|
|
222
|
+
'element',
|
|
223
|
+
{ type: CodeBlockPlugin.CODE_ELEMENT },
|
|
224
|
+
lines.map((line) =>
|
|
225
|
+
jsx('element', { type: CodeBlockPlugin.CODE_LINE_ELEMENT }, line),
|
|
226
|
+
),
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
},
|
|
214
230
|
},
|
|
215
231
|
{
|
|
216
232
|
type: CodeBlockPlugin.CODE_LINE_ELEMENT,
|
|
@@ -2,7 +2,6 @@ import isUrl from 'is-url';
|
|
|
2
2
|
import { Editor, Element, Range, Transforms } from 'slate';
|
|
3
3
|
import { jsx } from 'slate-hyperscript';
|
|
4
4
|
import type { RenderElementProps } from 'slate-react';
|
|
5
|
-
import { deserialize } from '../../core/deserialize';
|
|
6
5
|
import type { IPlugin } from '../../types';
|
|
7
6
|
import { LINK_ELEMENT } from './constants';
|
|
8
7
|
import { Link } from './Link';
|
|
@@ -88,6 +88,33 @@ export class ListsPlugin implements IPlugin {
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
+
if (this.isList(editor, node as CustomElement)) {
|
|
92
|
+
const prevListItemPath = Editor.before(editor, path, {
|
|
93
|
+
unit: 'block',
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
const prevList = Editor.above(editor, {
|
|
97
|
+
at: prevListItemPath,
|
|
98
|
+
match: (n) => this.isList(editor, n as CustomElement),
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (prevList) {
|
|
102
|
+
const currentDepth = this.getListDepth(editor, path);
|
|
103
|
+
const prevDepth = this.getListDepth(editor, prevList[1]);
|
|
104
|
+
|
|
105
|
+
if (
|
|
106
|
+
this.isList(editor, prevList[0] as CustomElement) &&
|
|
107
|
+
currentDepth === prevDepth
|
|
108
|
+
) {
|
|
109
|
+
try {
|
|
110
|
+
Transforms.mergeNodes(editor, {
|
|
111
|
+
match: (n) => this.isList(editor, n as CustomElement),
|
|
112
|
+
});
|
|
113
|
+
} catch (e) {}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
91
118
|
normalizeNode(entry);
|
|
92
119
|
};
|
|
93
120
|
|
|
@@ -249,17 +276,25 @@ export class ListsPlugin implements IPlugin {
|
|
|
249
276
|
return;
|
|
250
277
|
}
|
|
251
278
|
|
|
252
|
-
const
|
|
279
|
+
const prevListItemPath = Editor.before(editor, currentListItem[1], {
|
|
280
|
+
unit: 'block',
|
|
281
|
+
});
|
|
253
282
|
|
|
254
|
-
if (
|
|
255
|
-
|
|
256
|
-
|
|
283
|
+
if (currentListItem && prevListItemPath) {
|
|
284
|
+
const prevListItem = Editor.above(editor, {
|
|
285
|
+
at: prevListItemPath,
|
|
286
|
+
match: (n) => this.isListItem(editor, n as CustomElement),
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
if (!prevListItem) {
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
257
292
|
|
|
258
|
-
if (currentListItem && prevListItem) {
|
|
259
293
|
const currentDepth = this.getListItemDepth(
|
|
260
294
|
editor,
|
|
261
295
|
currentListItem[1],
|
|
262
296
|
);
|
|
297
|
+
|
|
263
298
|
const prevDepth = this.getListItemDepth(editor, prevListItem[1]);
|
|
264
299
|
|
|
265
300
|
if (prevListItem && prevDepth >= currentDepth) {
|
|
@@ -370,17 +405,37 @@ export class ListsPlugin implements IPlugin {
|
|
|
370
405
|
if (item) {
|
|
371
406
|
let count = 0;
|
|
372
407
|
let parent = Editor.parent(editor, item[1]);
|
|
373
|
-
|
|
408
|
+
do {
|
|
409
|
+
parent = Editor.parent(editor, parent[1]);
|
|
410
|
+
count++;
|
|
411
|
+
} while (
|
|
374
412
|
parent !== null &&
|
|
375
413
|
this.isList(editor, parent[0] as CustomElement)
|
|
376
|
-
)
|
|
377
|
-
|
|
414
|
+
);
|
|
415
|
+
|
|
416
|
+
return count;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return 0;
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
private getListDepth = (editor: Editor, path?: Path) => {
|
|
423
|
+
if (path) {
|
|
424
|
+
const node = Node.get(editor, path) as CustomElement;
|
|
425
|
+
if (!this.isList(editor, node)) {
|
|
426
|
+
return 0;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let count = 0;
|
|
430
|
+
let parent = Editor.parent(editor, path);
|
|
431
|
+
|
|
432
|
+
while (parent && this.isList(editor, parent[0] as CustomElement)) {
|
|
378
433
|
count++;
|
|
434
|
+
parent = Editor.parent(editor, parent[1]);
|
|
379
435
|
}
|
|
380
436
|
|
|
381
437
|
return count;
|
|
382
438
|
}
|
|
383
|
-
|
|
384
439
|
return 0;
|
|
385
440
|
};
|
|
386
441
|
|
|
@@ -67,7 +67,7 @@ export class ShortcutsPlugin implements IPlugin {
|
|
|
67
67
|
);
|
|
68
68
|
|
|
69
69
|
// Check if the text before cursor matches the pattern
|
|
70
|
-
matchBefore = new RegExp(shortcut.before.source
|
|
70
|
+
matchBefore = new RegExp(`${shortcut.before.source}`, 'g').exec(
|
|
71
71
|
textBeforeCursor,
|
|
72
72
|
);
|
|
73
73
|
}
|
|
@@ -77,7 +77,7 @@ export class ShortcutsPlugin implements IPlugin {
|
|
|
77
77
|
const currentPoint = Range.start(selection);
|
|
78
78
|
const [currentNode] = Editor.node(editor, currentPoint.path);
|
|
79
79
|
const textBetweenMatches = Node.string(currentNode).slice(
|
|
80
|
-
matchBefore.index,
|
|
80
|
+
matchBefore.index + matchBefore[0].length,
|
|
81
81
|
currentPoint.offset,
|
|
82
82
|
);
|
|
83
83
|
|
|
@@ -110,13 +110,10 @@ export class ShortcutsPlugin implements IPlugin {
|
|
|
110
110
|
shortcut.change(editor, {
|
|
111
111
|
before: matchBefore,
|
|
112
112
|
after: matchAfter,
|
|
113
|
-
text:
|
|
113
|
+
text: matchBefore.input.slice(matchBefore.index),
|
|
114
114
|
cleanText: matchBefore.input.slice(
|
|
115
115
|
matchBefore.index + matchBefore[0].length,
|
|
116
|
-
matchBefore.
|
|
117
|
-
matchBefore[0].length +
|
|
118
|
-
matchAfter.index -
|
|
119
|
-
matchAfter[0].length,
|
|
116
|
+
matchBefore.input.length - matchAfter[0].length,
|
|
120
117
|
),
|
|
121
118
|
});
|
|
122
119
|
return;
|