@thomasfosterau/prosekit-svelte 0.0.0 → 0.1.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/LICENSE +21 -0
- package/README.md +255 -2
- package/dist/editor/BlockEditor.svelte +66 -0
- package/dist/editor/BlockEditor.svelte.d.ts +42 -0
- package/dist/editor/Editor.svelte +60 -0
- package/dist/editor/Editor.svelte.d.ts +57 -0
- package/dist/editor/InlineEditor.svelte +55 -0
- package/dist/editor/InlineEditor.svelte.d.ts +36 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +21 -0
- package/dist/messages.svelte.d.ts +162 -0
- package/dist/messages.svelte.js +114 -0
- package/dist/toolbar/BlockToolbar.svelte +162 -0
- package/dist/toolbar/BlockToolbar.svelte.d.ts +52 -0
- package/dist/toolbar/ImageDialog.svelte +86 -0
- package/dist/toolbar/ImageDialog.svelte.d.ts +31 -0
- package/dist/toolbar/LinkDialog.svelte +113 -0
- package/dist/toolbar/LinkDialog.svelte.d.ts +40 -0
- package/dist/toolbar/SlashMenu.svelte +112 -0
- package/dist/toolbar/SlashMenu.svelte.d.ts +37 -0
- package/dist/toolbar/items.d.ts +125 -0
- package/dist/toolbar/items.js +262 -0
- package/package.json +60 -6
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The toolbar's data model, and the default sets built from it.
|
|
3
|
+
*
|
|
4
|
+
* `BlockToolbar` renders a list rather than a hard-coded row of buttons.
|
|
5
|
+
* The applications this package was extracted from hard-coded theirs, and
|
|
6
|
+
* the cost showed: one of them carried five buttons for node types its
|
|
7
|
+
* grammar no longer had, and neither could add an item without editing the
|
|
8
|
+
* component. Here an item is a value — an application appends its own,
|
|
9
|
+
* reorders, drops one, or passes a list of its own making.
|
|
10
|
+
*
|
|
11
|
+
* An item's `run` receives a {@link ToolbarRunContext} rather than the
|
|
12
|
+
* editor alone, because two of the default items open a dialog the toolbar
|
|
13
|
+
* owns. `dialogs.link()` / `dialogs.image()` are how an item asks for one
|
|
14
|
+
* without importing it.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
import { defaultEditorMessages } from "../messages.svelte.js";
|
|
19
|
+
/** Narrows an `items` entry to the separator arm. */
|
|
20
|
+
export function isToolbarSeparator(entry) {
|
|
21
|
+
return 'separator' in entry;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Reads a mark's active state without assuming the schema has it.
|
|
25
|
+
*
|
|
26
|
+
* The default item set covers `defineBlockDocumentExtension()`'s grammar,
|
|
27
|
+
* but an application may pass a narrower extension and keep the default
|
|
28
|
+
* items; a missing mark then makes the button inert rather than throwing on
|
|
29
|
+
* every editor update.
|
|
30
|
+
*/
|
|
31
|
+
const isMarkActive = (editor, name) => editor.marks[name]?.isActive() ?? false;
|
|
32
|
+
/** {@link isMarkActive} for a node type, optionally matching `attrs`. */
|
|
33
|
+
const isNodeActive = (editor, name, attrs) => editor.nodes[name]?.isActive(attrs) ?? false;
|
|
34
|
+
/**
|
|
35
|
+
* Toggles one of the three marks that have no toggle command of their own.
|
|
36
|
+
*
|
|
37
|
+
* `highlight`, `subscript` and `superscript` are the applications' own mark
|
|
38
|
+
* specs, and ProseKit's generic `addMark` is the only command that reaches
|
|
39
|
+
* them. `addMark` alone can never turn a mark off, which is exactly the lie
|
|
40
|
+
* `aria-pressed` would then tell, so the item reads its own `isActive` and
|
|
41
|
+
* picks `removeMark` when the mark is on.
|
|
42
|
+
*/
|
|
43
|
+
const toggleGenericMark = (editor, type) => {
|
|
44
|
+
if (isMarkActive(editor, type))
|
|
45
|
+
editor.commands['removeMark']?.({ type });
|
|
46
|
+
else
|
|
47
|
+
editor.commands['addMark']?.({ type });
|
|
48
|
+
};
|
|
49
|
+
/**
|
|
50
|
+
* The default toolbar: the nineteen items the applications shipped, in their
|
|
51
|
+
* order, with two separators marking the mark / block / history groups.
|
|
52
|
+
*
|
|
53
|
+
* The twentieth control those toolbars carried was a button that toggled an
|
|
54
|
+
* ad-hoc "slash" panel; it is the insert menu now, and `BlockToolbar`
|
|
55
|
+
* renders it itself from {@link createDefaultSlashCommands}.
|
|
56
|
+
*
|
|
57
|
+
* Every `run` goes through the editor's own `commands`, `marks` and `nodes`,
|
|
58
|
+
* so the set works against any extension that registers those node and mark
|
|
59
|
+
* types and quietly does nothing against one that does not.
|
|
60
|
+
*
|
|
61
|
+
* @param messages - Labels to use instead of the English defaults. A partial
|
|
62
|
+
* record; unlisted keys keep their default.
|
|
63
|
+
*/
|
|
64
|
+
export function createDefaultToolbarItems(messages) {
|
|
65
|
+
const m = { ...defaultEditorMessages, ...messages };
|
|
66
|
+
return [
|
|
67
|
+
{
|
|
68
|
+
id: 'bold',
|
|
69
|
+
label: m.bold,
|
|
70
|
+
kind: 'toggle',
|
|
71
|
+
isActive: (editor) => isMarkActive(editor, 'bold'),
|
|
72
|
+
run: ({ editor }) => void editor.commands['toggleBold']?.(),
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
id: 'italic',
|
|
76
|
+
label: m.italic,
|
|
77
|
+
kind: 'toggle',
|
|
78
|
+
isActive: (editor) => isMarkActive(editor, 'italic'),
|
|
79
|
+
run: ({ editor }) => void editor.commands['toggleItalic']?.(),
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
id: 'underline',
|
|
83
|
+
label: m.underline,
|
|
84
|
+
kind: 'toggle',
|
|
85
|
+
isActive: (editor) => isMarkActive(editor, 'underline'),
|
|
86
|
+
run: ({ editor }) => void editor.commands['toggleUnderline']?.(),
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
id: 'strike',
|
|
90
|
+
label: m.strike,
|
|
91
|
+
kind: 'toggle',
|
|
92
|
+
isActive: (editor) => isMarkActive(editor, 'strike'),
|
|
93
|
+
run: ({ editor }) => void editor.commands['toggleStrike']?.(),
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'highlight',
|
|
97
|
+
label: m.highlight,
|
|
98
|
+
kind: 'toggle',
|
|
99
|
+
isActive: (editor) => isMarkActive(editor, 'highlight'),
|
|
100
|
+
run: ({ editor }) => toggleGenericMark(editor, 'highlight'),
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
id: 'subscript',
|
|
104
|
+
label: m.subscript,
|
|
105
|
+
kind: 'toggle',
|
|
106
|
+
isActive: (editor) => isMarkActive(editor, 'subscript'),
|
|
107
|
+
run: ({ editor }) => toggleGenericMark(editor, 'subscript'),
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
id: 'superscript',
|
|
111
|
+
label: m.superscript,
|
|
112
|
+
kind: 'toggle',
|
|
113
|
+
isActive: (editor) => isMarkActive(editor, 'superscript'),
|
|
114
|
+
run: ({ editor }) => toggleGenericMark(editor, 'superscript'),
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
id: 'link',
|
|
118
|
+
label: m.link,
|
|
119
|
+
kind: 'action',
|
|
120
|
+
run: ({ dialogs }) => dialogs.link(),
|
|
121
|
+
},
|
|
122
|
+
{ id: 'marks-blocks', separator: true },
|
|
123
|
+
{
|
|
124
|
+
id: 'paragraph',
|
|
125
|
+
// An action, not a toggle: `setParagraph` only ever sets, so a pressed
|
|
126
|
+
// state that can never be un-pressed would be a claim `aria-pressed`
|
|
127
|
+
// makes out loud and the button cannot honour. The eleven items around
|
|
128
|
+
// it each have a real toggle command.
|
|
129
|
+
label: m.paragraph,
|
|
130
|
+
kind: 'action',
|
|
131
|
+
run: ({ editor }) => void editor.commands['setParagraph']?.(),
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
id: 'heading2',
|
|
135
|
+
label: m.heading2,
|
|
136
|
+
kind: 'toggle',
|
|
137
|
+
isActive: (editor) => isNodeActive(editor, 'heading', { level: 2 }),
|
|
138
|
+
run: ({ editor }) => void editor.commands['toggleHeading']?.({ level: 2 }),
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
id: 'quote',
|
|
142
|
+
label: m.quote,
|
|
143
|
+
kind: 'toggle',
|
|
144
|
+
isActive: (editor) => isNodeActive(editor, 'blockquote'),
|
|
145
|
+
run: ({ editor }) => void editor.commands['toggleBlockquote']?.(),
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: 'bullets',
|
|
149
|
+
label: m.bullets,
|
|
150
|
+
kind: 'toggle',
|
|
151
|
+
isActive: (editor) => isNodeActive(editor, 'list', { kind: 'bullet' }),
|
|
152
|
+
run: ({ editor }) => void editor.commands['toggleList']?.({ kind: 'bullet' }),
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
id: 'numbered',
|
|
156
|
+
label: m.numbered,
|
|
157
|
+
kind: 'toggle',
|
|
158
|
+
isActive: (editor) => isNodeActive(editor, 'list', { kind: 'ordered' }),
|
|
159
|
+
run: ({ editor }) => void editor.commands['toggleList']?.({ kind: 'ordered' }),
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
id: 'code-block',
|
|
163
|
+
label: m.codeBlock,
|
|
164
|
+
kind: 'toggle',
|
|
165
|
+
isActive: (editor) => isNodeActive(editor, 'codeBlock'),
|
|
166
|
+
run: ({ editor }) => void editor.commands['toggleCodeBlock']?.(),
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
id: 'rule',
|
|
170
|
+
label: m.rule,
|
|
171
|
+
kind: 'action',
|
|
172
|
+
run: ({ editor }) => void editor.commands['insertHorizontalRule']?.(),
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
id: 'table',
|
|
176
|
+
label: m.table,
|
|
177
|
+
kind: 'action',
|
|
178
|
+
run: ({ editor }) => void editor.commands['insertTable']?.({ row: 3, col: 3 }),
|
|
179
|
+
},
|
|
180
|
+
{
|
|
181
|
+
id: 'image',
|
|
182
|
+
label: m.image,
|
|
183
|
+
kind: 'action',
|
|
184
|
+
run: ({ dialogs }) => dialogs.image(),
|
|
185
|
+
},
|
|
186
|
+
{ id: 'blocks-history', separator: true },
|
|
187
|
+
{
|
|
188
|
+
id: 'undo',
|
|
189
|
+
label: m.undo,
|
|
190
|
+
kind: 'action',
|
|
191
|
+
run: ({ editor }) => void editor.commands['undo']?.(),
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
id: 'redo',
|
|
195
|
+
label: m.redo,
|
|
196
|
+
kind: 'action',
|
|
197
|
+
run: ({ editor }) => void editor.commands['redo']?.(),
|
|
198
|
+
},
|
|
199
|
+
];
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* The default insert menu: the ten entries the applications shipped, in
|
|
203
|
+
* their order.
|
|
204
|
+
*
|
|
205
|
+
* @param messages - Labels to use instead of the English defaults. A partial
|
|
206
|
+
* record; unlisted keys keep their default.
|
|
207
|
+
*/
|
|
208
|
+
export function createDefaultSlashCommands(messages) {
|
|
209
|
+
const m = { ...defaultEditorMessages, ...messages };
|
|
210
|
+
return [
|
|
211
|
+
{
|
|
212
|
+
id: 'paragraph',
|
|
213
|
+
label: m.slashParagraph,
|
|
214
|
+
run: ({ editor }) => void editor.commands['setParagraph']?.(),
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
id: 'heading2',
|
|
218
|
+
label: m.slashHeading2,
|
|
219
|
+
run: ({ editor }) => void editor.commands['toggleHeading']?.({ level: 2 }),
|
|
220
|
+
},
|
|
221
|
+
{
|
|
222
|
+
id: 'blockquote',
|
|
223
|
+
label: m.slashBlockquote,
|
|
224
|
+
run: ({ editor }) => void editor.commands['toggleBlockquote']?.(),
|
|
225
|
+
},
|
|
226
|
+
{
|
|
227
|
+
id: 'bullet-list',
|
|
228
|
+
label: m.slashBulletList,
|
|
229
|
+
run: ({ editor }) => void editor.commands['toggleList']?.({ kind: 'bullet' }),
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
id: 'ordered-list',
|
|
233
|
+
label: m.slashOrderedList,
|
|
234
|
+
run: ({ editor }) => void editor.commands['toggleList']?.({ kind: 'ordered' }),
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
id: 'todo-list',
|
|
238
|
+
label: m.slashTodoList,
|
|
239
|
+
run: ({ editor }) => void editor.commands['toggleList']?.({ kind: 'task' }),
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
id: 'code-block',
|
|
243
|
+
label: m.slashCodeBlock,
|
|
244
|
+
run: ({ editor }) => void editor.commands['toggleCodeBlock']?.(),
|
|
245
|
+
},
|
|
246
|
+
{
|
|
247
|
+
id: 'table',
|
|
248
|
+
label: m.slashTable,
|
|
249
|
+
run: ({ editor }) => void editor.commands['insertTable']?.({ row: 3, col: 3 }),
|
|
250
|
+
},
|
|
251
|
+
{
|
|
252
|
+
id: 'image',
|
|
253
|
+
label: m.slashImage,
|
|
254
|
+
run: ({ dialogs }) => dialogs.image(),
|
|
255
|
+
},
|
|
256
|
+
{
|
|
257
|
+
id: 'horizontal-rule',
|
|
258
|
+
label: m.slashHorizontalRule,
|
|
259
|
+
run: ({ editor }) => void editor.commands['insertHorizontalRule']?.(),
|
|
260
|
+
},
|
|
261
|
+
];
|
|
262
|
+
}
|
package/package.json
CHANGED
|
@@ -1,14 +1,68 @@
|
|
|
1
1
|
{
|
|
2
|
+
"$schema": "https://www.schemastore.org/package",
|
|
2
3
|
"name": "@thomasfosterau/prosekit-svelte",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"description": "Svelte 5 rich-text editor components on ProseKit — Editor, BlockEditor, InlineEditor, a declarative toolbar with an insert menu and link/image dialogs on bits-ui, every string a prop",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"accessibility",
|
|
8
|
+
"bits-ui",
|
|
9
|
+
"editor",
|
|
10
|
+
"prosekit",
|
|
11
|
+
"prosemirror",
|
|
12
|
+
"rich-text",
|
|
13
|
+
"svelte",
|
|
14
|
+
"svelte5"
|
|
15
|
+
],
|
|
5
16
|
"license": "MIT",
|
|
17
|
+
"author": "Thomas Foster <me@thomasfoster.au>",
|
|
6
18
|
"repository": {
|
|
7
19
|
"type": "git",
|
|
8
|
-
"url": "
|
|
20
|
+
"url": "https://github.com/thomasfosterau/prosekit-utilities.git",
|
|
9
21
|
"directory": "packages/prosekit-svelte"
|
|
10
22
|
},
|
|
11
|
-
"
|
|
12
|
-
"
|
|
23
|
+
"files": [
|
|
24
|
+
"dist"
|
|
25
|
+
],
|
|
26
|
+
"type": "module",
|
|
27
|
+
"sideEffects": false,
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"svelte": "./dist/index.js",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"svelte": "./dist/index.js",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@thomasfosterau/prosekit-utilities": "0.1.0",
|
|
39
|
+
"bits-ui": "^2.20.2"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@prosekit/pm": "^0.1.21",
|
|
43
|
+
"@storybook/addon-a11y": "^10.6.6",
|
|
44
|
+
"@storybook/addon-svelte-csf": "^5.1.6",
|
|
45
|
+
"@storybook/sveltekit": "^10.6.6",
|
|
46
|
+
"@sveltejs/kit": "3.1.1",
|
|
47
|
+
"@sveltejs/package": "3.0.2",
|
|
48
|
+
"@sveltejs/vite-plugin-svelte": "^7.3.1",
|
|
49
|
+
"@tailwindcss/vite": "^4.3.3",
|
|
50
|
+
"@testing-library/jest-dom": "^7.0.1",
|
|
51
|
+
"@types/node": "^26.6.6",
|
|
52
|
+
"@vitest/browser-playwright": "5.0.5",
|
|
53
|
+
"daisyui": "^5.7.50",
|
|
54
|
+
"playwright": "1.60.0",
|
|
55
|
+
"prosekit": "^0.23.0",
|
|
56
|
+
"storybook": "^10.6.6",
|
|
57
|
+
"svelte": "^5.57.3",
|
|
58
|
+
"svelte-check": "^4.7.8",
|
|
59
|
+
"tailwindcss": "^4.3.3",
|
|
60
|
+
"vitest": "5.0.5",
|
|
61
|
+
"vitest-browser-svelte": "^3.1.0"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@prosekit/pm": "^0.1.19",
|
|
65
|
+
"prosekit": "^0.22.2",
|
|
66
|
+
"svelte": "^5.30.1"
|
|
13
67
|
}
|
|
14
|
-
}
|
|
68
|
+
}
|