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

Commit 5fbb277

Browse files
committed
feat: ignore values for validation checks + persist to companion file on save
1 parent 0c03890 commit 5fbb277

File tree

10 files changed

+150
-32
lines changed

10 files changed

+150
-32
lines changed

src-electron/handlers.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import fs from 'node:fs/promises'
33
import path from 'node:path'
44
import { pick, orderBy } from 'lodash-es'
55
import { encode, decode } from '@msgpack/msgpack'
6+
import { getExtraFilePath } from './helpers'
67

78
/**
89
* Show the Open Dialog
@@ -11,10 +12,10 @@ import { encode, decode } from '@msgpack/msgpack'
1112
*/
1213
export async function openDocument (mainWindow) {
1314
const files = await dialog.showOpenDialog(mainWindow, {
14-
title: 'Open RFC / Internet Draft...',
15+
title: 'Open Internet Draft / RFC...',
1516
filters: [
1617
{
17-
name: 'RFC/Internet Draft',
18+
name: 'Internet Draft/RFC',
1819
extensions: ['md', 'txt', 'xml']
1920
}
2021
],
@@ -35,12 +36,19 @@ export async function openDocument (mainWindow) {
3536
*/
3637
export async function loadDocument (mainWindow, filePath) {
3738
const fileContents = await fs.readFile(filePath, 'utf8')
39+
let fileExtra = {}
40+
try {
41+
fileExtra = JSON.parse(await fs.readFile(getExtraFilePath(filePath), 'utf8'))
42+
} catch (err) {
43+
console.info(`No _draftforge.json extra file found or invalid format for ${filePath}.`)
44+
}
3845
const pathInfo = path.parse(filePath)
3946
mainWindow.webContents.send('openDocument', {
4047
type: pathInfo.ext.slice(1),
4148
path: filePath,
4249
fileName: pathInfo.base,
43-
data: fileContents
50+
data: fileContents,
51+
extra: fileExtra
4452
})
4553
app.addRecentDocument(filePath)
4654
}
@@ -52,9 +60,12 @@ export async function loadDocument (mainWindow, filePath) {
5260
* @param {string} filePath Path where to save the document
5361
* @param {string} contents Contents of the file
5462
*/
55-
export async function saveDocument (mainWindow, filePath, contents) {
63+
export async function saveDocument (mainWindow, filePath, contents, extra) {
5664
try {
5765
await fs.writeFile(filePath, contents, 'utf8')
66+
if (extra && Object.keys(extra).length > 0) {
67+
await fs.writeFile(getExtraFilePath(filePath), JSON.stringify(extra, null, 2), 'utf8')
68+
}
5869
mainWindow.webContents.send('notify', {
5970
message: 'Saved successfully.',
6071
color: 'positive',
@@ -211,7 +222,7 @@ export function registerCallbacks (mainWindow, mainMenu, auth, git, lsp, tlm, te
211222
openDocument(mainWindow)
212223
})
213224
ipcMain.on('save', (ev, opts) => {
214-
saveDocument(mainWindow, opts.path, opts.data)
225+
saveDocument(mainWindow, opts.path, opts.data, opts.extra)
215226
})
216227
ipcMain.handle('promptSave', async (ev, opts) => {
217228
return git.performFetch({ dir: opts.dir, remote: opts.remote })

src-electron/helpers.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,12 @@ export function mergeWithHeaders (headers = {}, key, value) {
1313
headers[key] = value
1414
}
1515
}
16+
17+
/**
18+
* Get the file path for the extra _draftforge.json file based on a document file path
19+
*
20+
* @param {string} filePath Document file path
21+
*/
22+
export function getExtraFilePath (filePath) {
23+
return filePath.substring(0, filePath.lastIndexOf('.')) + '_draftforge.json'
24+
}

src/components/DrawerChecks.vue

Lines changed: 69 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ q-list
4141
q-icon(v-else-if='editorStore.validationChecks[chk.key] === 2' name='mdi-information' size='xs' color='light-blue-5')
4242
q-icon(v-else-if='editorStore.validationChecks[chk.key] === -1' name='mdi-close-circle' size='xs' color='red-5')
4343
q-icon(v-else-if='editorStore.validationChecks[chk.key] === -2' name='mdi-alert-circle' size='xs' color='orange-5')
44+
q-menu(touch-position context-menu auto-close)
45+
q-list.bg-dark-1(separator)
46+
q-item(clickable, @click='resetIgnores(chk.key)')
47+
q-item-section(side)
48+
q-icon(name='mdi-cancel' size='xs' color='amber')
49+
q-item-section Reset all ignores for this check
4450
q-expansion-item.bg-dark-5(
4551
v-if='editorStore.validationChecksDetails[chk.key].count > 0'
4652
group='checks'
@@ -63,7 +69,7 @@ q-list
6369
no-caps
6470
outline
6571
color='purple-3'
66-
disabled
72+
@click='resetIgnores(chk.key)'
6773
)
6874
q-space
6975
q-btn.q-mr-sm(
@@ -95,8 +101,15 @@ q-list
95101
q-item-section(v-if='dtl.group', side)
96102
q-badge(color='purple' text-color='white' :label='dtl.group')
97103
q-item-section.text-caption {{ dtl.message }}
98-
q-item-section(v-if='dtl.range', side)
99-
q-badge(color='dark-3' text-color='white' :label='dtl.range.startLineNumber + ":" + dtl.range.startColumn')
104+
q-item-section(side)
105+
q-badge(v-if='dtl.range',color='dark-3' text-color='white') {{ dtl.range.startLineNumber + ":" + dtl.range.startColumn }} #[q-icon.q-ml-xs(name='mdi-dots-horizontal')]
106+
q-btn(v-else color='dark-3' text-color='white' icon='mdi-dots-horizontal' padding='none xs' size='xs' unelevated)
107+
q-menu(self='top left', anchor='top right' auto-close)
108+
q-list.bg-dark-1(separator)
109+
q-item(clickable, @click='ignoreCheck(chk.key, dtl.value)')
110+
q-item-section(side)
111+
q-icon(name='mdi-playlist-remove' size='xs' color='purple-2')
112+
q-item-section Ignore "{{ dtl.value }}" for this document
100113
</template>
101114

102115
<script setup>
@@ -164,10 +177,56 @@ const valChecks = [
164177
}
165178
]
166179
167-
// METHODS
180+
// IGNORES METHODS
181+
182+
function resetIgnores (key) {
183+
if (docsStore.activeDocument.extra?.checks?.[key]?.ignores) {
184+
docsStore.activeDocument.extra.checks[key].ignores = []
185+
}
186+
$q.notify({
187+
message: 'Ignores cleared!',
188+
caption: 'All ignores for this check have been reset.',
189+
color: 'positive',
190+
icon: 'mdi-playlist-remove'
191+
})
192+
}
193+
194+
function ignoreCheck (key, value) {
195+
if (!docsStore.activeDocument.extra.checks) {
196+
docsStore.activeDocument.extra.checks = {}
197+
}
198+
if (!docsStore.activeDocument.extra.checks[key]) {
199+
docsStore.activeDocument.extra.checks[key] = {}
200+
}
201+
if (!docsStore.activeDocument.extra.checks[key].ignores) {
202+
docsStore.activeDocument.extra.checks[key].ignores = [value]
203+
} else if (docsStore.activeDocument.extra.checks[key].ignores.includes(value)) {
204+
$q.notify({
205+
message: 'Ignore already added.',
206+
caption: 'Run the validation check again to use it.',
207+
color: 'orange-8',
208+
icon: 'mdi-alert'
209+
})
210+
return
211+
} else {
212+
docsStore.activeDocument.extra.checks[key].ignores.push(value)
213+
}
214+
$q.notify({
215+
message: 'Ignore added!',
216+
caption: 'Run the validation check again to use it.',
217+
color: 'positive',
218+
icon: 'mdi-playlist-plus'
219+
})
220+
}
221+
222+
function getIgnores (key) {
223+
return docsStore.activeDocument.extra?.checks?.[key]?.ignores ?? []
224+
}
225+
226+
// VALIDATION METHODS
168227
169228
function articlesCheck (silent) {
170-
const results = checkArticles(modelStore[docsStore.activeDocument.id].getValue())
229+
const results = checkArticles(modelStore[docsStore.activeDocument.id].getValue(), getIgnores('articles'))
171230
if (results.count < 1) {
172231
editorStore.setValidationCheckState('articles', 1)
173232
editorStore.setValidationCheckDetails('articles', [])
@@ -186,7 +245,7 @@ function articlesCheck (silent) {
186245
}
187246
188247
function hyphenationCheck (silent = false) {
189-
const results = checkHyphenation(modelStore[docsStore.activeDocument.id].getValue())
248+
const results = checkHyphenation(modelStore[docsStore.activeDocument.id].getValue(), getIgnores('hyphenation'))
190249
if (results.count < 1) {
191250
editorStore.setValidationCheckState('hyphenation', 1)
192251
editorStore.setValidationCheckDetails('hyphenation', [])
@@ -205,7 +264,7 @@ function hyphenationCheck (silent = false) {
205264
}
206265
207266
function inclusiveLangCheck (silent = false) {
208-
const results = checkInclusiveLanguage(modelStore[docsStore.activeDocument.id].getValue())
267+
const results = checkInclusiveLanguage(modelStore[docsStore.activeDocument.id].getValue(), getIgnores('inclusiveLanguage'))
209268
if (results.count < 1) {
210269
editorStore.setValidationCheckState('inclusiveLanguage', 1)
211270
editorStore.setValidationCheckDetails('inclusiveLanguage', [])
@@ -224,7 +283,7 @@ function inclusiveLangCheck (silent = false) {
224283
}
225284
226285
function nonAsciiCheck (silent = false) {
227-
const infos = checkNonAscii(modelStore[docsStore.activeDocument.id].getValue())
286+
const infos = checkNonAscii(modelStore[docsStore.activeDocument.id].getValue(), getIgnores('nonAscii'))
228287
if (infos < 1) {
229288
editorStore.setValidationCheckState('nonAscii', 1)
230289
if (!silent) {
@@ -241,7 +300,7 @@ function nonAsciiCheck (silent = false) {
241300
}
242301
243302
function placeholdersCheck (silent = false) {
244-
const results = checkCommonPlaceholders(modelStore[docsStore.activeDocument.id].getValue())
303+
const results = checkCommonPlaceholders(modelStore[docsStore.activeDocument.id].getValue(), getIgnores('placeholders'))
245304
if (results.count < 1) {
246305
editorStore.setValidationCheckState('placeholders', 1)
247306
editorStore.setValidationCheckDetails('placeholders', [])
@@ -260,7 +319,7 @@ function placeholdersCheck (silent = false) {
260319
}
261320
262321
function repeatedWordsCheck (silent) {
263-
const results = checkRepeatedWords(modelStore[docsStore.activeDocument.id].getValue())
322+
const results = checkRepeatedWords(modelStore[docsStore.activeDocument.id].getValue(), getIgnores('repeatedWords'))
264323
if (results.count < 1) {
265324
editorStore.setValidationCheckState('repeatedWords', 1)
266325
editorStore.setValidationCheckDetails('repeatedWords', [])

src/stores/docs.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ export const useDocsStore = defineStore('docs', {
5757
path: doc.path ?? '',
5858
fileName: doc.fileName ?? 'untitled-draft.xml',
5959
data: doc.data ?? '',
60+
extra: doc.extra ?? {},
6061
isModified: false,
6162
lastModifiedAt: DateTime.utc(),
6263
lastSavedVersion: modelStore[docId].getAlternativeVersionId(),
@@ -144,7 +145,8 @@ export const useDocsStore = defineStore('docs', {
144145
} else {
145146
window.ipcBridge.emit('save', {
146147
path: this.activeDocument.path,
147-
data: modelStore[this.activeDocument.id].getValue()
148+
data: modelStore[this.activeDocument.id].getValue(),
149+
extra: cloneDeep(this.activeDocument.extra),
148150
})
149151
this.activeDocument.data = modelStore[this.activeDocument.id].getValue()
150152
this.activeDocument.isModified = false

src/tools/articles.js

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { decorationsStore } from 'src/stores/models'
22
import { sortBy } from 'lodash-es'
33

4-
export function checkArticles (text) {
4+
export function checkArticles (text, ignores = []) {
55
const partARgx = /(?<!(?:[aA]ppendix|[cC]onnection|[lL]ink|[nN]ode|Operator)) (?!(?:[aA] (?:AAA|Europe|[oO]ne|U[A-Z]|U-label|[uU]biquitous|[uU]nicast|[uU]nicode|[uU]nidir|[uU]nif|[uU]nion|[uU]nique|[uU]nit|[uU]nivers|[uU]sable|[uU]sability|[uU]sage|[uU]se|[uU]tility)|a uCDN|A and))[aA] [aeiouAEIOU]/g
66
const partARRgx = /(?!(?:[aA] (?:RADIUS|RECEIVE|RECOMMENDED|REFER|RELOAD|RST|REALM|RESERVATION|REQUEST|RESET|ROUTE|RPL)))[aA] R[A-Z]/g
77
const partBRgx = / (?!(?:[aA]n (?:hour|honest|honor|Mtrace|x-coordinate|x coordinate|A[A-Z]|E[A-Z]|F[A-Z]|H[A-Z]|I[A-Z]|L[A-Z]|L[0-9][A-Z]|M[A-Z]|N[A-Z]|O[A-Z]|R[A-Z]|R[0-9]|S[A-Z]|X[A-Z]|X\.509|xTR)))[aA]n [b-df-hj-np-tv-zB-DF-HJ-NP-TV-Z]/g
@@ -24,6 +24,9 @@ export function checkArticles (text) {
2424

2525
for (const [lineIdx, line] of textLines.entries()) {
2626
for (const match of line.matchAll(partARgx)) {
27+
if (ignores.includes(match[0])) {
28+
continue
29+
}
2730
decorations.push({
2831
options: {
2932
hoverMessage: {
@@ -50,11 +53,15 @@ export function checkArticles (text) {
5053
startColumn: match.index + 2,
5154
endLineNumber: lineIdx + 1,
5255
endColumn: match.index + match[0].length
53-
}
56+
},
57+
value: match[0]
5458
})
5559
addToTermCount(match[0])
5660
}
5761
for (const match of line.matchAll(partARRgx)) {
62+
if (ignores.includes(match[0])) {
63+
continue
64+
}
5865
decorations.push({
5966
options: {
6067
hoverMessage: {
@@ -81,11 +88,15 @@ export function checkArticles (text) {
8188
startColumn: match.index + 2,
8289
endLineNumber: lineIdx + 1,
8390
endColumn: match.index + match[0].length
84-
}
91+
},
92+
value: match[0]
8593
})
8694
addToTermCount(match[0])
8795
}
8896
for (const match of line.matchAll(partBRgx)) {
97+
if (ignores.includes(match[0])) {
98+
continue
99+
}
89100
decorations.push({
90101
options: {
91102
hoverMessage: {
@@ -112,11 +123,15 @@ export function checkArticles (text) {
112123
startColumn: match.index + 2,
113124
endLineNumber: lineIdx + 1,
114125
endColumn: match.index + match[0].length
115-
}
126+
},
127+
value: match[0]
116128
})
117129
addToTermCount(match[0])
118130
}
119131
for (const match of line.matchAll(partCRgx)) {
132+
if (ignores.includes(match[0])) {
133+
continue
134+
}
120135
decorations.push({
121136
options: {
122137
hoverMessage: {
@@ -143,11 +158,15 @@ export function checkArticles (text) {
143158
startColumn: match.index + 2,
144159
endLineNumber: lineIdx + 1,
145160
endColumn: match.index + match[0].length
146-
}
161+
},
162+
value: match[0]
147163
})
148164
addToTermCount(match[0])
149165
}
150166
for (const match of line.matchAll(partCLFRgx)) {
167+
if (ignores.includes(match[0])) {
168+
continue
169+
}
151170
decorations.push({
152171
options: {
153172
hoverMessage: {
@@ -174,7 +193,8 @@ export function checkArticles (text) {
174193
startColumn: match.index + 2,
175194
endLineNumber: lineIdx + 1,
176195
endColumn: match.index + match[0].length
177-
}
196+
},
197+
value: match[0]
178198
})
179199
addToTermCount(match[0])
180200
}

src/tools/hyphenation.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import { repeat, sortBy } from 'lodash-es'
44
const hyphenTermRgx = /[a-z]+(?:-[a-z]+)+/gi
55
const targetPropRgx = / target="([^"]+?)"/gi
66

7-
export function checkHyphenation (text) {
7+
export function checkHyphenation (text, ignores = []) {
88
const textLines = text.split('\n').map(line => {
99
return line.replaceAll(targetPropRgx, (m, val) => {
1010
return ` target="${repeat(' ', val.length)}"`
@@ -21,6 +21,9 @@ export function checkHyphenation (text) {
2121
for (const match of line.matchAll(hyphenTermRgx)) {
2222
if (match[0].length > 3) {
2323
const termLower = match[0].toLowerCase()
24+
if (ignores.includes(termLower)) {
25+
continue
26+
}
2427
if (!hyphenTerms.includes(termLower)) {
2528
hyphenTerms.push(termLower)
2629
}
@@ -65,7 +68,8 @@ export function checkHyphenation (text) {
6568
key: crypto.randomUUID(),
6669
group: occIdx + 1,
6770
message: `${term} has alternate term(s)`,
68-
range: termOcc.range
71+
range: termOcc.range,
72+
value: term
6973
})
7074
}
7175
if (termCount[term]) {
@@ -101,7 +105,8 @@ export function checkHyphenation (text) {
101105
startColumn: match.index + 2,
102106
endLineNumber: lineIdx + 1,
103107
endColumn: match.index + match[0].length
104-
}
108+
},
109+
value: matchLower
105110
})
106111
if (termCount[matchLower]) {
107112
termCount[matchLower]++

0 commit comments

Comments
 (0)