@x33025/sveltely 0.1.10 → 0.1.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/dist/components/Library/ArticleEditor/ArticleBlockCode.svelte +21 -0
  2. package/dist/components/Library/ArticleEditor/ArticleBlockCode.svelte.d.ts +8 -0
  3. package/dist/components/Library/ArticleEditor/ArticleBlockDragControl.svelte +144 -0
  4. package/dist/components/Library/ArticleEditor/ArticleBlockDragControl.svelte.d.ts +14 -0
  5. package/dist/components/Library/ArticleEditor/ArticleBlockFAQ.svelte +47 -0
  6. package/dist/components/Library/ArticleEditor/ArticleBlockFAQ.svelte.d.ts +8 -0
  7. package/dist/components/Library/ArticleEditor/ArticleBlockFallback.svelte +79 -0
  8. package/dist/components/Library/ArticleEditor/ArticleBlockFallback.svelte.d.ts +15 -0
  9. package/dist/components/Library/ArticleEditor/ArticleBlockHeading.svelte +73 -0
  10. package/dist/components/Library/ArticleEditor/ArticleBlockHeading.svelte.d.ts +14 -0
  11. package/dist/components/Library/ArticleEditor/ArticleBlockImage.svelte +48 -0
  12. package/dist/components/Library/ArticleEditor/ArticleBlockImage.svelte.d.ts +9 -0
  13. package/dist/components/Library/ArticleEditor/ArticleBlockInsertControl.svelte +120 -0
  14. package/dist/components/Library/ArticleEditor/ArticleBlockInsertControl.svelte.d.ts +9 -0
  15. package/dist/components/Library/ArticleEditor/ArticleBlockList.svelte +114 -0
  16. package/dist/components/Library/ArticleEditor/ArticleBlockList.svelte.d.ts +15 -0
  17. package/dist/components/Library/ArticleEditor/ArticleBlockParagraph.svelte +79 -0
  18. package/dist/components/Library/ArticleEditor/ArticleBlockParagraph.svelte.d.ts +15 -0
  19. package/dist/components/Library/ArticleEditor/ArticleBlockShell.svelte +127 -0
  20. package/dist/components/Library/ArticleEditor/ArticleBlockShell.svelte.d.ts +22 -0
  21. package/dist/components/Library/ArticleEditor/ArticleBlockTable.svelte +274 -0
  22. package/dist/components/Library/ArticleEditor/ArticleBlockTable.svelte.d.ts +13 -0
  23. package/dist/components/Library/ArticleEditor/ArticleEditor.svelte +192 -0
  24. package/dist/components/Library/ArticleEditor/ArticleEditor.svelte.d.ts +39 -0
  25. package/dist/components/Library/ArticleEditor/ArticleEditorBody.svelte +328 -0
  26. package/dist/components/Library/ArticleEditor/ArticleEditorBody.svelte.d.ts +31 -0
  27. package/dist/components/Library/ArticleEditor/ArticleEditorHeader.svelte +57 -0
  28. package/dist/components/Library/ArticleEditor/ArticleEditorHeader.svelte.d.ts +11 -0
  29. package/dist/components/Library/ArticleEditor/ArticleImagePreview.svelte +71 -0
  30. package/dist/components/Library/ArticleEditor/ArticleImagePreview.svelte.d.ts +8 -0
  31. package/dist/components/Library/ArticleEditor/articleEditor.svelte.js +532 -0
  32. package/dist/components/Library/ArticleEditor/index.d.ts +18 -0
  33. package/dist/components/Library/ArticleEditor/index.js +16 -0
  34. package/dist/components/Library/ArticleEditor/types.d.ts +37 -0
  35. package/dist/components/Library/ArticleEditor/types.js +1 -0
  36. package/dist/components/Library/Floating/Floating.svelte +2 -1
  37. package/dist/components/Library/Grid/index.d.ts +1 -0
  38. package/dist/components/Library/Grid/index.js +1 -0
  39. package/dist/components/Library/Sheet/Sheet.svelte +1 -0
  40. package/dist/components/Library/TextEditor/TextEditor.svelte +90 -0
  41. package/dist/components/Library/TextEditor/TextEditor.svelte.d.ts +17 -0
  42. package/dist/components/Library/TextEditor/index.d.ts +1 -0
  43. package/dist/components/Library/TextEditor/index.js +1 -0
  44. package/dist/components/Local/ComponentGrid.svelte +1 -1
  45. package/dist/index.d.ts +3 -1
  46. package/dist/index.js +3 -1
  47. package/dist/style/index.css +1 -0
  48. package/dist/style.css +233 -0
  49. package/package.json +1 -1
  50. package/dist/components/Library/GridItem/index.d.ts +0 -1
  51. package/dist/components/Library/GridItem/index.js +0 -1
  52. /package/dist/components/Library/{GridItem → Grid}/GridItem.svelte +0 -0
  53. /package/dist/components/Library/{GridItem → Grid}/GridItem.svelte.d.ts +0 -0
