1
1
import { toMarkdown as mdastUtilToMarkdown } from "https://esm.sh/[email protected] " ;
2
2
import Quill from "https://esm.sh/[email protected] " ;
3
+ import { fromMarkdown } from "https://esm.sh/[email protected] " ;
3
4
4
5
/**
5
6
* @typedef {Object } QuillAttributes
@@ -61,7 +62,8 @@ function createAndReplaceTextarea(textarea) {
61
62
} else {
62
63
label . parentNode . insertBefore ( editorDiv , label . nextSibling ) ;
63
64
}
64
- textarea . style . display = "none" ;
65
+ // Hide the original textarea, but keep it focusable for validation
66
+ textarea . style = "transform: scale(0); position: absolute; opacity: 0;" ;
65
67
return editorDiv ;
66
68
}
67
69
@@ -105,11 +107,146 @@ function initializeQuillEditor(editorDiv, toolbarOptions, initialValue) {
105
107
] ,
106
108
} ) ;
107
109
if ( initialValue ) {
108
- quill . setText ( initialValue ) ;
110
+ const delta = markdownToDelta ( initialValue ) ;
111
+ quill . setContents ( delta ) ;
109
112
}
110
113
return quill ;
111
114
}
112
115
116
+ /**
117
+ * Converts Markdown string to a Quill Delta object.
118
+ * @param {string } markdown - The markdown string to convert.
119
+ * @returns {QuillDelta } - Quill Delta representation.
120
+ */
121
+ function markdownToDelta ( markdown ) {
122
+ try {
123
+ const mdastTree = fromMarkdown ( markdown ) ;
124
+ return mdastToDelta ( mdastTree ) ;
125
+ } catch ( error ) {
126
+ console . error ( "Error parsing markdown:" , error ) ;
127
+ return { ops : [ { insert : markdown } ] } ;
128
+ }
129
+ }
130
+
131
+ /**
132
+ * Converts MDAST to Quill Delta.
133
+ * @param {MdastNode } tree - The MDAST tree to convert.
134
+ * @returns {QuillDelta } - Quill Delta representation.
135
+ */
136
+ function mdastToDelta ( tree ) {
137
+ const delta = { ops : [ ] } ;
138
+ if ( ! tree || ! tree . children ) return delta ;
139
+
140
+ for ( const node of tree . children ) {
141
+ traverseMdastNode ( node , delta ) ;
142
+ }
143
+
144
+ return delta ;
145
+ }
146
+
147
+ /**
148
+ * Recursively traverse MDAST nodes and convert to Delta operations.
149
+ * @param {MdastNode } node - The MDAST node to process.
150
+ * @param {QuillDelta } delta - The Delta object to append operations to.
151
+ * @param {QuillAttributes } [attributes={}] - The current attributes to apply.
152
+ */
153
+ function traverseMdastNode ( node , delta , attributes = { } ) {
154
+ if ( ! node ) return ;
155
+
156
+ switch ( node . type ) {
157
+ case 'root' :
158
+ for ( const child of node . children || [ ] ) {
159
+ traverseMdastNode ( child , delta ) ;
160
+ }
161
+ break ;
162
+
163
+ case 'paragraph' :
164
+ for ( const child of node . children || [ ] ) {
165
+ traverseMdastNode ( child , delta , attributes ) ;
166
+ }
167
+ delta . ops . push ( { insert : '\n' } ) ;
168
+ break ;
169
+
170
+ case 'heading' :
171
+ for ( const child of node . children || [ ] ) {
172
+ traverseMdastNode ( child , delta , { header : node . depth } ) ;
173
+ }
174
+ delta . ops . push ( { insert : '\n' , attributes : { header : node . depth } } ) ;
175
+ break ;
176
+
177
+ case 'text' :
178
+ delta . ops . push ( { insert : node . value || '' , attributes } ) ;
179
+ break ;
180
+
181
+ case 'strong' :
182
+ for ( const child of node . children || [ ] ) {
183
+ traverseMdastNode ( child , delta , { ...attributes , bold : true } ) ;
184
+ }
185
+ break ;
186
+
187
+ case 'emphasis' :
188
+ for ( const child of node . children || [ ] ) {
189
+ traverseMdastNode ( child , delta , { ...attributes , italic : true } ) ;
190
+ }
191
+ break ;
192
+
193
+ case 'link' :
194
+ for ( const child of node . children || [ ] ) {
195
+ traverseMdastNode ( child , delta , { ...attributes , link : node . url } ) ;
196
+ }
197
+ break ;
198
+
199
+ case 'image' :
200
+ delta . ops . push ( {
201
+ insert : { image : node . url } ,
202
+ attributes : { alt : node . alt || '' }
203
+ } ) ;
204
+ break ;
205
+
206
+ case 'list' :
207
+ for ( const child of node . children || [ ] ) {
208
+ traverseMdastNode ( child , delta , {
209
+ ...attributes ,
210
+ list : node . ordered ? 'ordered' : 'bullet'
211
+ } ) ;
212
+ }
213
+ break ;
214
+
215
+ case 'listItem' :
216
+ for ( const child of node . children || [ ] ) {
217
+ traverseMdastNode ( child , delta , attributes ) ;
218
+ }
219
+ break ;
220
+
221
+ case 'blockquote' :
222
+ for ( const child of node . children || [ ] ) {
223
+ traverseMdastNode ( child , delta , { ...attributes , blockquote : true } ) ;
224
+ }
225
+ break ;
226
+
227
+ case 'code' :
228
+ delta . ops . push ( {
229
+ insert : node . value || '' ,
230
+ attributes : { 'code-block' : node . lang || 'plain' }
231
+ } ) ;
232
+ delta . ops . push ( { insert : '\n' , attributes : { 'code-block' : node . lang || 'plain' } } ) ;
233
+ break ;
234
+
235
+ case 'inlineCode' :
236
+ delta . ops . push ( { insert : node . value || '' , attributes : { code : true } } ) ;
237
+ break ;
238
+
239
+ default :
240
+ if ( node . children ) {
241
+ for ( const child of node . children ) {
242
+ traverseMdastNode ( child , delta , attributes ) ;
243
+ }
244
+ } else if ( node . value ) {
245
+ delta . ops . push ( { insert : node . value , attributes } ) ;
246
+ }
247
+ }
248
+ }
249
+
113
250
/**
114
251
* Attaches a submit event listener to the form to update the hidden textarea.
115
252
* @param {HTMLFormElement|null } form - The form containing the editor.
@@ -125,10 +262,19 @@ function updateTextareaOnSubmit(form, textarea, quill) {
125
262
) ;
126
263
return ;
127
264
}
128
- form . addEventListener ( "submit" , ( ) => {
265
+ form . addEventListener ( "submit" , ( event ) => {
129
266
const delta = quill . getContents ( ) ;
130
267
const markdownContent = deltaToMarkdown ( delta ) ;
131
268
textarea . value = markdownContent ;
269
+ if ( textarea . required && ! markdownContent ) {
270
+ textarea . setCustomValidity ( `${ textarea . name } cannot be empty` ) ;
271
+ quill . once ( "text-change" , ( delta ) => {
272
+ textarea . value = deltaToMarkdown ( delta ) ;
273
+ textarea . setCustomValidity ( "" ) ;
274
+ } ) ;
275
+ quill . focus ( ) ;
276
+ event . preventDefault ( ) ;
277
+ }
132
278
} ) ;
133
279
}
134
280
0 commit comments