@@ -9,8 +9,9 @@ function loadCodeMirror () {
99 import ( /* webpackChunkName: "email-editor" */ '@codemirror/commands' ) ,
1010 import ( /* webpackChunkName: "email-editor" */ '@codemirror/language' ) ,
1111 import ( /* webpackChunkName: "email-editor" */ '@codemirror/lang-html' ) ,
12+ import ( /* webpackChunkName: "email-editor" */ '@codemirror/lint' ) ,
1213 import ( /* webpackChunkName: "email-editor" */ '@specious/htmlflow' )
13- ] ) . then ( ( [ view , commands , language , html , htmlflow ] ) => {
14+ ] ) . then ( ( [ view , commands , language , html , lint , htmlflow ] ) => {
1415 return {
1516 minimalSetup : [
1617 commands . history ( ) ,
@@ -19,6 +20,8 @@ function loadCodeMirror () {
1920 ] ,
2021 EditorView : view . EditorView ,
2122 html : html . html ,
23+ htmlLanguage : html . htmlLanguage ,
24+ linter : lint . linter ,
2225 htmlflow : htmlflow . default || htmlflow
2326 }
2427 } )
@@ -46,6 +49,70 @@ export default targetable(class extends HTMLElement {
4649
4750 this . previewViewTab . addEventListener ( 'click' , this . showPreviewView )
4851 this . codeViewTab . addEventListener ( 'click' , this . showCodeView )
52+
53+ this . form = this . closest ( 'form' )
54+ this . form ?. addEventListener ( 'submit' , this . validateOnSubmit )
55+ }
56+
57+ disconnectedCallback ( ) {
58+ this . form ?. removeEventListener ( 'submit' , this . validateOnSubmit )
59+ }
60+
61+ validateOnSubmit = ( e ) => {
62+ if ( ! this . htmlLanguage ) return
63+
64+ const bodyType = this . form . querySelector ( 'input[name$="[body_type]"]:checked' ) ?. value
65+
66+ if ( bodyType && bodyType !== 'html' ) return
67+
68+ const diagnostics = this . buildDiagnostics ( this . input . value )
69+
70+ if ( diagnostics . length === 0 ) return
71+
72+ e . preventDefault ( )
73+
74+ this . showCodeView ( )
75+
76+ const pos = Math . min ( diagnostics [ 0 ] . from , this . editorView . state . doc . length )
77+
78+ this . editorView . dispatch ( { selection : { anchor : pos } , scrollIntoView : true } )
79+ this . editorView . focus ( )
80+
81+ alert ( diagnostics [ 0 ] . message )
82+ }
83+
84+ buildDiagnostics ( value ) {
85+ const diagnostics = [ ]
86+
87+ if ( ! value . trim ( ) ) return diagnostics
88+
89+ if ( ! / ^ \s * ( < ! d o c t y p e [ ^ > ] * > \s * ) ? < h t m l / i. test ( value ) ) {
90+ diagnostics . push ( {
91+ from : 0 ,
92+ to : Math . min ( 5 , value . length ) ,
93+ severity : 'error' ,
94+ message : 'The email template must start with the <html> tag'
95+ } )
96+ }
97+
98+ const seen = new Set ( )
99+
100+ this . htmlLanguage . parser . parse ( value ) . iterate ( {
101+ enter : ( node ) => {
102+ if ( ! node . type . isError || seen . has ( node . from ) || seen . size >= 20 ) return
103+
104+ seen . add ( node . from )
105+
106+ diagnostics . push ( {
107+ from : node . from ,
108+ to : Math . min ( node . to + 1 , value . length ) ,
109+ severity : 'error' ,
110+ message : 'The email template contains invalid HTML'
111+ } )
112+ }
113+ } )
114+
115+ return diagnostics
49116 }
50117
51118 showCodeView = ( ) => {
@@ -76,7 +143,9 @@ export default targetable(class extends HTMLElement {
76143 this . input = this . querySelector ( 'input[type="hidden"]' )
77144 this . input . style . display = 'none'
78145
79- const { EditorView, minimalSetup, html, htmlflow } = await loadCodeMirror ( )
146+ const { EditorView, minimalSetup, html, htmlLanguage, linter, htmlflow } = await loadCodeMirror ( )
147+
148+ this . htmlLanguage = htmlLanguage
80149
81150 this . editorView = new EditorView ( {
82151 doc : this . input . value ,
@@ -85,8 +154,11 @@ export default targetable(class extends HTMLElement {
85154 html ( ) ,
86155 minimalSetup ,
87156 EditorView . lineWrapping ,
157+ linter ( ( view ) => this . buildDiagnostics ( view . state . doc . toString ( ) ) , { delay : 600 } ) ,
88158 EditorView . updateListener . of ( update => {
89- if ( update . docChanged ) this . input . value = update . state . doc . toString ( )
159+ if ( update . docChanged ) {
160+ this . input . value = update . state . doc . toString ( )
161+ }
90162 } ) ,
91163 EditorView . theme ( {
92164 '&' : {
0 commit comments