22const IGNORE_UNSUPPORTED = [ Comment , HTMLBodyElement ] ;
33
44$ ( ( ) => {
5- DOMPurify . addHook ( " uponSanitizeAttribute" , ( node , event ) => {
6- const rowspan = node . getAttribute ( " rowspan" ) ;
7- const colspan = node . getAttribute ( " colspan" ) ;
5+ DOMPurify . addHook ( ' uponSanitizeAttribute' , ( node , event ) => {
6+ const rowspan = node . getAttribute ( ' rowspan' ) ;
7+ const colspan = node . getAttribute ( ' colspan' ) ;
88
99 if ( rowspan && Number . isNaN ( + rowspan ) ) {
1010 event . keepAttr = false ;
@@ -29,7 +29,7 @@ $(() => {
2929 */
3030 const stringInsert = ( str , idx , insert ) => str . slice ( 0 , idx ) + insert + str . slice ( idx ) ;
3131
32- const placeholder = " ![Uploading, please wait...]()" ;
32+ const placeholder = ' ![Uploading, please wait...]()' ;
3333
3434 $uploadForm . find ( 'input[type="file"]' ) . on ( 'change' , async ( evt ) => {
3535 /** @type {HTMLInputElement } */
@@ -39,7 +39,7 @@ $(() => {
3939
4040 postField . value = stringInsert ( postText , cursorPos , placeholder ) ;
4141
42- $uploadForm . trigger ( 'submit' )
42+ $uploadForm . trigger ( 'submit' ) ;
4343 } ) ;
4444
4545 QPixel . DOM ?. watchClass ( '#markdown-image-upload.is-active' , ( target ) => {
@@ -67,7 +67,8 @@ $(() => {
6767
6868 if ( ! isUploadModalOpened ) {
6969 QPixel . createNotification ( 'danger' , `Can't upload files with size more than 2MB` ) ;
70- } else {
70+ }
71+ else {
7172 $tgt . find ( '.js-max-size' ) . addClass ( 'has-color-red-700 error-shake' ) ;
7273 setTimeout ( ( ) => {
7374 $tgt . find ( '.js-max-size' ) . removeClass ( 'error-shake' ) ;
@@ -82,7 +83,7 @@ $(() => {
8283
8384 const resp = await fetch ( $tgt . attr ( 'action' ) , {
8485 method : $tgt . attr ( 'method' ) ,
85- body : new FormData ( /** @type {HTMLFormElement } */ ( $tgt [ 0 ] ) )
86+ body : new FormData ( /** @type {HTMLFormElement } */ ( $tgt [ 0 ] ) ) ,
8687 } ) ;
8788
8889 const data = await resp . json ( ) ;
@@ -104,7 +105,7 @@ $(() => {
104105 $postField . val ( postText . replace ( placeholder , `` ) ) ;
105106 $tgt . parents ( '.modal' ) . removeClass ( 'is-active' ) ;
106107
107- $postFields . trigger ( 'change' )
108+ $postFields . trigger ( 'change' ) ;
108109 } ) ;
109110
110111 $uploadForm . on ( 'ajax:failure' , async ( evt , data ) => {
@@ -168,13 +169,20 @@ $(() => {
168169 * @returns {Promise<boolean> }
169170 */
170171 const deleteDraft = async ( $field ) => {
171- const data = await QPixel . deleteDraft ( ) ;
172+ // TODO: extra error handling should be moved to QPixel#deleteDraft
173+ try {
174+ const data = await QPixel . deleteDraft ( ) ;
172175
173- return QPixel . handleJSONResponse ( data , ( ) => {
174- flashDraftStatus ( $field , 'draft deleted' ) ;
175- removeDraftLoadedNotice ( ) ;
176- } ) ;
177- }
176+ return QPixel . handleJSONResponse ( data , ( ) => {
177+ flashDraftStatus ( $field , 'draft deleted' ) ;
178+ removeDraftLoadedNotice ( ) ;
179+ } ) ;
180+ }
181+ catch ( error ) {
182+ QPixel . createNotification ( 'danger' , `Failed to delete post draft` ) ;
183+ return false ;
184+ }
185+ } ;
178186
179187 /**
180188 * Helper for getting draft-related elements from a given event target
@@ -204,7 +212,7 @@ $(() => {
204212
205213 const $licenseField = $form . find ( '.js-license-select' ) ;
206214 const $excerptField = $form . find ( '.js-tag-excerpt' ) ;
207-
215+
208216 const $tagsField = $form . find ( '#post_tags_cache' ) ;
209217 const $titleField = $form . find ( '#post_title' ) ;
210218 const $commentField = $form . find ( '#edit_comment' ) ;
@@ -224,7 +232,7 @@ $(() => {
224232 comment : commentText ,
225233 excerpt : excerptText ,
226234 license : license ,
227- tags : Array . isArray ( tags ) ? tags : [ ] ,
235+ tags : Array . isArray ( tags ) ? tags : [ ] ,
228236 tag_name : tagName ,
229237 title : titleText ,
230238 } ;
@@ -269,7 +277,7 @@ $(() => {
269277 const eventData = /** @type {ClipboardEvent } */ ( evt . originalEvent ) ;
270278 if ( eventData . clipboardData . files . length > 0 ) {
271279 // must be called to prevent raw file name to be inserted after the placeholder
272- evt . preventDefault ( )
280+ evt . preventDefault ( ) ;
273281
274282 /** @type {JQuery<HTMLInputElement> } */
275283 const $fileInput = $uploadForm . find ( 'input[type="file"]' ) ;
@@ -278,53 +286,79 @@ $(() => {
278286 }
279287 } ) ;
280288
281- $postFields . on ( 'focus keyup paste change markdown' , ( ( ) => {
289+ const onPostFieldChange = ( ( ) => {
290+ /** @type {string | null } */
282291 let previous = null ;
292+
293+ /**
294+ * @param {JQuery.EventBase } evt
295+ */
283296 return ( evt ) => {
284297 const $tgt = $ ( evt . target ) ;
285298 const text = $ ( evt . target ) . val ( ) ;
299+
286300 // Don't bother re-rendering if nothing's changed
287- if ( text === previous ) { return ; }
301+ if ( text === previous ) {
302+ return ;
303+ }
304+
288305 previous = text ;
306+
289307 if ( ! window . converter ) {
290308 window . converter = window . markdownit ( {
291309 html : true ,
292310 breaks : false ,
293- linkify : true
311+ linkify : true ,
294312 } ) ;
295313 window . converter . use ( window . markdownitFootnote ) ;
296314 window . converter . use ( window . latexEscape ) ;
297315 }
316+
298317 window . setTimeout ( ( ) => {
299318 const converter = window . converter ;
300319 const unsafe_html = converter . render ( text ) ;
301320 const html = DOMPurify . sanitize ( unsafe_html , {
302321 ALLOWED_TAGS : QPixel . ALLOWED_POST_TAGS ,
303- ALLOWED_ATTR : QPixel . ALLOWED_POST_ATTRS
322+ ALLOWED_ATTR : QPixel . ALLOWED_POST_ATTRS ,
304323 } ) ;
305324
306- const removedElements = [ ...new Set ( DOMPurify . removed
307- . filter ( ( entry ) => entry . element && ! IGNORE_UNSUPPORTED . some ( ( ctor ) => entry . element instanceof ctor ) )
308- . map ( ( entry ) => entry . element . localName ) ) ] ;
309-
310- const removedAttributes = [ ...new Set ( DOMPurify . removed
311- . filter ( ( entry ) => entry . attribute )
312- . map ( ( entry ) => [
313- entry . attribute . name + ( entry . attribute . value ? `='${ entry . attribute . value } '` : '' ) ,
314- entry . from . localName
315- ] ) ) ]
316-
317- $tgt . parents ( 'form' )
325+ const removedElements = [
326+ ...new Set (
327+ DOMPurify . removed
328+ . filter ( ( entry ) => entry . element && ! IGNORE_UNSUPPORTED . some ( ( ctor ) => entry . element instanceof ctor ) )
329+ . map ( ( entry ) => entry . element . localName ) ,
330+ ) ,
331+ ] ;
332+
333+ const removedAttributes = [
334+ ...new Set (
335+ DOMPurify . removed
336+ . filter ( ( entry ) => entry . attribute )
337+ . map ( ( entry ) => [
338+ entry . attribute . name + ( entry . attribute . value ? `='${ entry . attribute . value } '` : '' ) ,
339+ entry . from . localName ,
340+ ] ) ,
341+ ) ,
342+ ] ;
343+
344+ $tgt
345+ . parents ( 'form' )
318346 . find ( '.rejected-elements' )
319347 . toggleClass ( 'hide' , removedElements . length === 0 && removedAttributes . length === 0 )
320348 . find ( 'ul' )
321349 . empty ( )
322350 . append (
323351 removedElements . map ( ( name ) => $ ( `<li><code><${ name } ></code></li>` ) ) ,
324- removedAttributes . map ( ( [ attr , elName ] ) => $ ( `<li><code>${ attr } </code> (in <code><${ elName } ></code>)</li>` ) ) ) ;
352+ removedAttributes . map ( ( [ attr , elName ] ) =>
353+ $ ( `<li><code>${ attr } </code> (in <code><${ elName } ></code>)</li>` ) ,
354+ ) ,
355+ ) ;
325356
326357 $tgt . parents ( '.form-group' ) . siblings ( '.post-preview' ) . html ( html ) ;
327- $tgt . parents ( 'form' ) . find ( '.js-post-html[name="__html"]' ) . val ( html + '<!-- g: js, mdit -->' ) ;
358+ $tgt
359+ . parents ( 'form' )
360+ . find ( '.js-post-html[name="__html"]' )
361+ . val ( html + '<!-- g: js, mdit -->' ) ;
328362 } , 0 ) ;
329363
330364 if ( featureTimeout ) {
@@ -340,79 +374,60 @@ $(() => {
340374 }
341375 } , 1000 ) ;
342376 } ;
343- } ) ( ) ) . trigger ( 'markdown' ) ;
377+ } ) ( ) ;
378+
379+ $postFields . on ( 'focus keyup paste change markdown' , onPostFieldChange ) . trigger ( 'markdown' ) ;
344380
345381 $postFields . parents ( 'form' ) . on ( 'submit' , async ( ev ) => {
346382 const $tgt = $ ( ev . target ) ;
347383 const $field = $tgt . find ( '.js-post-field' ) ;
348384
349- const draftDeleted = $tgt . attr ( 'data-draft-deleted' ) === 'true' ;
350385 const isValidated = $tgt . attr ( 'data-validated' ) === 'true' ;
351386
352- if ( draftDeleted && isValidated ) {
387+ if ( isValidated ) {
353388 return ;
354389 }
355390
356391 ev . preventDefault ( ) ;
357392
358- // Draft handling
359- if ( ! draftDeleted ) {
360- const status = await deleteDraft ( $field ) ;
361-
362- if ( status ) {
363- $tgt . attr ( 'data-draft-deleted' , 'true' ) ;
364-
365- if ( isValidated ) {
366- $tgt . submit ( ) ;
367- }
368- }
369- else {
370- QPixel . createNotification ( 'danger' , `Failed to delete post draft. (${ status } )` ) ;
371- }
393+ const text = $field . val ( ) ?. toString ( ) ;
394+ const validated = QPixel . validatePost ( text ) ;
395+ if ( validated [ 0 ] === true ) {
396+ $tgt . attr ( 'data-validated' , 'true' ) ;
397+ $tgt . submit ( ) ;
372398 }
373-
374-
375- // Validation
376- if ( ! isValidated ) {
377- const text = $field . val ( ) ?. toString ( ) ;
378- const validated = QPixel . validatePost ( text ) ;
379- if ( validated [ 0 ] === true ) {
380- $tgt . attr ( 'data-validated' , 'true' ) ;
381- $tgt . submit ( ) ;
399+ else {
400+ const warnings = validated [ 1 ] . filter ( ( x ) => x [ 'type' ] === 'warning' ) ;
401+ const errors = validated [ 1 ] . filter ( ( x ) => x [ 'type' ] === 'error' ) ;
402+
403+ if ( warnings . length > 0 ) {
404+ const $warningBox = $ ( `<div class="notice is-warning"></div>` ) ;
405+ const $warningList = $ ( `<ul></ul>` ) ;
406+ warnings . forEach ( ( w ) => {
407+ $warningList . append ( `<li>${ w [ 'message' ] } </li>` ) ;
408+ } ) ;
409+ $warningBox . append ( $warningList ) ;
410+ $tgt . find ( 'input[type="submit"]' ) . before ( $warningBox ) ;
382411 }
383- else {
384- const warnings = validated [ 1 ] . filter ( ( x ) => x [ 'type' ] === 'warning' ) ;
385- const errors = validated [ 1 ] . filter ( ( x ) => x [ 'type' ] === 'error' ) ;
386-
387- if ( warnings . length > 0 ) {
388- const $warningBox = $ ( `<div class="notice is-warning"></div>` ) ;
389- const $warningList = $ ( `<ul></ul>` ) ;
390- warnings . forEach ( ( w ) => {
391- $warningList . append ( `<li>${ w [ 'message' ] } </li>` ) ;
392- } ) ;
393- $warningBox . append ( $warningList ) ;
394- $tgt . find ( 'input[type="submit"]' ) . before ( $warningBox ) ;
395- }
396412
397- if ( errors . length > 0 ) {
398- const $errorBox = $ ( `<div class="notice is-danger"></div>` ) ;
399- const $errorList = $ ( `<ul></ul>` ) ;
400- errors . forEach ( ( e ) => {
401- $errorList . append ( `<li>${ e [ 'message' ] } </li>` ) ;
402- } ) ;
403- $errorBox . append ( $errorList ) ;
404- $tgt . find ( 'input[type="submit"]' ) . before ( $errorBox ) ;
405- }
406-
407- if ( warnings . length > 0 && errors . length === 0 ) {
408- $tgt . attr ( 'data-validated' , 'true' ) ;
409- }
413+ if ( errors . length > 0 ) {
414+ const $errorBox = $ ( `<div class="notice is-danger"></div>` ) ;
415+ const $errorList = $ ( `<ul></ul>` ) ;
416+ errors . forEach ( ( e ) => {
417+ $errorList . append ( `<li>${ e [ 'message' ] } </li>` ) ;
418+ } ) ;
419+ $errorBox . append ( $errorList ) ;
420+ $tgt . find ( 'input[type="submit"]' ) . before ( $errorBox ) ;
410421 }
411422
412- setTimeout ( ( ) => {
413- $tgt . find ( 'input[type="submit"]' ) . attr ( 'disabled ' , null ) ;
414- } , 1000 ) ;
423+ if ( warnings . length > 0 && errors . length === 0 ) {
424+ $tgt . attr ( 'data-validated ' , 'true' ) ;
425+ }
415426 }
427+
428+ setTimeout ( ( ) => {
429+ $tgt . find ( 'input[type="submit"]' ) . attr ( 'disabled' , null ) ;
430+ } , 1000 ) ;
416431 } ) ;
417432
418433 $ ( '.js-draft-loaded' ) . each ( ( _i , e ) => {
@@ -423,36 +438,34 @@ $(() => {
423438 } ) ;
424439
425440 const setCopyButtonState = ( $button , state ) => {
426- const isSuccess = state === " success" ;
427- const buttonClass = isSuccess ? " is-green" : " is-danger" ;
428- const iconClass = isSuccess ? " fa-check" : " fa-times" ;
441+ const isSuccess = state === ' success' ;
442+ const buttonClass = isSuccess ? ' is-green' : ' is-danger' ;
443+ const iconClass = isSuccess ? ' fa-check' : ' fa-times' ;
429444
430- const $icon = $button . find ( " .fa" ) ;
445+ const $icon = $button . find ( ' .fa' ) ;
431446
432- $icon . removeClass ( " fa-copy" ) ;
447+ $icon . removeClass ( ' fa-copy' ) ;
433448 $icon . addClass ( iconClass ) ;
434449 $button . addClass ( buttonClass ) ;
435450
436451 setTimeout ( ( ) => {
437452 $icon . removeClass ( iconClass ) ;
438453 $button . removeClass ( buttonClass ) ;
439- $icon . addClass ( " fa-copy" ) ;
454+ $icon . addClass ( ' fa-copy' ) ;
440455 } , 1e3 ) ;
441456 } ;
442457
443- $ ( " .js-permalink-trigger" ) . removeAttr ( " hidden" ) ;
458+ $ ( ' .js-permalink-trigger' ) . removeAttr ( ' hidden' ) ;
444459
445- $ ( " .js-permalink-copy" ) . on ( " click" , async ( ev ) => {
460+ $ ( ' .js-permalink-copy' ) . on ( ' click' , async ( ev ) => {
446461 ev . preventDefault ( ) ;
447462
448463 const $tgt = $ ( ev . target ) ;
449464
450- const $button = $tgt . hasClass ( "js-permalink-copy" )
451- ? $tgt
452- : $tgt . parents ( ".js-permalink-copy" ) ;
465+ const $button = $tgt . hasClass ( 'js-permalink-copy' ) ? $tgt : $tgt . parents ( '.js-permalink-copy' ) ;
453466
454- const postId = $button . data ( " post-id" ) ;
455- const linkType = $button . data ( " link-type" ) ;
467+ const postId = $button . data ( ' post-id' ) ;
468+ const linkType = $button . data ( ' link-type' ) ;
456469
457470 if ( ! postId || ! linkType ) {
458471 return ;
@@ -468,10 +481,10 @@ $(() => {
468481
469482 try {
470483 await navigator . clipboard . writeText ( url ) ;
471- setCopyButtonState ( $button , " success" ) ;
484+ setCopyButtonState ( $button , ' success' ) ;
472485 }
473486 catch ( _e ) {
474- setCopyButtonState ( $button , " error" ) ;
487+ setCopyButtonState ( $button , ' error' ) ;
475488 }
476489 } ) ;
477490
0 commit comments