66 q-space
77 q-btn.q-mr-sm (
88 v-if ='editorStore.validationChecksDirty'
9- icon = 'mdi-close '
10- padding ='none'
9+ label = 'Clear '
10+ padding ='none xs '
1111 size ='sm'
1212 no-caps
13- flat
13+ outline
1414 color ='light-blue-3'
1515 @click ='clearErrors'
1616 )
@@ -110,10 +110,111 @@ q-list
110110 q-item-section( side )
111111 q-icon( name ='mdi-playlist-remove' size ='xs' color ='purple-2' )
112112 q-item-section Ignore "{{ dtl.value }}" for this document
113+
114+ q-separator.q-mt-md ( inset )
115+ .q-px-md.q-pt-md.q-pb-sm
116+ .flex.items-center
117+ .text-caption.text-light-blue-3
118+ strong ID Nits
119+ q-list
120+ q-item(
121+ clickable
122+ @click ='idnitsCheck'
123+ )
124+ q-item-section( side )
125+ q-icon( name ='mdi-cube-scan' size ='xs' color ='amber' )
126+ q-item-section
127+ q-item-label Run idnits
128+ q-item-label.text-amber ( caption ) Check for nits in this document
129+ q-item-section( side )
130+ q-circular-progress( v-show ='state.idnitsLoading' indeterminate size ='xs' color ='amber' )
131+ q-item
132+ q-item-section
133+ q-select(
134+ outlined
135+ label ='Mode'
136+ v-model ='state.idnitsMode'
137+ :options ='idnitsModes'
138+ dense
139+ color ='light-blue-4'
140+ emit-value
141+ map-options
142+ )
143+ q-item-section( side )
144+ q-btn(
145+ flat
146+ :icon ='state.idnitsOffline ? `mdi-access-point-network-off` : `mdi-satellite-uplink`'
147+ :color ='state.idnitsOffline ? `red` : `light-blue-4`'
148+ dense
149+ size ='sm'
150+ @click ='state.idnitsOffline = !state.idnitsOffline'
151+ )
152+ q-tooltip Offline Mode
153+ q-expansion-item.bg-dark-5.q-mt-sm (
154+ v-if ='state.idnitsErrors.length > 0'
155+ group ='idnits'
156+ default-opened
157+ dense
158+ )
159+ template( #header )
160+ q-item-section
161+ .flex.items-center
162+ q-icon.q-mr-sm ( name ='mdi-close-box' color ='red' )
163+ q-item-label.text-red-3 {{ state.idnitsErrors.length > 1 ? state.idnitsErrors.length + ' errors' : ' 1 error' }}
164+ .bg-dark-5.checkdetails
165+ q-list( dense , separator )
166+ q-item(
167+ v-for ='nit of state.idnitsErrors'
168+ )
169+ q-item-section
170+ .text-caption : strong.text-red-4 {{ nit.name }}
171+ .text-caption.text-blue-grey-2 {{ nit.message }}
172+ q-item-section( side )
173+ q-btn( color ='dark-2' text-color ='white' icon ='mdi-chevron-right' padding ='sm xs' size ='sm' unelevated )
174+ q-expansion-item.bg-dark-5 (
175+ v-if ='state.idnitsWarnings.length > 0'
176+ group ='idnits'
177+ dense
178+ )
179+ template( #header )
180+ q-item-section
181+ .flex.items-center
182+ q-icon.q-mr-sm ( name ='mdi-alert' color ='warning' )
183+ q-item-label.text-orange-3 {{ state.idnitsWarnings.length > 1 ? state.idnitsWarnings.length + ' warnings' : ' 1 warning' }}
184+ .bg-dark-5.checkdetails
185+ q-list( dense , separator )
186+ q-item(
187+ v-for ='nit of state.idnitsWarnings'
188+ )
189+ q-item-section
190+ .text-caption : strong.text-orange-4 {{ nit.name }}
191+ .text-caption.text-blue-grey-2 {{ nit.message }}
192+ q-item-section( side )
193+ q-btn( color ='dark-2' text-color ='white' icon ='mdi-chevron-right' padding ='sm xs' size ='sm' unelevated )
194+ q-expansion-item.bg-dark-5 (
195+ v-if ='state.idnitsComments.length > 0'
196+ group ='idnits'
197+ dense
198+ )
199+ template( #header )
200+ q-item-section
201+ .flex.items-center
202+ q-icon.q-mr-sm ( name ='mdi-information' color ='cyan' )
203+ q-item-label.text-light-blue-3 {{ state.idnitsComments.length > 1 ? state.idnitsComments.length + ' comments' : ' 1 comment' }}
204+ .bg-dark-5.checkdetails
205+ q-list( dense , separator )
206+ q-item(
207+ v-for ='nit of state.idnitsComments'
208+ )
209+ q-item-section
210+ .text-caption : strong.text-light-blue-4 {{ nit.name }}
211+ .text-caption.text-blue-grey-2 {{ nit.message }}
212+ q-item-section( side )
213+ q-btn( color ='dark-2' text-color ='white' icon ='mdi-chevron-right' padding ='sm xs' size ='sm' unelevated )
113214</template >
114215
115216<script setup>
116- import { onBeforeUnmount , onMounted } from ' vue'
217+ import { onBeforeUnmount , onMounted , reactive } from ' vue'
117218import { useQuasar } from ' quasar'
118219import { checkArticles } from ' src/tools/articles'
119220import { checkRepeatedWords } from ' src/tools/repeated-words'
@@ -122,9 +223,11 @@ import { checkInclusiveLanguage } from 'src/tools/inclusive-language'
122223import { checkNonAscii } from ' src/tools/non-ascii'
123224import { checkCommonPlaceholders } from ' src/tools/placeholders'
124225import { checkTypos } from ' src/tools/typos'
226+ import { checkIdnits } from ' src/tools/idnits'
125227import { useDocsStore } from ' src/stores/docs'
126228import { useEditorStore } from ' src/stores/editor'
127229import { modelStore } from ' src/stores/models'
230+ import { MODES } from ' @ietf-tools/idnits'
128231
129232const $q = useQuasar ()
130233
@@ -133,6 +236,16 @@ const $q = useQuasar()
133236const docsStore = useDocsStore ()
134237const editorStore = useEditorStore ()
135238
239+ const state = reactive ({
240+ idnitsLoading: false ,
241+ idnitsMode: MODES .NORMAL ,
242+ idnitsOffline: false ,
243+ idnitsTotal: 0 ,
244+ idnitsErrors: [],
245+ idnitsWarnings: [],
246+ idnitsComments: []
247+ })
248+
136249const valChecks = [
137250 {
138251 key: ' articles' ,
@@ -185,6 +298,12 @@ const valChecks = [
185298 }
186299]
187300
301+ const idnitsModes = [
302+ { label: ' Normal' , value: MODES .NORMAL },
303+ { label: ' Forgive Checklist' , value: MODES .FORGIVE_CHECKLIST },
304+ { label: ' Submission' , value: MODES .SUBMISSION }
305+ ]
306+
188307// IGNORES METHODS
189308
190309function resetIgnores (key ) {
@@ -364,6 +483,65 @@ function typosCheck (silent) {
364483 }
365484}
366485
486+ async function idnitsCheck () {
487+ if (state .idnitsLoading ) { return }
488+ state .idnitsLoading = true
489+ idnitsReset ()
490+ try {
491+ const results = await checkIdnits (modelStore[docsStore .activeDocument .id ].getValue (), docsStore .activeDocument .fileName , state .idnitsMode , state .idnitsOffline )
492+ state .idnitsTotal = results .length
493+ for (const result of results) {
494+ switch (result .constructor .name ) {
495+ case ' ValidationError' : {
496+ state .idnitsErrors .push (result)
497+ break
498+ }
499+ case ' ValidationWarning' : {
500+ state .idnitsWarnings .push (result)
501+ break
502+ }
503+ case ' ValidationComment' : {
504+ state .idnitsComments .push (result)
505+ break
506+ }
507+ default : {
508+ console .warn (' idnits - Invalid result type: ' , result)
509+ }
510+ }
511+ }
512+ if (state .idnitsTotal > 0 ) {
513+ $q .notify ({
514+ message: state .idnitsTotal > 1 ? ` ${ state .idnitsTotal } nits found.` : ' 1 nit found.' ,
515+ caption: ' Review the sidebar for nits details.' ,
516+ color: ' orange-8' ,
517+ icon: ' mdi-cube-scan'
518+ })
519+ } else {
520+ $q .notify ({
521+ message: ' Looks good!' ,
522+ caption: ' No nits found.' ,
523+ color: ' positive' ,
524+ icon: ' mdi-cube-scan'
525+ })
526+ }
527+ } catch (err) {
528+ console .warn (err)
529+ $q .notify ({
530+ message: ' Unexpected error' ,
531+ caption: err .message ,
532+ color: ' negative' ,
533+ icon: ' mdi-close-octagon'
534+ })
535+ }
536+ state .idnitsLoading = false
537+ }
538+ function idnitsReset () {
539+ state .idnitsTotal = 0
540+ state .idnitsErrors = []
541+ state .idnitsWarnings = []
542+ state .idnitsComments = []
543+ }
544+
367545function runAllChecks () {
368546 editorStore .clearErrors ()
369547 articlesCheck (true )
0 commit comments