Skip to content
This repository was archived by the owner on Dec 8, 2025. It is now read-only.

Commit ee497ab

Browse files
committed
feat: initial idnits integration (wip)
1 parent c830259 commit ee497ab

File tree

2 files changed

+191
-4
lines changed

2 files changed

+191
-4
lines changed

src/components/DrawerChecks.vue

Lines changed: 182 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
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'
117218
import { useQuasar } from 'quasar'
118219
import { checkArticles } from 'src/tools/articles'
119220
import { checkRepeatedWords } from 'src/tools/repeated-words'
@@ -122,9 +223,11 @@ import { checkInclusiveLanguage } from 'src/tools/inclusive-language'
122223
import { checkNonAscii } from 'src/tools/non-ascii'
123224
import { checkCommonPlaceholders } from 'src/tools/placeholders'
124225
import { checkTypos } from 'src/tools/typos'
226+
import { checkIdnits } from 'src/tools/idnits'
125227
import { useDocsStore } from 'src/stores/docs'
126228
import { useEditorStore } from 'src/stores/editor'
127229
import { modelStore } from 'src/stores/models'
230+
import { MODES } from '@ietf-tools/idnits'
128231
129232
const $q = useQuasar()
130233
@@ -133,6 +236,16 @@ const $q = useQuasar()
133236
const docsStore = useDocsStore()
134237
const 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+
136249
const 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
190309
function 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+
367545
function runAllChecks () {
368546
editorStore.clearErrors()
369547
articlesCheck(true)

src/tools/idnits.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { checkNits, MODES } from '@ietf-tools/idnits'
2+
3+
export async function checkIdnits (text, filename, mode = MODES.NORMAL, offline = false) {
4+
const enc = new TextEncoder()
5+
return checkNits(new Uint8Array(enc.encode(text)), filename, {
6+
mode,
7+
offline
8+
})
9+
}

0 commit comments

Comments
 (0)