@sveltia/ui 0.41.0 → 0.41.2
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/components/text-editor/constants.d.ts +28 -1
- package/dist/components/text-editor/constants.js +128 -1
- package/dist/components/text-editor/core.d.ts +17 -5
- package/dist/components/text-editor/core.js +116 -143
- package/dist/components/text-editor/lexical-root.svelte +4 -3
- package/dist/components/text-editor/store.svelte.js +11 -2
- package/dist/components/text-editor/text-editor.svelte +2 -7
- package/dist/components/text-editor/text-editor.svelte.d.ts +3 -4
- package/dist/components/text-editor/toolbar/text-editor-toolbar.svelte +21 -18
- package/dist/typedefs.d.ts +6 -1
- package/dist/typedefs.js +6 -1
- package/package.json +10 -10
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import {
|
|
2
|
+
* @import { EditorThemeClasses } from 'lexical';
|
|
3
|
+
* @import { Transformer } from '@lexical/markdown';
|
|
4
|
+
* @import {
|
|
5
|
+
* TextEditorBlockType,
|
|
6
|
+
* TextEditorFormatType,
|
|
7
|
+
* TextEditorInlineType,
|
|
8
|
+
* TextEditorNodeType,
|
|
9
|
+
* } from '../../typedefs';
|
|
3
10
|
*/
|
|
11
|
+
export const PRISM_BASE_URL: "https://unpkg.com/prismjs@1.30.0";
|
|
12
|
+
/**
|
|
13
|
+
* @type {EditorThemeClasses}
|
|
14
|
+
*/
|
|
15
|
+
export const EDITOR_THEME: EditorThemeClasses;
|
|
4
16
|
/**
|
|
5
17
|
* List of available buttons.
|
|
6
18
|
* @type {{ [key: string]: { labelKey: string, icon: string, inline: boolean } }}
|
|
@@ -28,6 +40,21 @@ export const BLOCK_BUTTON_TYPES: TextEditorBlockType[];
|
|
|
28
40
|
* Image related components IDs. `linked-image` is used in Sveltia CMS.
|
|
29
41
|
*/
|
|
30
42
|
export const IMAGE_COMPONENT_IDS: string[];
|
|
43
|
+
/**
|
|
44
|
+
* Map of Lexical nodes for each block type. The `paragraph` block type is excluded because it is
|
|
45
|
+
* the default block type.
|
|
46
|
+
* @type {Record<Exclude<TextEditorBlockType | 'link', 'paragraph'>, any[]>}
|
|
47
|
+
*/
|
|
48
|
+
export const NODE_MAP: Record<Exclude<TextEditorBlockType | "link", "paragraph">, any[]>;
|
|
49
|
+
/**
|
|
50
|
+
* Map of Lexical transformers for each block type. The `paragraph` block type is excluded because
|
|
51
|
+
* it is the default block type.
|
|
52
|
+
* @type {Record<Exclude<TextEditorNodeType, 'paragraph'>, Transformer[]>}
|
|
53
|
+
*/
|
|
54
|
+
export const TRANSFORMER_MAP: Record<Exclude<TextEditorNodeType, "paragraph">, Transformer[]>;
|
|
55
|
+
import type { EditorThemeClasses } from 'lexical';
|
|
31
56
|
import type { TextEditorFormatType } from '../../typedefs';
|
|
32
57
|
import type { TextEditorInlineType } from '../../typedefs';
|
|
33
58
|
import type { TextEditorBlockType } from '../../typedefs';
|
|
59
|
+
import type { TextEditorNodeType } from '../../typedefs';
|
|
60
|
+
import type { Transformer } from '@lexical/markdown';
|
|
@@ -1,6 +1,87 @@
|
|
|
1
|
+
import { CodeHighlightNode, CodeNode } from '@lexical/code';
|
|
2
|
+
import { LinkNode } from '@lexical/link';
|
|
3
|
+
import { ListItemNode, ListNode } from '@lexical/list';
|
|
4
|
+
import {
|
|
5
|
+
BOLD_ITALIC_STAR,
|
|
6
|
+
BOLD_ITALIC_UNDERSCORE,
|
|
7
|
+
BOLD_STAR,
|
|
8
|
+
CODE,
|
|
9
|
+
HEADING,
|
|
10
|
+
INLINE_CODE,
|
|
11
|
+
ITALIC_UNDERSCORE,
|
|
12
|
+
LINK,
|
|
13
|
+
ORDERED_LIST,
|
|
14
|
+
QUOTE,
|
|
15
|
+
STRIKETHROUGH,
|
|
16
|
+
UNORDERED_LIST,
|
|
17
|
+
} from '@lexical/markdown';
|
|
18
|
+
import { HeadingNode, QuoteNode } from '@lexical/rich-text';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @import { EditorThemeClasses } from 'lexical';
|
|
22
|
+
* @import { Transformer } from '@lexical/markdown';
|
|
23
|
+
* @import {
|
|
24
|
+
* TextEditorBlockType,
|
|
25
|
+
* TextEditorFormatType,
|
|
26
|
+
* TextEditorInlineType,
|
|
27
|
+
* TextEditorNodeType,
|
|
28
|
+
* } from '../../typedefs';
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
export const PRISM_BASE_URL = `https://unpkg.com/prismjs@1.30.0`;
|
|
32
|
+
|
|
1
33
|
/**
|
|
2
|
-
* @
|
|
34
|
+
* @type {EditorThemeClasses}
|
|
3
35
|
*/
|
|
36
|
+
export const EDITOR_THEME = {
|
|
37
|
+
text: {
|
|
38
|
+
/**
|
|
39
|
+
* Enable bold+italic and strikethrough styling.
|
|
40
|
+
* @see https://github.com/facebook/lexical/discussions/4381
|
|
41
|
+
*/
|
|
42
|
+
italic: 'italic',
|
|
43
|
+
strikethrough: 'strikethrough',
|
|
44
|
+
},
|
|
45
|
+
list: {
|
|
46
|
+
nested: {
|
|
47
|
+
listitem: 'nested',
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
code: 'code-block',
|
|
51
|
+
// https://github.com/facebook/lexical/blob/main/packages/lexical-website/docs/getting-started/theming.md
|
|
52
|
+
codeHighlight: {
|
|
53
|
+
atrule: 'token atrule',
|
|
54
|
+
attr: 'token attr',
|
|
55
|
+
boolean: 'token boolean',
|
|
56
|
+
builtin: 'token builtin',
|
|
57
|
+
cdata: 'token cdata',
|
|
58
|
+
char: 'token char',
|
|
59
|
+
class: 'token class',
|
|
60
|
+
'class-name': 'token class-name',
|
|
61
|
+
comment: 'token comment',
|
|
62
|
+
constant: 'token constant',
|
|
63
|
+
deleted: 'token deleted',
|
|
64
|
+
doctype: 'token doctype',
|
|
65
|
+
entity: 'token entity',
|
|
66
|
+
function: 'token function',
|
|
67
|
+
important: 'token important',
|
|
68
|
+
inserted: 'token inserted',
|
|
69
|
+
keyword: 'token keyword',
|
|
70
|
+
namespace: 'token namespace',
|
|
71
|
+
number: 'token number',
|
|
72
|
+
operator: 'token operator',
|
|
73
|
+
prolog: 'token prolog',
|
|
74
|
+
property: 'token property',
|
|
75
|
+
punctuation: 'token punctuation',
|
|
76
|
+
regex: 'token regex',
|
|
77
|
+
selector: 'token selector',
|
|
78
|
+
string: 'token string',
|
|
79
|
+
symbol: 'token symbol',
|
|
80
|
+
tag: 'token tag',
|
|
81
|
+
url: 'token url',
|
|
82
|
+
variable: 'token variable',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
4
85
|
|
|
5
86
|
/**
|
|
6
87
|
* List of available buttons.
|
|
@@ -120,3 +201,49 @@ export const BLOCK_BUTTON_TYPES = [
|
|
|
120
201
|
* Image related components IDs. `linked-image` is used in Sveltia CMS.
|
|
121
202
|
*/
|
|
122
203
|
export const IMAGE_COMPONENT_IDS = ['image', 'linked-image'];
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Map of Lexical nodes for each block type. The `paragraph` block type is excluded because it is
|
|
207
|
+
* the default block type.
|
|
208
|
+
* @type {Record<Exclude<TextEditorBlockType | 'link', 'paragraph'>, any[]>}
|
|
209
|
+
*/
|
|
210
|
+
export const NODE_MAP = {
|
|
211
|
+
'heading-1': [HeadingNode],
|
|
212
|
+
'heading-2': [HeadingNode],
|
|
213
|
+
'heading-3': [HeadingNode],
|
|
214
|
+
'heading-4': [HeadingNode],
|
|
215
|
+
'heading-5': [HeadingNode],
|
|
216
|
+
'heading-6': [HeadingNode],
|
|
217
|
+
'bulleted-list': [ListNode, ListItemNode],
|
|
218
|
+
'numbered-list': [ListNode, ListItemNode],
|
|
219
|
+
blockquote: [QuoteNode],
|
|
220
|
+
'code-block': [CodeNode, CodeHighlightNode],
|
|
221
|
+
link: [LinkNode],
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Map of Lexical transformers for each block type. The `paragraph` block type is excluded because
|
|
226
|
+
* it is the default block type.
|
|
227
|
+
* @type {Record<Exclude<TextEditorNodeType, 'paragraph'>, Transformer[]>}
|
|
228
|
+
*/
|
|
229
|
+
export const TRANSFORMER_MAP = {
|
|
230
|
+
// ELEMENT_TRANSFORMERS
|
|
231
|
+
'heading-1': [HEADING],
|
|
232
|
+
'heading-2': [HEADING],
|
|
233
|
+
'heading-3': [HEADING],
|
|
234
|
+
'heading-4': [HEADING],
|
|
235
|
+
'heading-5': [HEADING],
|
|
236
|
+
'heading-6': [HEADING],
|
|
237
|
+
'bulleted-list': [UNORDERED_LIST],
|
|
238
|
+
'numbered-list': [ORDERED_LIST],
|
|
239
|
+
blockquote: [QUOTE],
|
|
240
|
+
// MULTILINE_ELEMENT_TRANSFORMERS
|
|
241
|
+
'code-block': [CODE],
|
|
242
|
+
// TEXT_FORMAT_TRANSFORMERS
|
|
243
|
+
code: [INLINE_CODE],
|
|
244
|
+
bold: [BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR], // Exclude BOLD_UNDERSCORE
|
|
245
|
+
italic: [BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, ITALIC_UNDERSCORE], // Exclude ITALIC_STAR
|
|
246
|
+
strikethrough: [STRIKETHROUGH],
|
|
247
|
+
// TEXT_MATCH_TRANSFORMERS
|
|
248
|
+
link: [LINK],
|
|
249
|
+
};
|
|
@@ -1,12 +1,24 @@
|
|
|
1
1
|
export function getSelectionTypes(): TextEditorSelectionState;
|
|
2
|
-
export function onEditorUpdate(editor: LexicalEditor): void;
|
|
3
|
-
export function initEditor({ components, useMarkdownShortcuts, isCodeEditor, defaultLanguage, }: TextEditorConfig):
|
|
2
|
+
export function onEditorUpdate(editor: LexicalEditor, enabledTransformers: Transformer[]): void;
|
|
3
|
+
export function initEditor({ enabledButtons, components, useMarkdownShortcuts, isCodeEditor, defaultLanguage, }: TextEditorConfig): InitEditorResult;
|
|
4
|
+
export function loadCodeHighlighter(lang: string): Promise<void>;
|
|
5
|
+
export function convertMarkdownToLexical(editor: LexicalEditor, value: string, enabledTransformers: Transformer[]): Promise<void>;
|
|
6
|
+
export function focusEditor(editor: LexicalEditor): Promise<void>;
|
|
7
|
+
export type InitEditorResult = {
|
|
8
|
+
/**
|
|
9
|
+
* Editor instance.
|
|
10
|
+
*/
|
|
4
11
|
editor: LexicalEditor;
|
|
12
|
+
/**
|
|
13
|
+
* List of enabled Markdown transformers.
|
|
14
|
+
*/
|
|
15
|
+
enabledTransformers: Transformer[];
|
|
16
|
+
/**
|
|
17
|
+
* Remove all registered Lexical listeners.
|
|
18
|
+
*/
|
|
5
19
|
dispose: () => void;
|
|
6
20
|
};
|
|
7
|
-
export function loadCodeHighlighter(lang: string): Promise<void>;
|
|
8
|
-
export function convertMarkdownToLexical(editor: LexicalEditor, value: string): Promise<void>;
|
|
9
|
-
export function focusEditor(editor: LexicalEditor): Promise<void>;
|
|
10
21
|
import type { TextEditorSelectionState } from '../../typedefs';
|
|
11
22
|
import type { LexicalEditor } from 'lexical';
|
|
23
|
+
import type { Transformer } from '@lexical/markdown';
|
|
12
24
|
import type { TextEditorConfig } from '../../typedefs';
|
|
@@ -3,8 +3,6 @@
|
|
|
3
3
|
import 'prismjs';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
CodeHighlightNode,
|
|
7
|
-
CodeNode,
|
|
8
6
|
$createCodeNode as createCodeNode,
|
|
9
7
|
$isCodeHighlightNode as isCodeHighlightNode,
|
|
10
8
|
$isCodeNode as isCodeNode,
|
|
@@ -15,7 +13,6 @@ import { HorizontalRuleNode } from '@lexical/extension';
|
|
|
15
13
|
import { createEmptyHistoryState, registerHistory } from '@lexical/history';
|
|
16
14
|
import {
|
|
17
15
|
$isLinkNode as isLinkNode,
|
|
18
|
-
LinkNode,
|
|
19
16
|
TOGGLE_LINK_COMMAND,
|
|
20
17
|
$toggleLink as toggleLink,
|
|
21
18
|
} from '@lexical/link';
|
|
@@ -26,20 +23,16 @@ import {
|
|
|
26
23
|
$insertList as insertList,
|
|
27
24
|
$isListItemNode as isListItemNode,
|
|
28
25
|
$isListNode as isListNode,
|
|
29
|
-
ListItemNode,
|
|
30
26
|
ListNode,
|
|
31
27
|
} from '@lexical/list';
|
|
32
28
|
import {
|
|
33
29
|
$convertFromMarkdownString as convertFromMarkdownString,
|
|
34
30
|
$convertToMarkdownString as convertToMarkdownString,
|
|
35
31
|
registerMarkdownShortcuts,
|
|
36
|
-
TRANSFORMERS,
|
|
37
32
|
} from '@lexical/markdown';
|
|
38
33
|
import {
|
|
39
|
-
HeadingNode,
|
|
40
34
|
$isHeadingNode as isHeadingNode,
|
|
41
35
|
$isQuoteNode as isQuoteNode,
|
|
42
|
-
QuoteNode,
|
|
43
36
|
registerRichText,
|
|
44
37
|
} from '@lexical/rich-text';
|
|
45
38
|
import { TableCellNode, TableNode, TableRowNode } from '@lexical/table';
|
|
@@ -57,93 +50,36 @@ import {
|
|
|
57
50
|
OUTDENT_CONTENT_COMMAND,
|
|
58
51
|
} from 'lexical';
|
|
59
52
|
import prismComponents from 'prismjs/components';
|
|
60
|
-
import {
|
|
53
|
+
import {
|
|
54
|
+
BLOCK_BUTTON_TYPES,
|
|
55
|
+
EDITOR_THEME,
|
|
56
|
+
NODE_MAP,
|
|
57
|
+
PRISM_BASE_URL,
|
|
58
|
+
TEXT_FORMAT_BUTTON_TYPES,
|
|
59
|
+
TRANSFORMER_MAP,
|
|
60
|
+
} from './constants.js';
|
|
61
61
|
import { increaseListIndentation, splitMultilineFormatting } from './markdown.js';
|
|
62
62
|
import { HR } from './transformers/hr.js';
|
|
63
63
|
import { TABLE } from './transformers/table.js';
|
|
64
64
|
|
|
65
65
|
/**
|
|
66
66
|
* @import { CreateEditorArgs, LexicalEditor } from 'lexical';
|
|
67
|
+
* @import { Transformer } from '@lexical/markdown';
|
|
67
68
|
* @import {
|
|
68
69
|
* TextEditorBlockType,
|
|
69
70
|
* TextEditorConfig,
|
|
70
71
|
* TextEditorInlineType,
|
|
72
|
+
* TextEditorNodeType,
|
|
71
73
|
* TextEditorSelectionState,
|
|
72
74
|
* } from '../../typedefs';
|
|
73
75
|
*/
|
|
74
76
|
|
|
75
|
-
const allTransformers = [...TRANSFORMERS, HR, TABLE];
|
|
76
|
-
const prismBaseURL = `https://unpkg.com/prismjs@1.30.0`;
|
|
77
|
-
|
|
78
77
|
/**
|
|
79
|
-
*
|
|
80
|
-
* @
|
|
78
|
+
* @typedef {object} InitEditorResult
|
|
79
|
+
* @property {LexicalEditor} editor Editor instance.
|
|
80
|
+
* @property {Transformer[]} enabledTransformers List of enabled Markdown transformers.
|
|
81
|
+
* @property {() => void} dispose Remove all registered Lexical listeners.
|
|
81
82
|
*/
|
|
82
|
-
const editorConfig = {
|
|
83
|
-
namespace: 'editor',
|
|
84
|
-
nodes: [
|
|
85
|
-
HeadingNode,
|
|
86
|
-
QuoteNode,
|
|
87
|
-
LinkNode,
|
|
88
|
-
ListNode,
|
|
89
|
-
ListItemNode,
|
|
90
|
-
CodeNode,
|
|
91
|
-
CodeHighlightNode,
|
|
92
|
-
HorizontalRuleNode,
|
|
93
|
-
TableNode,
|
|
94
|
-
TableCellNode,
|
|
95
|
-
TableRowNode,
|
|
96
|
-
],
|
|
97
|
-
theme: {
|
|
98
|
-
text: {
|
|
99
|
-
/**
|
|
100
|
-
* Enable bold+italic and strikethrough styling.
|
|
101
|
-
* @see https://github.com/facebook/lexical/discussions/4381
|
|
102
|
-
*/
|
|
103
|
-
italic: 'italic',
|
|
104
|
-
strikethrough: 'strikethrough',
|
|
105
|
-
},
|
|
106
|
-
list: {
|
|
107
|
-
nested: {
|
|
108
|
-
listitem: 'nested',
|
|
109
|
-
},
|
|
110
|
-
},
|
|
111
|
-
code: 'code-block',
|
|
112
|
-
// https://github.com/facebook/lexical/blob/main/packages/lexical-website/docs/getting-started/theming.md
|
|
113
|
-
codeHighlight: {
|
|
114
|
-
atrule: 'token atrule',
|
|
115
|
-
attr: 'token attr',
|
|
116
|
-
boolean: 'token boolean',
|
|
117
|
-
builtin: 'token builtin',
|
|
118
|
-
cdata: 'token cdata',
|
|
119
|
-
char: 'token char',
|
|
120
|
-
class: 'token class',
|
|
121
|
-
'class-name': 'token class-name',
|
|
122
|
-
comment: 'token comment',
|
|
123
|
-
constant: 'token constant',
|
|
124
|
-
deleted: 'token deleted',
|
|
125
|
-
doctype: 'token doctype',
|
|
126
|
-
entity: 'token entity',
|
|
127
|
-
function: 'token function',
|
|
128
|
-
important: 'token important',
|
|
129
|
-
inserted: 'token inserted',
|
|
130
|
-
keyword: 'token keyword',
|
|
131
|
-
namespace: 'token namespace',
|
|
132
|
-
number: 'token number',
|
|
133
|
-
operator: 'token operator',
|
|
134
|
-
prolog: 'token prolog',
|
|
135
|
-
property: 'token property',
|
|
136
|
-
punctuation: 'token punctuation',
|
|
137
|
-
regex: 'token regex',
|
|
138
|
-
selector: 'token selector',
|
|
139
|
-
string: 'token string',
|
|
140
|
-
symbol: 'token symbol',
|
|
141
|
-
tag: 'token tag',
|
|
142
|
-
url: 'token url',
|
|
143
|
-
variable: 'token variable',
|
|
144
|
-
},
|
|
145
|
-
},
|
|
146
|
-
};
|
|
147
83
|
|
|
148
84
|
/**
|
|
149
85
|
* Get the current selection’s block node key as well as block and inline level types.
|
|
@@ -223,15 +159,14 @@ export const getSelectionTypes = () => {
|
|
|
223
159
|
* Listen to changes made on the editor and trigger the Update event.
|
|
224
160
|
* @internal
|
|
225
161
|
* @param {LexicalEditor} editor Editor instance.
|
|
162
|
+
* @param {Transformer[]} enabledTransformers Enabled Markdown transformers.
|
|
226
163
|
*/
|
|
227
|
-
export const onEditorUpdate = (editor) => {
|
|
164
|
+
export const onEditorUpdate = (editor, enabledTransformers) => {
|
|
228
165
|
editor.getRootElement()?.dispatchEvent(
|
|
229
166
|
new CustomEvent('Update', {
|
|
230
167
|
detail: {
|
|
231
|
-
value: convertToMarkdownString(
|
|
232
|
-
//
|
|
233
|
-
allTransformers.filter((/** @type {any} */ { tag }) => tag !== '*'),
|
|
234
|
-
) // Remove unnecessary backslash for underscore and backslash characters
|
|
168
|
+
value: convertToMarkdownString(enabledTransformers)
|
|
169
|
+
// Remove unnecessary backslash for underscore and backslash characters
|
|
235
170
|
// @see https://github.com/sveltia/sveltia-cms/issues/430
|
|
236
171
|
// @see https://github.com/sveltia/sveltia-cms/issues/512
|
|
237
172
|
.replace(/\\([_\\])/g, '$1')
|
|
@@ -249,18 +184,44 @@ export const onEditorUpdate = (editor) => {
|
|
|
249
184
|
/**
|
|
250
185
|
* Initialize the Lexical editor.
|
|
251
186
|
* @param {TextEditorConfig} config Editor configuration.
|
|
252
|
-
* @returns {
|
|
187
|
+
* @returns {InitEditorResult} Editor instance and cleanup.
|
|
253
188
|
*/
|
|
254
189
|
export const initEditor = ({
|
|
190
|
+
enabledButtons = [],
|
|
255
191
|
components = [],
|
|
256
192
|
useMarkdownShortcuts,
|
|
257
193
|
isCodeEditor = false,
|
|
258
194
|
defaultLanguage = 'plain',
|
|
259
195
|
}) => {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
196
|
+
/** @type {CreateEditorArgs} */
|
|
197
|
+
const editorConfig = {
|
|
198
|
+
namespace: 'editor',
|
|
199
|
+
nodes: [
|
|
200
|
+
...components.map(({ node }) => node),
|
|
201
|
+
...new Set(
|
|
202
|
+
Object.entries(NODE_MAP)
|
|
203
|
+
.filter(([button]) => enabledButtons.includes(/** @type {TextEditorNodeType} */ (button)))
|
|
204
|
+
.flatMap(([, nodes]) => nodes),
|
|
205
|
+
),
|
|
206
|
+
HorizontalRuleNode,
|
|
207
|
+
TableNode,
|
|
208
|
+
TableCellNode,
|
|
209
|
+
TableRowNode,
|
|
210
|
+
],
|
|
211
|
+
theme: EDITOR_THEME,
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** @type {Transformer[]} */
|
|
215
|
+
const enabledTransformers = [
|
|
216
|
+
...components.map(({ transformer }) => transformer),
|
|
217
|
+
...new Set(
|
|
218
|
+
Object.entries(TRANSFORMER_MAP)
|
|
219
|
+
.filter(([button]) => enabledButtons.includes(/** @type {TextEditorNodeType} */ (button)))
|
|
220
|
+
.flatMap(([, transformers]) => transformers),
|
|
221
|
+
),
|
|
222
|
+
HR,
|
|
223
|
+
TABLE,
|
|
224
|
+
];
|
|
264
225
|
|
|
265
226
|
const editor = createEditor(editorConfig);
|
|
266
227
|
/** @type {Array<() => void>} */
|
|
@@ -282,63 +243,73 @@ export const initEditor = ({
|
|
|
282
243
|
addUnregister(registerHistory(editor, createEmptyHistoryState(), 1000));
|
|
283
244
|
|
|
284
245
|
if (useMarkdownShortcuts) {
|
|
285
|
-
addUnregister(registerMarkdownShortcuts(editor,
|
|
246
|
+
addUnregister(registerMarkdownShortcuts(editor, enabledTransformers));
|
|
286
247
|
}
|
|
287
248
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
editor.registerCommand(
|
|
300
|
-
TOGGLE_LINK_COMMAND,
|
|
301
|
-
(payload) => {
|
|
302
|
-
toggleLink(typeof payload === 'string' ? payload : null);
|
|
303
|
-
|
|
304
|
-
return true;
|
|
305
|
-
},
|
|
306
|
-
COMMAND_PRIORITY_NORMAL,
|
|
307
|
-
),
|
|
308
|
-
);
|
|
309
|
-
|
|
310
|
-
addUnregister(
|
|
311
|
-
editor.registerCommand(
|
|
312
|
-
INSERT_UNORDERED_LIST_COMMAND,
|
|
313
|
-
() => {
|
|
314
|
-
insertList('bullet');
|
|
249
|
+
if (enabledButtons.includes('code-block') || isCodeEditor) {
|
|
250
|
+
addUnregister(
|
|
251
|
+
registerCodeHighlighting(editor, {
|
|
252
|
+
defaultLanguage,
|
|
253
|
+
// eslint-disable-next-line jsdoc/require-jsdoc
|
|
254
|
+
tokenize: (code, lang = 'plain') =>
|
|
255
|
+
window.Prism.tokenize(code, window.Prism.languages[lang] ?? window.Prism.languages.plain),
|
|
256
|
+
$tokenize: PrismTokenizer.$tokenize,
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
}
|
|
315
260
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
261
|
+
if (enabledButtons.includes('link')) {
|
|
262
|
+
addUnregister(
|
|
263
|
+
editor.registerCommand(
|
|
264
|
+
TOGGLE_LINK_COMMAND,
|
|
265
|
+
(payload) => {
|
|
266
|
+
toggleLink(typeof payload === 'string' ? payload : null);
|
|
267
|
+
|
|
268
|
+
return true;
|
|
269
|
+
},
|
|
270
|
+
COMMAND_PRIORITY_NORMAL,
|
|
271
|
+
),
|
|
272
|
+
);
|
|
273
|
+
}
|
|
321
274
|
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
275
|
+
if (enabledButtons.includes('bulleted-list')) {
|
|
276
|
+
addUnregister(
|
|
277
|
+
editor.registerCommand(
|
|
278
|
+
INSERT_UNORDERED_LIST_COMMAND,
|
|
279
|
+
() => {
|
|
280
|
+
insertList('bullet');
|
|
281
|
+
|
|
282
|
+
return true;
|
|
283
|
+
},
|
|
284
|
+
COMMAND_PRIORITY_NORMAL,
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
327
288
|
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
289
|
+
if (enabledButtons.includes('numbered-list')) {
|
|
290
|
+
addUnregister(
|
|
291
|
+
editor.registerCommand(
|
|
292
|
+
INSERT_ORDERED_LIST_COMMAND,
|
|
293
|
+
() => {
|
|
294
|
+
insertList('number');
|
|
295
|
+
|
|
296
|
+
return true;
|
|
297
|
+
},
|
|
298
|
+
COMMAND_PRIORITY_NORMAL,
|
|
299
|
+
),
|
|
300
|
+
);
|
|
301
|
+
}
|
|
333
302
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
303
|
+
if (enabledButtons.includes('bulleted-list') || enabledButtons.includes('numbered-list')) {
|
|
304
|
+
// https://github.com/facebook/lexical/blob/main/packages/lexical-react/src/shared/useList.ts
|
|
305
|
+
addUnregister(
|
|
306
|
+
editor.registerCommand(
|
|
307
|
+
INSERT_PARAGRAPH_COMMAND,
|
|
308
|
+
() => handleListInsertParagraph(),
|
|
309
|
+
COMMAND_PRIORITY_NORMAL,
|
|
310
|
+
),
|
|
311
|
+
);
|
|
312
|
+
}
|
|
342
313
|
|
|
343
314
|
addUnregister(
|
|
344
315
|
editor.registerUpdateListener(() => {
|
|
@@ -369,7 +340,7 @@ export const initEditor = ({
|
|
|
369
340
|
}
|
|
370
341
|
}
|
|
371
342
|
|
|
372
|
-
onEditorUpdate(editor);
|
|
343
|
+
onEditorUpdate(editor, enabledTransformers);
|
|
373
344
|
});
|
|
374
345
|
})();
|
|
375
346
|
}),
|
|
@@ -424,6 +395,7 @@ export const initEditor = ({
|
|
|
424
395
|
|
|
425
396
|
return {
|
|
426
397
|
editor,
|
|
398
|
+
enabledTransformers,
|
|
427
399
|
/**
|
|
428
400
|
* Remove all registered Lexical listeners.
|
|
429
401
|
*/
|
|
@@ -455,7 +427,7 @@ export const loadCodeHighlighter = async (lang) => {
|
|
|
455
427
|
|
|
456
428
|
try {
|
|
457
429
|
// eslint-disable-next-line jsdoc/no-bad-blocks
|
|
458
|
-
await import(/* @vite-ignore */ `${
|
|
430
|
+
await import(/* @vite-ignore */ `${PRISM_BASE_URL}/components/prism-${canonicalLang}.min.js`);
|
|
459
431
|
} catch {
|
|
460
432
|
//
|
|
461
433
|
}
|
|
@@ -465,10 +437,11 @@ export const loadCodeHighlighter = async (lang) => {
|
|
|
465
437
|
* Convert Markdown to Lexical nodes.
|
|
466
438
|
* @param {LexicalEditor} editor Editor instance.
|
|
467
439
|
* @param {string} value Current Markdown value.
|
|
440
|
+
* @param {Transformer[]} enabledTransformers List of enabled Markdown transformers.
|
|
468
441
|
* @returns {Promise<void>} Nothing.
|
|
469
442
|
* @throws {Error} Failed to convert the value to Lexical nodes.
|
|
470
443
|
*/
|
|
471
|
-
export const convertMarkdownToLexical = async (editor, value) => {
|
|
444
|
+
export const convertMarkdownToLexical = async (editor, value, enabledTransformers) => {
|
|
472
445
|
// Load Prism language support on demand; the `loadLanguages` Prism utility method cannot be used
|
|
473
446
|
await Promise.all(
|
|
474
447
|
[...value.matchAll(/^```(?<lang>.+?)\n/gm)].map(async ({ groups: { lang = 'plain' } = {} }) =>
|
|
@@ -485,7 +458,7 @@ export const convertMarkdownToLexical = async (editor, value) => {
|
|
|
485
458
|
return new Promise((resolve, reject) => {
|
|
486
459
|
editor.update(() => {
|
|
487
460
|
try {
|
|
488
|
-
convertFromMarkdownString(value,
|
|
461
|
+
convertFromMarkdownString(value, enabledTransformers);
|
|
489
462
|
resolve(undefined);
|
|
490
463
|
} catch (ex) {
|
|
491
464
|
reject(new Error('Failed to convert Markdown', { cause: ex }));
|
|
@@ -88,9 +88,10 @@
|
|
|
88
88
|
};
|
|
89
89
|
|
|
90
90
|
onMount(() => {
|
|
91
|
-
const { editor, dispose } = initEditor(editorStore.config);
|
|
91
|
+
const { editor, enabledTransformers, dispose } = initEditor(editorStore.config);
|
|
92
92
|
|
|
93
93
|
editorStore.editor = editor;
|
|
94
|
+
editorStore.enabledTransformers = enabledTransformers;
|
|
94
95
|
|
|
95
96
|
lexicalRoot?.addEventListener('Update', onUpdate);
|
|
96
97
|
lexicalRoot?.addEventListener('click', onClick);
|
|
@@ -159,10 +160,10 @@
|
|
|
159
160
|
.lexical-root[aria-invalid=true] {
|
|
160
161
|
border-color: var(--sui-error-border-color);
|
|
161
162
|
}
|
|
162
|
-
.lexical-root :global(:first-child) {
|
|
163
|
+
.lexical-root :global([dir]:first-child) {
|
|
163
164
|
margin-top: 0;
|
|
164
165
|
}
|
|
165
|
-
.lexical-root :global(:last-child) {
|
|
166
|
+
.lexical-root :global([dir]:last-child) {
|
|
166
167
|
margin-bottom: 0;
|
|
167
168
|
}
|
|
168
169
|
.lexical-root :global(strong.italic) {
|
|
@@ -2,8 +2,9 @@ import { generateElementId } from '@sveltia/utils/element';
|
|
|
2
2
|
import { convertMarkdownToLexical } from './core.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* @import { TextEditorConfig, TextEditorSelectionState, TextEditorStore } from '../../typedefs';
|
|
6
5
|
* @import { LexicalEditor } from 'lexical';
|
|
6
|
+
* @import { Transformer } from '@lexical/markdown';
|
|
7
|
+
* @import { TextEditorConfig, TextEditorSelectionState, TextEditorStore } from '../../typedefs';
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -17,6 +18,8 @@ export const createEditorStore = () => {
|
|
|
17
18
|
let initialized = $state(false);
|
|
18
19
|
/** @type {LexicalEditor | undefined} */
|
|
19
20
|
let editor = $state();
|
|
21
|
+
/** @type {Transformer[]} */
|
|
22
|
+
let enabledTransformers = $state([]);
|
|
20
23
|
|
|
21
24
|
/** @type {TextEditorConfig} */
|
|
22
25
|
let config = $state({
|
|
@@ -53,7 +56,7 @@ export const createEditorStore = () => {
|
|
|
53
56
|
// We should avoid an empty editor; there should be at least one `<p>`, so give it an empty
|
|
54
57
|
// string if the `value` is `undefined`
|
|
55
58
|
// @see https://github.com/facebook/lexical/issues/2308
|
|
56
|
-
await convertMarkdownToLexical(editor, inputValue || '');
|
|
59
|
+
await convertMarkdownToLexical(editor, inputValue || '', enabledTransformers);
|
|
57
60
|
} catch (ex) {
|
|
58
61
|
hasConverterError = true;
|
|
59
62
|
inputValue = originalValue;
|
|
@@ -70,6 +73,12 @@ export const createEditorStore = () => {
|
|
|
70
73
|
set editor(newValue) {
|
|
71
74
|
editor = newValue;
|
|
72
75
|
},
|
|
76
|
+
set enabledTransformers(newValue) {
|
|
77
|
+
enabledTransformers = newValue;
|
|
78
|
+
},
|
|
79
|
+
get enabledTransformers() {
|
|
80
|
+
return enabledTransformers;
|
|
81
|
+
},
|
|
73
82
|
get initialized() {
|
|
74
83
|
return initialized;
|
|
75
84
|
},
|
|
@@ -15,12 +15,7 @@
|
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* @import { Snippet } from 'svelte';
|
|
18
|
-
* @import {
|
|
19
|
-
* TextEditorBlockType,
|
|
20
|
-
* TextEditorComponent,
|
|
21
|
-
* TextEditorInlineType,
|
|
22
|
-
* TextEditorMode,
|
|
23
|
-
* } from '../../typedefs';
|
|
18
|
+
* @import { TextEditorComponent, TextEditorMode, TextEditorNodeType } from '../../typedefs';
|
|
24
19
|
*/
|
|
25
20
|
|
|
26
21
|
/**
|
|
@@ -29,7 +24,7 @@
|
|
|
29
24
|
* @property {boolean} [flex] Make the text input container flexible.
|
|
30
25
|
* @property {'ltr' | 'rtl' | 'auto'} [dir] The `dir` attribute on the `<textarea>` element.
|
|
31
26
|
* @property {TextEditorMode[]} [modes] Enabled modes.
|
|
32
|
-
* @property {
|
|
27
|
+
* @property {TextEditorNodeType[]} [buttons] Enabled buttons.
|
|
33
28
|
* @property {TextEditorComponent[]} [components] Editor components.
|
|
34
29
|
* @property {boolean} [useMarkdownShortcuts] Whether to enable Markdown keyboard shortcuts in the
|
|
35
30
|
* rich text editor.
|
|
@@ -24,7 +24,7 @@ declare const TextEditor: import("svelte").Component<{
|
|
|
24
24
|
/**
|
|
25
25
|
* Enabled buttons.
|
|
26
26
|
*/
|
|
27
|
-
buttons?:
|
|
27
|
+
buttons?: TextEditorNodeType[] | undefined;
|
|
28
28
|
/**
|
|
29
29
|
* Editor components.
|
|
30
30
|
*/
|
|
@@ -87,7 +87,7 @@ type Props = {
|
|
|
87
87
|
/**
|
|
88
88
|
* Enabled buttons.
|
|
89
89
|
*/
|
|
90
|
-
buttons?:
|
|
90
|
+
buttons?: TextEditorNodeType[] | undefined;
|
|
91
91
|
/**
|
|
92
92
|
* Editor components.
|
|
93
93
|
*/
|
|
@@ -131,7 +131,6 @@ type Props = {
|
|
|
131
131
|
children?: Snippet<[]> | undefined;
|
|
132
132
|
};
|
|
133
133
|
import type { TextEditorMode } from '../../typedefs';
|
|
134
|
-
import type {
|
|
135
|
-
import type { TextEditorInlineType } from '../../typedefs';
|
|
134
|
+
import type { TextEditorNodeType } from '../../typedefs';
|
|
136
135
|
import type { TextEditorComponent } from '../../typedefs';
|
|
137
136
|
import type { Snippet } from 'svelte';
|
|
@@ -82,24 +82,27 @@
|
|
|
82
82
|
</script>
|
|
83
83
|
|
|
84
84
|
<ToolbarWrapper disabled={disabled || readonly} aria-label={_('_sui.text_editor.text_editor')}>
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
85
|
+
{#if blockLevelButtons.length > 1}
|
|
86
|
+
<MenuButton
|
|
87
|
+
disabled={!editorStore.useRichText}
|
|
88
|
+
aria-label={_('_sui.text_editor.show_text_style_options')}
|
|
89
|
+
aria-controls="{editorStore.editorId}-lexical-root"
|
|
90
|
+
>
|
|
91
|
+
{#snippet startIcon()}
|
|
92
|
+
<Icon
|
|
93
|
+
name={AVAILABLE_BUTTONS[editorStore.selection.blockType ?? '']?.icon ??
|
|
94
|
+
'format_paragraph'}
|
|
95
|
+
/>
|
|
96
|
+
{/snippet}
|
|
97
|
+
{#snippet popup()}
|
|
98
|
+
<Menu aria-label={_('_sui.text_editor.text_style_options')}>
|
|
99
|
+
{#each blockLevelButtons as type (type)}
|
|
100
|
+
<ToggleBlockMenuItem {type} />
|
|
101
|
+
{/each}
|
|
102
|
+
</Menu>
|
|
103
|
+
{/snippet}
|
|
104
|
+
</MenuButton>
|
|
105
|
+
{/if}
|
|
103
106
|
{#if editorStore.selection.blockType === 'code-block'}
|
|
104
107
|
<Divider orientation="vertical" />
|
|
105
108
|
<CodeLanguageSwitcher disabled={!editorStore.useRichText} />
|
package/dist/typedefs.d.ts
CHANGED
|
@@ -664,6 +664,7 @@ export type ToastPosition = "auto" | "top-left" | "top-center" | "top-right" | "
|
|
|
664
664
|
export type TextEditorBlockType = "paragraph" | "heading-1" | "heading-2" | "heading-3" | "heading-4" | "heading-5" | "heading-6" | "bulleted-list" | "numbered-list" | "blockquote" | "code-block";
|
|
665
665
|
export type TextEditorFormatType = "bold" | "italic" | "strikethrough" | "code";
|
|
666
666
|
export type TextEditorInlineType = TextEditorFormatType | "link";
|
|
667
|
+
export type TextEditorNodeType = TextEditorBlockType | TextEditorInlineType;
|
|
667
668
|
export type TextEditorMode = "rich-text" | "plain-text";
|
|
668
669
|
export type TextEditorComponent = {
|
|
669
670
|
/**
|
|
@@ -701,7 +702,7 @@ export type TextEditorConfig = {
|
|
|
701
702
|
* Enabled buttons for the
|
|
702
703
|
* editor.
|
|
703
704
|
*/
|
|
704
|
-
enabledButtons:
|
|
705
|
+
enabledButtons: TextEditorNodeType[];
|
|
705
706
|
/**
|
|
706
707
|
* Editor components.
|
|
707
708
|
*/
|
|
@@ -739,6 +740,10 @@ export type TextEditorStore = {
|
|
|
739
740
|
* Lexical editor instance.
|
|
740
741
|
*/
|
|
741
742
|
editor: LexicalEditor | undefined;
|
|
743
|
+
/**
|
|
744
|
+
* Enabled Lexical transformers.
|
|
745
|
+
*/
|
|
746
|
+
enabledTransformers: Transformer[];
|
|
742
747
|
/**
|
|
743
748
|
* Whether the Lexical editor is initialized.
|
|
744
749
|
*/
|
package/dist/typedefs.js
CHANGED
|
@@ -270,6 +270,10 @@
|
|
|
270
270
|
* @typedef {TextEditorFormatType | 'link'} TextEditorInlineType
|
|
271
271
|
*/
|
|
272
272
|
|
|
273
|
+
/**
|
|
274
|
+
* @typedef {TextEditorBlockType | TextEditorInlineType} TextEditorNodeType
|
|
275
|
+
*/
|
|
276
|
+
|
|
273
277
|
/**
|
|
274
278
|
* @typedef {'rich-text' | 'plain-text'} TextEditorMode
|
|
275
279
|
*/
|
|
@@ -288,7 +292,7 @@
|
|
|
288
292
|
/**
|
|
289
293
|
* @typedef {object} TextEditorConfig
|
|
290
294
|
* @property {TextEditorMode[]} modes Enabled modes.
|
|
291
|
-
* @property {
|
|
295
|
+
* @property {TextEditorNodeType[]} enabledButtons Enabled buttons for the
|
|
292
296
|
* editor.
|
|
293
297
|
* @property {TextEditorComponent[]} components Editor components.
|
|
294
298
|
* @property {boolean} useMarkdownShortcuts Whether to enable Markdown keyboard shortcuts in the
|
|
@@ -307,6 +311,7 @@
|
|
|
307
311
|
/**
|
|
308
312
|
* @typedef {object} TextEditorStore
|
|
309
313
|
* @property {LexicalEditor | undefined} editor Lexical editor instance.
|
|
314
|
+
* @property {Transformer[]} enabledTransformers Enabled Lexical transformers.
|
|
310
315
|
* @property {boolean} initialized Whether the Lexical editor is initialized.
|
|
311
316
|
* @property {string} editorId Random ID assigned to the editor.
|
|
312
317
|
* @property {TextEditorConfig} config Editor configuration.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sveltia/ui",
|
|
3
|
-
"version": "0.41.
|
|
3
|
+
"version": "0.41.2",
|
|
4
4
|
"description": "A collection of Svelte components and utilities for building user interfaces.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -53,24 +53,24 @@
|
|
|
53
53
|
},
|
|
54
54
|
"devDependencies": {
|
|
55
55
|
"@sveltejs/adapter-auto": "^7.0.1",
|
|
56
|
-
"@sveltejs/kit": "^2.
|
|
56
|
+
"@sveltejs/kit": "^2.69.1",
|
|
57
57
|
"@sveltejs/package": "^2.5.8",
|
|
58
|
-
"@sveltejs/vite-plugin-svelte": "^7.1.
|
|
59
|
-
"@vitest/coverage-v8": "^4.1.
|
|
58
|
+
"@sveltejs/vite-plugin-svelte": "^7.1.4",
|
|
59
|
+
"@vitest/coverage-v8": "^4.1.10",
|
|
60
60
|
"cspell": "^10.0.1",
|
|
61
61
|
"eslint": "^9.39.4",
|
|
62
62
|
"eslint-config-airbnb-extended": "^3.1.0",
|
|
63
63
|
"eslint-config-prettier": "^10.1.8",
|
|
64
64
|
"eslint-plugin-import": "^2.32.0",
|
|
65
|
-
"eslint-plugin-jsdoc": "^63.0.
|
|
65
|
+
"eslint-plugin-jsdoc": "^63.0.12",
|
|
66
66
|
"eslint-plugin-package-json": "^1.5.0",
|
|
67
67
|
"eslint-plugin-svelte": "^3.20.0",
|
|
68
68
|
"globals": "^17.7.0",
|
|
69
69
|
"happy-dom": "^20.10.6",
|
|
70
|
-
"oxlint": "^1.
|
|
71
|
-
"postcss": "^8.5.
|
|
70
|
+
"oxlint": "^1.73.0",
|
|
71
|
+
"postcss": "^8.5.16",
|
|
72
72
|
"postcss-html": "^1.8.1",
|
|
73
|
-
"prettier": "^3.9.
|
|
73
|
+
"prettier": "^3.9.4",
|
|
74
74
|
"prettier-plugin-svelte": "^4.1.1",
|
|
75
75
|
"sass": "^1.101.0",
|
|
76
76
|
"stylelint": "^17.14.0",
|
|
@@ -80,8 +80,8 @@
|
|
|
80
80
|
"svelte-check": "^4.7.1",
|
|
81
81
|
"svelte-preprocess": "^6.0.5",
|
|
82
82
|
"tslib": "^2.8.1",
|
|
83
|
-
"vite": "^8.1.
|
|
84
|
-
"vitest": "^4.1.
|
|
83
|
+
"vite": "^8.1.3",
|
|
84
|
+
"vitest": "^4.1.10"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
87
|
"svelte": "^5.0.0"
|