@@ -0,0 +1,71 @@
1
+ <script lang="ts">
2
+ import { ImageIcon } from '@lucide/svelte';
3
+
4
+ let {
5
+ src,
6
+ alt,
7
+ aspectRatio = '4 / 3'
8
+ } = $props<{
9
+ src?: string | null;
10
+ alt: string;
11
+ aspectRatio?: string;
12
+ }>();
13
+
14
+ let imageLoaded = $state(false);
15
+ let imageFailed = $state(false);
16
+
17
+ $effect(() => {
18
+ src;
19
+ imageLoaded = false;
20
+ imageFailed = false;
21
+ });
22
+ </script>
23
+
24
+ <div class="article-image-preview" style={`aspect-ratio: ${aspectRatio};`}>
25
+ {#if src && !imageFailed}
26
+ <img
27
+ {src}
28
+ {alt}
29
+ class="article-image-preview-media"
30
+ class:article-image-preview-media-loaded={imageLoaded}
31
+ onload={() => (imageLoaded = true)}
32
+ onerror={() => (imageFailed = true)}
33
+ />
34
+ {/if}
35
+ {#if !src || !imageLoaded || imageFailed}
36
+ <div class="article-image-preview-placeholder" aria-hidden="true">
37
+ <ImageIcon size={32} strokeWidth={1.75} />
38
+ </div>
39
+ {/if}
40
+ </div>
41
+
42
+ <style>
43
+ .article-image-preview {
44
+ position: relative;
45
+ display: grid;
46
+ place-items: center;
47
+ overflow: hidden;
48
+ background: #f4f4f5;
49
+ }
50
+
51
+ .article-image-preview-media {
52
+ display: block;
53
+ width: 100%;
54
+ height: 100%;
55
+ object-fit: cover;
56
+ opacity: 0;
57
+ transition: opacity 120ms ease;
58
+ }
59
+
60
+ .article-image-preview-media-loaded {
61
+ opacity: 1;
62
+ }
63
+
64
+ .article-image-preview-placeholder {
65
+ position: absolute;
66
+ inset: 0;
67
+ display: grid;
68
+ place-items: center;
69
+ color: #a1a1aa;
70
+ }
71
+ </style>
@@ -0,0 +1,8 @@
1
+ type $$ComponentProps = {
2
+ src?: string | null;
3
+ alt: string;
4
+ aspectRatio?: string;
5
+ };
6
+ declare const ArticleImagePreview: import("svelte").Component<$$ComponentProps, {}, "">;
7
+ type ArticleImagePreview = ReturnType<typeof ArticleImagePreview>;
8
+ export default ArticleImagePreview;
@@ -0,0 +1,532 @@
1
+ const cloneBlocks = (blocks) => blocks
2
+ .slice()
3
+ .sort((a, b) => a.order - b.order)
4
+ .map((block) => ({
5
+ ...block,
6
+ items: block.items ? [...block.items] : null,
7
+ listItems: block.listItems ? block.listItems.map((item) => ({ ...item })) : null,
8
+ faqItems: block.faqItems ? block.faqItems.map((item) => ({ ...item })) : null,
9
+ columns: block.columns ? [...block.columns] : null,
10
+ rows: block.rows ? block.rows.map((row) => [...row]) : null
11
+ }));
12
+ const reindexBlocks = (blocks) => blocks.map((block, index) => ({
13
+ ...block,
14
+ order: index
15
+ }));
16
+ const makeParagraphBlock = (order, text = '') => ({
17
+ id: crypto.randomUUID(),
18
+ order,
19
+ type: 'paragraph',
20
+ sectionID: null,
21
+ level: null,
22
+ text,
23
+ textHTML: null,
24
+ ordered: null,
25
+ items: null,
26
+ listItems: null,
27
+ faqItems: null,
28
+ columns: null,
29
+ rows: null,
30
+ language: null,
31
+ code: null,
32
+ imageURL: null,
33
+ imageAlt: null
34
+ });
35
+ const makeInsertedBlock = (order, kind) => {
36
+ const baseBlock = makeParagraphBlock(order);
37
+ if (kind === 'image') {
38
+ return {
39
+ ...baseBlock,
40
+ type: 'image',
41
+ text: null,
42
+ imageURL: null,
43
+ imageAlt: ''
44
+ };
45
+ }
46
+ if (kind === 'table') {
47
+ return {
48
+ ...baseBlock,
49
+ type: 'table',
50
+ text: null,
51
+ columns: ['Column 1', 'Column 2'],
52
+ rows: [
53
+ ['', ''],
54
+ ['', '']
55
+ ]
56
+ };
57
+ }
58
+ if (kind === 'bullet_list' || kind === 'numbered_list') {
59
+ return {
60
+ ...baseBlock,
61
+ type: kind,
62
+ text: null,
63
+ ordered: kind === 'numbered_list',
64
+ items: ['']
65
+ };
66
+ }
67
+ return baseBlock;
68
+ };
69
+ const canMergeText = (block) => block?.type === 'paragraph' || block?.type === 'heading';
70
+ const canFormatText = (block) => block?.type === 'paragraph' ||
71
+ block?.type === 'heading' ||
72
+ block?.type === 'bullet_list' ||
73
+ block?.type === 'list' ||
74
+ block?.type === 'numbered_list' ||
75
+ block?.type === 'code';
76
+ const textFromBlock = (block) => {
77
+ if (block.items?.length)
78
+ return block.items.join('\n');
79
+ return block.text ?? '';
80
+ };
81
+ export const articleBlockLabel = (block) => {
82
+ if (block.type === 'heading') {
83
+ const level = Math.min(3, Math.max(1, Number(block.level ?? 2)));
84
+ return level === 1 ? 'Heading 1' : level === 2 ? 'Heading 2' : 'Heading 3';
85
+ }
86
+ if (block.type === 'numbered_list')
87
+ return 'Numbered list';
88
+ if (block.type === 'bullet_list' || block.type === 'list')
89
+ return 'Bulleted list';
90
+ if (block.type === 'image')
91
+ return 'Image';
92
+ if (block.type === 'table')
93
+ return 'Table';
94
+ if (block.type === 'faq')
95
+ return 'FAQ';
96
+ if (block.type === 'code')
97
+ return 'Code';
98
+ return 'Text';
99
+ };
100
+ export function createArticleEditor(article) {
101
+ const initialDraftBlocks = cloneBlocks(article.articleBlocks ?? []);
102
+ let draftTitle = $state(article.title);
103
+ let draftImageAltText = $state(article.imageAltText ?? '');
104
+ let draftBlocks = $state(initialDraftBlocks);
105
+ let selectedBlockID = $state(initialDraftBlocks[0]?.id ?? null);
106
+ let focusTarget = $state(null);
107
+ let focusPosition = $state(null);
108
+ let undoStack = $state([]);
109
+ let redoStack = $state([]);
110
+ const sortedDraftBlocks = $derived(draftBlocks.slice().sort((a, b) => a.order - b.order));
111
+ const selectedBlock = $derived(sortedDraftBlocks.find((block) => block.id === selectedBlockID) ?? sortedDraftBlocks[0] ?? null);
112
+ const takeSnapshot = () => ({
113
+ draftTitle,
114
+ draftImageAltText,
115
+ draftBlocks: cloneBlocks(draftBlocks),
116
+ selectedBlockID
117
+ });
118
+ const restoreSnapshot = (snapshot) => {
119
+ draftTitle = snapshot.draftTitle;
120
+ draftImageAltText = snapshot.draftImageAltText;
121
+ draftBlocks = cloneBlocks(snapshot.draftBlocks);
122
+ selectedBlockID = snapshot.selectedBlockID;
123
+ focusTarget = snapshot.selectedBlockID;
124
+ focusPosition = null;
125
+ };
126
+ const pushUndoSnapshot = () => {
127
+ undoStack = [...undoStack, takeSnapshot()].slice(-100);
128
+ redoStack = [];
129
+ };
130
+ return {
131
+ get draftTitle() {
132
+ return draftTitle;
133
+ },
134
+ get draftImageAltText() {
135
+ return draftImageAltText;
136
+ },
137
+ get blocks() {
138
+ return sortedDraftBlocks;
139
+ },
140
+ get selectedBlockID() {
141
+ return selectedBlockID;
142
+ },
143
+ get selectedBlock() {
144
+ return selectedBlock;
145
+ },
146
+ get focusTarget() {
147
+ return focusTarget;
148
+ },
149
+ get focusPosition() {
150
+ return focusPosition;
151
+ },
152
+ get canUndo() {
153
+ return undoStack.length > 0;
154
+ },
155
+ get canRedo() {
156
+ return redoStack.length > 0;
157
+ },
158
+ setTitle(value) {
159
+ if (draftTitle === value)
160
+ return;
161
+ pushUndoSnapshot();
162
+ draftTitle = value;
163
+ },
164
+ setImageAltText(value) {
165
+ if (draftImageAltText === value)
166
+ return;
167
+ pushUndoSnapshot();
168
+ draftImageAltText = value;
169
+ },
170
+ selectBlock(id) {
171
+ selectedBlockID = id;
172
+ },
173
+ updateBlock(id, patch) {
174
+ pushUndoSnapshot();
175
+ draftBlocks = draftBlocks.map((block) => (block.id === id ? { ...block, ...patch } : block));
176
+ },
177
+ changeBlockTextFormat(id, format) {
178
+ const block = sortedDraftBlocks.find((candidate) => candidate.id === id);
179
+ if (!canFormatText(block))
180
+ return;
181
+ const text = textFromBlock(block);
182
+ const listItems = block.items?.length
183
+ ? [...block.items]
184
+ : text
185
+ .split('\n')
186
+ .map((item) => item.trim())
187
+ .filter(Boolean);
188
+ pushUndoSnapshot();
189
+ draftBlocks = draftBlocks.map((candidate) => {
190
+ if (candidate.id !== id)
191
+ return candidate;
192
+ if (format === 'bullet_list' || format === 'numbered_list') {
193
+ return {
194
+ ...candidate,
195
+ type: format,
196
+ level: null,
197
+ text: null,
198
+ textHTML: null,
199
+ ordered: format === 'numbered_list',
200
+ items: listItems.length ? listItems : [''],
201
+ listItems: null,
202
+ faqItems: null,
203
+ columns: null,
204
+ rows: null,
205
+ language: null,
206
+ code: null,
207
+ imageURL: null,
208
+ imageAlt: null
209
+ };
210
+ }
211
+ const level = format === 'paragraph' ? null : Number(format.replace('heading-', ''));
212
+ return {
213
+ ...candidate,
214
+ type: format === 'paragraph' ? 'paragraph' : 'heading',
215
+ level,
216
+ text,
217
+ textHTML: null,
218
+ ordered: null,
219
+ items: null,
220
+ listItems: null,
221
+ faqItems: null,
222
+ columns: null,
223
+ rows: null,
224
+ language: null,
225
+ code: null,
226
+ imageURL: null,
227
+ imageAlt: null
228
+ };
229
+ });
230
+ selectedBlockID = id;
231
+ focusTarget = id;
232
+ focusPosition = null;
233
+ },
234
+ insertBlockAfter(id, kind) {
235
+ const index = sortedDraftBlocks.findIndex((block) => block.id === id);
236
+ if (index === -1)
237
+ return;
238
+ pushUndoSnapshot();
239
+ const nextBlocks = [...sortedDraftBlocks];
240
+ const newBlock = makeInsertedBlock(index + 1, kind);
241
+ nextBlocks.splice(index + 1, 0, newBlock);
242
+ draftBlocks = reindexBlocks(nextBlocks);
243
+ selectedBlockID = newBlock.id;
244
+ focusTarget =
245
+ kind === 'bullet_list' || kind === 'numbered_list' ? `${newBlock.id}:0` : newBlock.id;
246
+ focusPosition = null;
247
+ },
248
+ updateListItem(blockID, index, value) {
249
+ pushUndoSnapshot();
250
+ draftBlocks = draftBlocks.map((block) => {
251
+ if (block.id !== blockID)
252
+ return block;
253
+ const items = [...(block.items ?? [])];
254
+ items[index] = value;
255
+ return { ...block, items };
256
+ });
257
+ },
258
+ updateFAQItem(blockID, index, field, value) {
259
+ pushUndoSnapshot();
260
+ draftBlocks = draftBlocks.map((block) => {
261
+ if (block.id !== blockID)
262
+ return block;
263
+ const faqItems = [...(block.faqItems ?? [])];
264
+ faqItems[index] = { ...faqItems[index], [field]: value };
265
+ return { ...block, faqItems };
266
+ });
267
+ },
268
+ updateTableCell(blockID, rowIndex, cellIndex, value) {
269
+ pushUndoSnapshot();
270
+ draftBlocks = draftBlocks.map((block) => {
271
+ if (block.id !== blockID)
272
+ return block;
273
+ const rows = (block.rows ?? []).map((row) => [...row]);
274
+ rows[rowIndex][cellIndex] = value;
275
+ return { ...block, rows };
276
+ });
277
+ },
278
+ updateTableHeader(blockID, cellIndex, value) {
279
+ pushUndoSnapshot();
280
+ draftBlocks = draftBlocks.map((block) => {
281
+ if (block.id !== blockID)
282
+ return block;
283
+ const columns = [...(block.columns ?? [])];
284
+ columns[cellIndex] = value;
285
+ return { ...block, columns };
286
+ });
287
+ },
288
+ addTableColumn(blockID) {
289
+ pushUndoSnapshot();
290
+ draftBlocks = draftBlocks.map((block) => {
291
+ if (block.id !== blockID)
292
+ return block;
293
+ const columns = [...(block.columns ?? [])];
294
+ const rows = (block.rows ?? []).map((row) => [...row]);
295
+ columns.push('');
296
+ return {
297
+ ...block,
298
+ columns,
299
+ rows: rows.map((row) => [...row, ''])
300
+ };
301
+ });
302
+ },
303
+ removeTableColumn(blockID, columnIndex) {
304
+ const block = draftBlocks.find((candidate) => candidate.id === blockID);
305
+ const columns = [...(block?.columns ?? [])];
306
+ const rows = (block?.rows ?? []).map((row) => [...row]);
307
+ const columnCount = Math.max(columns.length, rows[0]?.length ?? 0);
308
+ if (!block || columnCount <= 1 || columnIndex < 0 || columnIndex >= columnCount)
309
+ return;
310
+ pushUndoSnapshot();
311
+ draftBlocks = draftBlocks.map((candidate) => {
312
+ if (candidate.id !== blockID)
313
+ return candidate;
314
+ return {
315
+ ...candidate,
316
+ columns: columns.filter((_, index) => index !== columnIndex),
317
+ rows: rows.map((row) => row.filter((_, index) => index !== columnIndex))
318
+ };
319
+ });
320
+ },
321
+ addTableRow(blockID) {
322
+ pushUndoSnapshot();
323
+ draftBlocks = draftBlocks.map((block) => {
324
+ if (block.id !== blockID)
325
+ return block;
326
+ const columns = [...(block.columns ?? [])];
327
+ const rows = (block.rows ?? []).map((row) => [...row]);
328
+ const columnCount = Math.max(columns.length, rows[0]?.length ?? 0, 1);
329
+ return {
330
+ ...block,
331
+ columns: columns.length > 0 ? columns : Array.from({ length: columnCount }, () => ''),
332
+ rows: [...rows, Array.from({ length: columnCount }, () => '')]
333
+ };
334
+ });
335
+ },
336
+ removeTableRow(blockID, rowIndex) {
337
+ const block = draftBlocks.find((candidate) => candidate.id === blockID);
338
+ const rows = (block?.rows ?? []).map((row) => [...row]);
339
+ if (!block || rows.length <= 1 || rowIndex < 0 || rowIndex >= rows.length)
340
+ return;
341
+ pushUndoSnapshot();
342
+ draftBlocks = draftBlocks.map((candidate) => {
343
+ if (candidate.id !== blockID)
344
+ return candidate;
345
+ return {
346
+ ...candidate,
347
+ rows: rows.filter((_, index) => index !== rowIndex)
348
+ };
349
+ });
350
+ },
351
+ reorderBlock(draggedID, targetID, placement) {
352
+ if (draggedID === targetID)
353
+ return;
354
+ const draggedBlock = sortedDraftBlocks.find((block) => block.id === draggedID);
355
+ if (!draggedBlock)
356
+ return;
357
+ const nextBlocks = sortedDraftBlocks.filter((block) => block.id !== draggedID);
358
+ const targetIndex = nextBlocks.findIndex((block) => block.id === targetID);
359
+ if (targetIndex === -1)
360
+ return;
361
+ pushUndoSnapshot();
362
+ nextBlocks.splice(placement === 'after' ? targetIndex + 1 : targetIndex, 0, draggedBlock);
363
+ draftBlocks = reindexBlocks(nextBlocks);
364
+ selectedBlockID = draggedID;
365
+ focusTarget = null;
366
+ focusPosition = null;
367
+ },
368
+ createParagraphAfter(id, text = '', currentText = null) {
369
+ const index = draftBlocks.findIndex((block) => block.id === id);
370
+ if (index === -1)
371
+ return;
372
+ pushUndoSnapshot();
373
+ const nextBlocks = [...draftBlocks];
374
+ if (currentText !== null) {
375
+ nextBlocks[index] = {
376
+ ...nextBlocks[index],
377
+ text: currentText,
378
+ textHTML: null
379
+ };
380
+ }
381
+ const newBlock = makeParagraphBlock(index + 1, text);
382
+ nextBlocks.splice(index + 1, 0, newBlock);
383
+ draftBlocks = reindexBlocks(nextBlocks);
384
+ selectedBlockID = newBlock.id;
385
+ focusTarget = newBlock.id;
386
+ focusPosition = currentText !== null ? 0 : null;
387
+ },
388
+ mergeBlockWithPrevious(id) {
389
+ const blocks = sortedDraftBlocks;
390
+ const index = blocks.findIndex((block) => block.id === id);
391
+ const currentBlock = blocks[index];
392
+ const previousBlock = blocks[index - 1];
393
+ if (!currentBlock || !canMergeText(currentBlock) || !canMergeText(previousBlock))
394
+ return;
395
+ const previousText = previousBlock.text ?? '';
396
+ const currentText = currentBlock.text ?? '';
397
+ pushUndoSnapshot();
398
+ draftBlocks = reindexBlocks(blocks
399
+ .filter((block) => block.id !== id)
400
+ .map((block) => block.id === previousBlock.id
401
+ ? {
402
+ ...block,
403
+ text: `${previousText}${currentText}`,
404
+ textHTML: null
405
+ }
406
+ : block));
407
+ selectedBlockID = previousBlock.id;
408
+ focusTarget = previousBlock.id;
409
+ focusPosition = previousText.length;
410
+ },
411
+ insertListItemAfter(blockID, index, text = '', currentText = null) {
412
+ pushUndoSnapshot();
413
+ draftBlocks = draftBlocks.map((block) => {
414
+ if (block.id !== blockID)
415
+ return block;
416
+ const items = [...(block.items ?? [])];
417
+ if (currentText !== null) {
418
+ items[index] = currentText;
419
+ }
420
+ items.splice(index + 1, 0, text);
421
+ return { ...block, items };
422
+ });
423
+ selectedBlockID = blockID;
424
+ focusTarget = `${blockID}:${index + 1}`;
425
+ focusPosition = currentText !== null ? 0 : null;
426
+ },
427
+ mergeListItemWithPrevious(blockID, index) {
428
+ const block = draftBlocks.find((candidate) => candidate.id === blockID);
429
+ const items = [...(block?.items ?? [])];
430
+ if (!block || index <= 0 || index >= items.length)
431
+ return;
432
+ const previousText = items[index - 1] ?? '';
433
+ const currentText = items[index] ?? '';
434
+ pushUndoSnapshot();
435
+ items[index - 1] = `${previousText}${currentText}`;
436
+ items.splice(index, 1);
437
+ draftBlocks = draftBlocks.map((candidate) => candidate.id === blockID ? { ...candidate, items } : candidate);
438
+ selectedBlockID = blockID;
439
+ focusTarget = `${blockID}:${index - 1}`;
440
+ focusPosition = previousText.length;
441
+ },
442
+ removeBlock(id) {
443
+ const index = draftBlocks.findIndex((block) => block.id === id);
444
+ if (index === -1)
445
+ return;
446
+ const nextFocus = draftBlocks[index - 1]?.id ?? draftBlocks[index + 1]?.id ?? null;
447
+ pushUndoSnapshot();
448
+ draftBlocks = reindexBlocks(draftBlocks.filter((block) => block.id !== id));
449
+ selectedBlockID = nextFocus;
450
+ focusTarget = nextFocus;
451
+ focusPosition = null;
452
+ },
453
+ removeListItem(blockID, index) {
454
+ const block = draftBlocks.find((candidate) => candidate.id === blockID);
455
+ const items = [...(block?.items ?? [])];
456
+ if (!block || index < 0 || index >= items.length)
457
+ return;
458
+ if (items.length === 1) {
459
+ pushUndoSnapshot();
460
+ draftBlocks = draftBlocks.map((candidate) => {
461
+ if (candidate.id !== blockID)
462
+ return candidate;
463
+ return {
464
+ ...candidate,
465
+ type: 'paragraph',
466
+ text: '',
467
+ textHTML: null,
468
+ ordered: null,
469
+ items: null,
470
+ listItems: null
471
+ };
472
+ });
473
+ selectedBlockID = blockID;
474
+ focusTarget = blockID;
475
+ focusPosition = null;
476
+ return;
477
+ }
478
+ pushUndoSnapshot();
479
+ items.splice(index, 1);
480
+ draftBlocks = draftBlocks.map((candidate) => candidate.id === blockID ? { ...candidate, items } : candidate);
481
+ selectedBlockID = blockID;
482
+ focusTarget = `${blockID}:${Math.max(0, index - 1)}`;
483
+ focusPosition = null;
484
+ },
485
+ convertToList(id, kind) {
486
+ pushUndoSnapshot();
487
+ draftBlocks = draftBlocks.map((block) => {
488
+ if (block.id !== id)
489
+ return block;
490
+ return {
491
+ ...block,
492
+ type: kind,
493
+ text: null,
494
+ textHTML: null,
495
+ ordered: kind === 'numbered_list',
496
+ items: [''],
497
+ listItems: null,
498
+ faqItems: null,
499
+ columns: null,
500
+ rows: null,
501
+ language: null,
502
+ code: null,
503
+ imageURL: null,
504
+ imageAlt: null
505
+ };
506
+ });
507
+ selectedBlockID = id;
508
+ focusTarget = `${id}:0`;
509
+ focusPosition = null;
510
+ },
511
+ handleFocus() {
512
+ focusTarget = null;
513
+ focusPosition = null;
514
+ },
515
+ undo() {
516
+ const snapshot = undoStack.at(-1);
517
+ if (!snapshot)
518
+ return;
519
+ undoStack = undoStack.slice(0, -1);
520
+ redoStack = [...redoStack, takeSnapshot()].slice(-100);
521
+ restoreSnapshot(snapshot);
522
+ },
523
+ redo() {
524
+ const snapshot = redoStack.at(-1);
525
+ if (!snapshot)
526
+ return;
527
+ redoStack = redoStack.slice(0, -1);
528
+ undoStack = [...undoStack, takeSnapshot()].slice(-100);
529
+ restoreSnapshot(snapshot);
530
+ }
531
+ };
532
+ }
@@ -0,0 +1,18 @@
1
+ export { default as ArticleEditor } from './ArticleEditor.svelte';
2
+ export { default as ArticleEditorBody } from './ArticleEditorBody.svelte';
3
+ export { default as ArticleBlockCode } from './ArticleBlockCode.svelte';
4
+ export { default as ArticleBlockFAQ } from './ArticleBlockFAQ.svelte';
5
+ export { default as ArticleBlockFallback } from './ArticleBlockFallback.svelte';
6
+ export { default as ArticleBlockHeading } from './ArticleBlockHeading.svelte';
7
+ export { default as ArticleBlockImage } from './ArticleBlockImage.svelte';
8
+ export { default as ArticleBlockInsertControl } from './ArticleBlockInsertControl.svelte';
9
+ export { default as ArticleBlockDragControl } from './ArticleBlockDragControl.svelte';
10
+ export { default as ArticleBlockList } from './ArticleBlockList.svelte';
11
+ export { default as ArticleBlockParagraph } from './ArticleBlockParagraph.svelte';
12
+ export { default as ArticleBlockShell } from './ArticleBlockShell.svelte';
13
+ export { default as ArticleBlockTable } from './ArticleBlockTable.svelte';
14
+ export { default as ArticleEditorHeader } from './ArticleEditorHeader.svelte';
15
+ export { default as ArticleImagePreview } from './ArticleImagePreview.svelte';
16
+ export { articleBlockLabel, createArticleEditor } from './articleEditor.svelte.js';
17
+ export type { BlockInsertKind, BlockTextFormat } from './articleEditor.svelte.js';
18
+ export type { ArticleBlock, ArticleEditorArticle, BlockDraft } from './types.js';
@@ -0,0 +1,16 @@
1
+ export { default as ArticleEditor } from './ArticleEditor.svelte';
2
+ export { default as ArticleEditorBody } from './ArticleEditorBody.svelte';
3
+ export { default as ArticleBlockCode } from './ArticleBlockCode.svelte';
4
+ export { default as ArticleBlockFAQ } from './ArticleBlockFAQ.svelte';
5
+ export { default as ArticleBlockFallback } from './ArticleBlockFallback.svelte';
6
+ export { default as ArticleBlockHeading } from './ArticleBlockHeading.svelte';
7
+ export { default as ArticleBlockImage } from './ArticleBlockImage.svelte';
8
+ export { default as ArticleBlockInsertControl } from './ArticleBlockInsertControl.svelte';
9
+ export { default as ArticleBlockDragControl } from './ArticleBlockDragControl.svelte';
10
+ export { default as ArticleBlockList } from './ArticleBlockList.svelte';
11
+ export { default as ArticleBlockParagraph } from './ArticleBlockParagraph.svelte';
12
+ export { default as ArticleBlockShell } from './ArticleBlockShell.svelte';
13
+ export { default as ArticleBlockTable } from './ArticleBlockTable.svelte';
14
+ export { default as ArticleEditorHeader } from './ArticleEditorHeader.svelte';
15
+ export { default as ArticleImagePreview } from './ArticleImagePreview.svelte';
16
+ export { articleBlockLabel, createArticleEditor } from './articleEditor.svelte.js';
@@ -0,0 +1,37 @@
1
+ export type ArticleBlock = {
2
+ id: string;
3
+ order: number;
4
+ type: string;
5
+ sectionID?: string | null;
6
+ level?: number | null;
7
+ text?: string | null;
8
+ textHTML?: string | null;
9
+ ordered?: boolean | null;
10
+ items?: string[] | null;
11
+ listItems?: {
12
+ lead?: string | null;
13
+ body: string;
14
+ bodyHTML?: string | null;
15
+ }[] | null;
16
+ faqItems?: {
17
+ question: string;
18
+ answer: string;
19
+ }[] | null;
20
+ columns?: string[] | null;
21
+ rows?: string[][] | null;
22
+ imageURL?: string | null;
23
+ imageAlt?: string | null;
24
+ caption?: string | null;
25
+ language?: string | null;
26
+ code?: string | null;
27
+ };
28
+ export type BlockDraft = ArticleBlock & {
29
+ id: string;
30
+ };
31
+ export type ArticleEditorArticle = {
32
+ id?: string;
33
+ title: string;
34
+ imageURL?: string | null;
35
+ imageAltText?: string | null;
36
+ articleBlocks?: ArticleBlock[] | null;
37
+ };
@@ -0,0 +1 @@
1
+ export {};