1
1
import Quill from "https://esm.sh/quill@2.0.3" ;
2
2
import { toMarkdown as mdastUtilToMarkdown } from "https://esm.sh/mdast-util-to-markdown@2.1.2" ;
3
3
4
+ /**
5
+ * @typedef {Object } QuillAttributes
6
+ * @property {boolean } [bold] - Whether the text is bold.
7
+ * @property {boolean } [italic] - Whether the text is italic.
8
+ * @property {string } [link] - URL if the text is a link.
9
+ * @property {number } [header] - Header level (1-3).
10
+ * @property {string } [list] - List type ('ordered' or 'bullet').
11
+ * @property {boolean } [blockquote] - Whether the text is in a blockquote.
12
+ * @property {string } [code-block] - Code language if in a code block.
13
+ * @property {string } [alt] - Alt text for images.
14
+ */
15
+
16
+ /**
17
+ * @typedef {Object } QuillOperation
18
+ * @property {string|Object } [insert] - Content to insert (string or object with image URL).
19
+ * @property {number } [delete] - Number of characters to delete.
20
+ * @property {number } [retain] - Number of characters to retain.
21
+ * @property {QuillAttributes } [attributes] - Formatting attributes.
22
+ */
23
+
24
+ /**
25
+ * @typedef {Object } QuillDelta
26
+ * @property {Array<QuillOperation> } ops - Array of operations in the delta.
27
+ */
28
+
4
29
/**
5
30
* Converts Quill Delta object to a Markdown string using mdast.
6
- * @param {object } delta - Quill Delta object (https://quilljs.com/docs/delta/).
31
+ * @param {QuillDelta } delta - Quill Delta object (https://quilljs.com/docs/delta/).
7
32
* @returns {string } - Markdown representation.
8
33
*/
9
34
function deltaToMarkdown ( delta ) {
@@ -26,6 +51,11 @@ function deltaToMarkdown(delta) {
26
51
}
27
52
}
28
53
54
+ /**
55
+ * Extracts plain text from a Quill Delta object.
56
+ * @param {QuillDelta } delta - Quill Delta object.
57
+ * @returns {string } - Plain text extracted from the delta.
58
+ */
29
59
function extractPlainTextFromDelta ( delta ) {
30
60
try {
31
61
return delta . ops
@@ -60,7 +90,7 @@ function createAndReplaceTextarea(textarea) {
60
90
61
91
/**
62
92
* Returns the toolbar options array configured for Markdown compatibility.
63
- * @returns {Array } - Quill toolbar options.
93
+ * @returns {Array<Array<any>> } - Quill toolbar options.
64
94
*/
65
95
function getMarkdownToolbarOptions ( ) {
66
96
return [
@@ -75,7 +105,7 @@ function getMarkdownToolbarOptions() {
75
105
/**
76
106
* Initializes a Quill editor instance on a given div.
77
107
* @param {HTMLDivElement } editorDiv - The div element for the editor.
78
- * @param {Array } toolbarOptions - The toolbar configuration.
108
+ * @param {Array<Array<any>> } toolbarOptions - The toolbar configuration.
79
109
* @param {string } initialValue - The initial content for the editor.
80
110
* @returns {Quill } - The initialized Quill instance.
81
111
*/
@@ -94,9 +124,10 @@ function initializeQuillEditor(editorDiv, toolbarOptions, initialValue) {
94
124
95
125
/**
96
126
* Attaches a submit event listener to the form to update the hidden textarea.
97
- * @param {HTMLFormElement } form - The form containing the editor.
127
+ * @param {HTMLFormElement|null } form - The form containing the editor.
98
128
* @param {HTMLTextAreaElement } textarea - The original (hidden) textarea.
99
129
* @param {Quill } quill - The Quill editor instance.
130
+ * @returns {void }
100
131
*/
101
132
function updateTextareaOnSubmit ( form , textarea , quill ) {
102
133
if ( ! form ) {
@@ -113,13 +144,23 @@ function updateTextareaOnSubmit(form, textarea, quill) {
113
144
} ) ;
114
145
}
115
146
147
+ /**
148
+ * Loads the Quill CSS stylesheet.
149
+ * @returns {void }
150
+ */
116
151
function loadQuillStylesheet ( ) {
117
152
const link = document . createElement ( "link" ) ;
118
153
link . rel = "stylesheet" ;
119
154
link . href = "https://esm.sh/quill@2.0.3/dist/quill.snow.css" ;
120
155
document . head . appendChild ( link ) ;
121
156
}
122
157
158
+ /**
159
+ * Handles errors during editor initialization.
160
+ * @param {HTMLTextAreaElement } textarea - The textarea that failed initialization.
161
+ * @param {Error } error - The error that occurred.
162
+ * @returns {void }
163
+ */
123
164
function handleEditorInitError ( textarea , error ) {
124
165
console . error ( "Failed to initialize Quill for textarea:" , textarea , error ) ;
125
166
textarea . style . display = "" ;
@@ -129,6 +170,12 @@ function handleEditorInitError(textarea, error) {
129
170
textarea . parentNode . insertBefore ( errorMsg , textarea . nextSibling ) ;
130
171
}
131
172
173
+ /**
174
+ * Sets up a single editor for a textarea.
175
+ * @param {HTMLTextAreaElement } textarea - The textarea to replace with an editor.
176
+ * @param {Array<Array<any>> } toolbarOptions - The toolbar configuration.
177
+ * @returns {boolean } - Whether the setup was successful.
178
+ */
132
179
function setupSingleEditor ( textarea , toolbarOptions ) {
133
180
if ( textarea . dataset . quillInitialized === "true" ) {
134
181
return false ;
@@ -152,6 +199,10 @@ function setupSingleEditor(textarea, toolbarOptions) {
152
199
}
153
200
}
154
201
202
+ /**
203
+ * Initializes Quill editors for all textareas in the document.
204
+ * @returns {void }
205
+ */
155
206
function initializeEditors ( ) {
156
207
loadQuillStylesheet ( ) ;
157
208
@@ -177,9 +228,30 @@ function initializeEditors() {
177
228
}
178
229
179
230
// MDAST conversion functions
231
+ /**
232
+ * @typedef {Object } MdastNode
233
+ * @property {string } type - The type of the node.
234
+ * @property {Array<MdastNode> } [children] - Child nodes.
235
+ * @property {string } [value] - Text value for text nodes.
236
+ * @property {string } [url] - URL for link and image nodes.
237
+ * @property {string } [title] - Title for image nodes.
238
+ * @property {string } [alt] - Alt text for image nodes.
239
+ * @property {number } [depth] - Depth for heading nodes.
240
+ * @property {boolean } [ordered] - Whether the list is ordered.
241
+ * @property {boolean } [spread] - Whether the list is spread.
242
+ * @property {string } [lang] - Language for code blocks.
243
+ */
244
+
245
+ /**
246
+ * Converts a Quill Delta to a MDAST (Markdown Abstract Syntax Tree).
247
+ * @param {QuillDelta } delta - The Quill Delta to convert.
248
+ * @returns {MdastNode } - The root MDAST node.
249
+ */
180
250
function deltaToMdast ( delta ) {
181
251
const mdast = createRootNode ( ) ;
252
+ /** @type {MdastNode|null } */
182
253
let currentParagraph = null ;
254
+ /** @type {MdastNode|null } */
183
255
let currentList = null ;
184
256
let textBuffer = "" ;
185
257
@@ -239,24 +311,42 @@ function deltaToMdast(delta) {
239
311
return mdast ;
240
312
}
241
313
314
+ /**
315
+ * Creates a root MDAST node.
316
+ * @returns {MdastNode } - The root node.
317
+ */
242
318
function createRootNode ( ) {
243
319
return {
244
320
type : "root" ,
245
321
children : [ ] ,
246
322
} ;
247
323
}
248
324
325
+ /**
326
+ * Creates a paragraph MDAST node.
327
+ * @returns {MdastNode } - The paragraph node.
328
+ */
249
329
function createParagraphNode ( ) {
250
330
return {
251
331
type : "paragraph" ,
252
332
children : [ ] ,
253
333
} ;
254
334
}
255
335
336
+ /**
337
+ * Checks if an operation is an image insertion.
338
+ * @param {Object } op - The operation to check.
339
+ * @returns {boolean } - Whether the operation is an image insertion.
340
+ */
256
341
function isImageInsert ( op ) {
257
342
return typeof op . insert === "object" && op . insert . image ;
258
343
}
259
344
345
+ /**
346
+ * Creates an image MDAST node.
347
+ * @param {Object } op - The operation containing the image.
348
+ * @returns {MdastNode } - The image node.
349
+ */
260
350
function createImageNode ( op ) {
261
351
return {
262
352
type : "image" ,
@@ -266,6 +356,12 @@ function createImageNode(op) {
266
356
} ;
267
357
}
268
358
359
+ /**
360
+ * Creates a text MDAST node with optional formatting.
361
+ * @param {string } text - The text content.
362
+ * @param {Object } attributes - The formatting attributes.
363
+ * @returns {MdastNode } - The formatted text node.
364
+ */
269
365
function createTextNode ( text , attributes ) {
270
366
let node = {
271
367
type : "text" ,
@@ -291,13 +387,28 @@ function createTextNode(text, attributes) {
291
387
return node ;
292
388
}
293
389
390
+ /**
391
+ * Wraps a node with a formatting container.
392
+ * @param {MdastNode } node - The node to wrap.
393
+ * @param {string } type - The type of container.
394
+ * @returns {MdastNode } - The wrapped node.
395
+ */
294
396
function wrapNodeWith ( node , type ) {
295
397
return {
296
398
type : type ,
297
399
children : [ node ] ,
298
400
} ;
299
401
}
300
402
403
+ /**
404
+ * Processes a line break in the Delta.
405
+ * @param {MdastNode } mdast - The root MDAST node.
406
+ * @param {MdastNode|null } currentParagraph - The current paragraph being built.
407
+ * @param {Object } attributes - The attributes for the line.
408
+ * @param {string } textBuffer - The text buffer for the current line.
409
+ * @param {MdastNode|null } currentList - The current list being built.
410
+ * @returns {void }
411
+ */
301
412
function processLineBreak (
302
413
mdast ,
303
414
currentParagraph ,
@@ -323,6 +434,13 @@ function processLineBreak(
323
434
}
324
435
}
325
436
437
+ /**
438
+ * Handles an empty line with special attributes.
439
+ * @param {MdastNode } mdast - The root MDAST node.
440
+ * @param {Object } attributes - The attributes for the line.
441
+ * @param {MdastNode|null } currentList - The current list being built.
442
+ * @returns {void }
443
+ */
326
444
function handleEmptyLineWithAttributes ( mdast , attributes , currentList ) {
327
445
if ( attributes [ "code-block" ] ) {
328
446
mdast . children . push ( createEmptyCodeBlock ( attributes ) ) ;
@@ -334,6 +452,11 @@ function handleEmptyLineWithAttributes(mdast, attributes, currentList) {
334
452
}
335
453
}
336
454
455
+ /**
456
+ * Creates an empty code block MDAST node.
457
+ * @param {Object } attributes - The attributes for the code block.
458
+ * @returns {MdastNode } - The code block node.
459
+ */
337
460
function createEmptyCodeBlock ( attributes ) {
338
461
return {
339
462
type : "code" ,
@@ -343,6 +466,10 @@ function createEmptyCodeBlock(attributes) {
343
466
} ;
344
467
}
345
468
469
+ /**
470
+ * Creates an empty list item MDAST node.
471
+ * @returns {MdastNode } - The list item node.
472
+ */
346
473
function createEmptyListItem ( ) {
347
474
return {
348
475
type : "listItem" ,
@@ -351,13 +478,24 @@ function createEmptyListItem() {
351
478
} ;
352
479
}
353
480
481
+ /**
482
+ * Creates an empty blockquote MDAST node.
483
+ * @returns {MdastNode } - The blockquote node.
484
+ */
354
485
function createEmptyBlockquote ( ) {
355
486
return {
356
487
type : "blockquote" ,
357
488
children : [ { type : "paragraph" , children : [ ] } ] ,
358
489
} ;
359
490
}
360
491
492
+ /**
493
+ * Processes a header line break.
494
+ * @param {MdastNode } mdast - The root MDAST node.
495
+ * @param {string } textBuffer - The text buffer for the current line.
496
+ * @param {Object } attributes - The attributes for the line.
497
+ * @returns {void }
498
+ */
361
499
function processHeaderLineBreak ( mdast , textBuffer , attributes ) {
362
500
const lines = textBuffer . split ( "\n" ) ;
363
501
@@ -386,6 +524,13 @@ function processHeaderLineBreak(mdast, textBuffer, attributes) {
386
524
}
387
525
}
388
526
527
+ /**
528
+ * Processes a code block line break.
529
+ * @param {MdastNode } mdast - The root MDAST node.
530
+ * @param {string } textBuffer - The text buffer for the current line.
531
+ * @param {Object } attributes - The attributes for the line.
532
+ * @returns {void }
533
+ */
389
534
function processCodeBlockLineBreak ( mdast , textBuffer , attributes ) {
390
535
mdast . children . push ( {
391
536
type : "code" ,
@@ -395,6 +540,13 @@ function processCodeBlockLineBreak(mdast, textBuffer, attributes) {
395
540
} ) ;
396
541
}
397
542
543
+ /**
544
+ * Ensures a list exists in the MDAST.
545
+ * @param {MdastNode } mdast - The root MDAST node.
546
+ * @param {Object } attributes - The attributes for the line.
547
+ * @param {MdastNode|null } currentList - The current list being built.
548
+ * @returns {MdastNode } - The list node.
549
+ */
398
550
function ensureList ( mdast , attributes , currentList ) {
399
551
if ( ! currentList || currentList . ordered !== ( attributes . list === "ordered" ) ) {
400
552
const newList = {
@@ -409,6 +561,14 @@ function ensureList(mdast, attributes, currentList) {
409
561
return currentList ;
410
562
}
411
563
564
+ /**
565
+ * Processes a list line break.
566
+ * @param {MdastNode } mdast - The root MDAST node.
567
+ * @param {MdastNode } currentParagraph - The current paragraph being built.
568
+ * @param {Object } attributes - The attributes for the line.
569
+ * @param {MdastNode|null } currentList - The current list being built.
570
+ * @returns {void }
571
+ */
412
572
function processListLineBreak (
413
573
mdast ,
414
574
currentParagraph ,
@@ -426,6 +586,12 @@ function processListLineBreak(
426
586
list . children . push ( listItem ) ;
427
587
}
428
588
589
+ /**
590
+ * Processes a blockquote line break.
591
+ * @param {MdastNode } mdast - The root MDAST node.
592
+ * @param {MdastNode } currentParagraph - The current paragraph being built.
593
+ * @returns {void }
594
+ */
429
595
function processBlockquoteLineBreak ( mdast , currentParagraph ) {
430
596
mdast . children . push ( {
431
597
type : "blockquote" ,
@@ -435,5 +601,3 @@ function processBlockquoteLineBreak(mdast, currentParagraph) {
435
601
436
602
// Main execution
437
603
document . addEventListener ( "DOMContentLoaded" , initializeEditors ) ;
438
-
439
- export { deltaToMdast } ;
0 commit comments