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

Commit 9492048

Browse files
committed
feat: validation results output to clipboard + save to file + fix hyphenation check
1 parent d2e09d6 commit 9492048

File tree

7 files changed

+172
-20
lines changed

7 files changed

+172
-20
lines changed

src-electron/electron-preload.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ contextBridge.exposeInMainWorld('ipcBridge', {
4242
gitDiscardChanges: (files) => ipcRenderer.invoke('gitDiscardChanges', { files }),
4343
gitCommit: (message) => ipcRenderer.invoke('gitCommit', { message }),
4444
lspSendRequest: (method, params) => ipcRenderer.invoke('lspSendRequest', { method, params }),
45+
saveValidationResults: (output) => ipcRenderer.invoke('saveValidationResults', { output }),
4546
persistSession: (data) => ipcRenderer.invoke('persistSession', data),
4647
restoreSession: () => ipcRenderer.invoke('restoreSession')
4748
})

src-electron/handlers.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,30 @@ export function registerCallbacks (mainWindow, mainMenu, auth, git, lsp, tlm) {
353353
lsp.sendNotification(opts.method, opts.params)
354354
})
355355
// ----------------------------------------------------------
356+
// VALIDATION CHECKS
357+
// ----------------------------------------------------------
358+
ipcMain.handle('saveValidationResults', async (ev, opts) => {
359+
const saveOpts = await dialog.showSaveDialog(mainWindow, {
360+
title: 'Save Validation Results As...',
361+
defaultPath: path.join(app.getPath('desktop'), 'results.txt'),
362+
filters: [{
363+
name: 'Plain Text',
364+
extensions: ['txt']
365+
}],
366+
properties: ['showOverwriteConfirmation', 'createDirectory']
367+
})
368+
if (!saveOpts.canceled) {
369+
try {
370+
await fs.writeFile(saveOpts.filePath, opts.output)
371+
return true
372+
} catch (err) {
373+
console.error(err)
374+
throw err
375+
}
376+
}
377+
return false
378+
})
379+
// ----------------------------------------------------------
356380
// MISC
357381
// ----------------------------------------------------------
358382
ipcMain.on('setMenuItemCheckedState', (ev, opts) => {

src/components/DrawerChecks.vue

Lines changed: 65 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,52 @@ q-list
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')
4444
q-expansion-item.bg-dark-5(
45-
v-if='editorStore.validationChecksDetails[chk.key].length > 0'
45+
v-if='editorStore.validationChecksDetails[chk.key].count > 0'
4646
group='checks'
47+
dense
4748
@show='setCurrentCheck(chk.key)'
4849
)
4950
template(#header)
5051
q-item-section.q-pl-sm
5152
.flex.items-center
52-
q-item-label.text-purple-2 └─ {{ editorStore.validationChecksDetails[chk.key].length }} issues
53+
q-item-label.text-purple-2 └─ {{ editorStore.validationChecksDetails[chk.key].count }} issues
5354
.bg-dark-5.checkdetails
5455
q-list(dense, separator)
56+
q-item
57+
q-item-section
58+
.flex
59+
q-btn.q-mr-sm(
60+
label='Reset Ignores'
61+
padding='none xs'
62+
size='sm'
63+
no-caps
64+
outline
65+
color='purple-3'
66+
disabled
67+
)
68+
q-space
69+
q-btn.q-mr-sm(
70+
label='Save to File'
71+
padding='none xs'
72+
size='sm'
73+
no-caps
74+
outline
75+
color='purple-3'
76+
@click='saveResultsToFile(chk.key)'
77+
:disabled='!editorStore.validationChecksDetails[chk.key].hasTextOutput'
78+
)
79+
q-btn(
80+
label='Copy to Clipboard'
81+
padding='none xs'
82+
size='sm'
83+
no-caps
84+
outline
85+
color='purple-3'
86+
@click='copyResultsToClipboard(chk.key)'
87+
:disabled='!editorStore.validationChecksDetails[chk.key].hasTextOutput'
88+
)
5589
q-item(
56-
v-for='dtl of editorStore.validationChecksDetails[chk.key]'
90+
v-for='dtl of editorStore.validationChecksDetails[chk.key].details'
5791
:key='dtl.key'
5892
clickable
5993
@click='goToPosition(dtl.range)'
@@ -139,7 +173,7 @@ function articlesCheck (silent) {
139173
}
140174
} else {
141175
editorStore.setValidationCheckState('articles', -2)
142-
editorStore.setValidationCheckDetails('articles', results.details)
176+
editorStore.setValidationCheckDetails('articles', results)
143177
}
144178
}
145179
@@ -158,7 +192,7 @@ function hyphenationCheck (silent = false) {
158192
}
159193
} else {
160194
editorStore.setValidationCheckState('hyphenation', -2)
161-
editorStore.setValidationCheckDetails('hyphenation', results.details)
195+
editorStore.setValidationCheckDetails('hyphenation', results)
162196
}
163197
}
164198
@@ -177,7 +211,7 @@ function inclusiveLangCheck (silent = false) {
177211
}
178212
} else {
179213
editorStore.setValidationCheckState('inclusiveLanguage', -2)
180-
editorStore.setValidationCheckDetails('inclusiveLanguage', results.details)
214+
editorStore.setValidationCheckDetails('inclusiveLanguage', results)
181215
}
182216
}
183217
@@ -213,7 +247,7 @@ function placeholdersCheck (silent = false) {
213247
}
214248
} else {
215249
editorStore.setValidationCheckState('placeholders', -2)
216-
editorStore.setValidationCheckDetails('placeholders', results.details)
250+
editorStore.setValidationCheckDetails('placeholders', results)
217251
}
218252
}
219253
@@ -275,6 +309,30 @@ function setCurrentCheck (key) {
275309
editorStore.validationChecksCurrent = key
276310
}
277311
312+
// Validation Results Output
313+
314+
async function saveResultsToFile (key) {
315+
if (await window.ipcBridge.saveValidationResults(editorStore.validationChecksDetails[key].getTextOutput())) {
316+
$q.notify({
317+
message: 'Results saved!',
318+
caption: 'Results have been saved to file successfully.',
319+
color: 'positive',
320+
icon: 'mdi-content-save-check-outline'
321+
})
322+
}
323+
}
324+
325+
function copyResultsToClipboard (key) {
326+
window.ipcBridge.emit('writeToClipboard', {
327+
text: editorStore.validationChecksDetails[key].getTextOutput()
328+
})
329+
$q.notify({
330+
message: 'Results copied!',
331+
caption: 'Results have been copied to the clipboard successfully.',
332+
color: 'positive',
333+
icon: 'mdi-clipboard'
334+
})
335+
}
278336
// MOUNTED
279337
280338
onMounted(() => {

src/tools/articles.js

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ export function checkArticles (text) {
1111

1212
const decorations = []
1313
const details = []
14+
const termCount = {}
15+
16+
function addToTermCount (term) {
17+
const termSanitized = term.trim()
18+
if (termCount[termSanitized]) {
19+
termCount[termSanitized]++
20+
} else {
21+
termCount[termSanitized] = 1
22+
}
23+
}
24+
1425
for (const [lineIdx, line] of textLines.entries()) {
1526
for (const match of line.matchAll(partARgx)) {
1627
decorations.push({
@@ -41,6 +52,7 @@ export function checkArticles (text) {
4152
endColumn: match.index + match[0].length
4253
}
4354
})
55+
addToTermCount(match[0])
4456
}
4557
for (const match of line.matchAll(partARRgx)) {
4658
decorations.push({
@@ -71,6 +83,7 @@ export function checkArticles (text) {
7183
endColumn: match.index + match[0].length
7284
}
7385
})
86+
addToTermCount(match[0])
7487
}
7588
for (const match of line.matchAll(partBRgx)) {
7689
decorations.push({
@@ -101,6 +114,7 @@ export function checkArticles (text) {
101114
endColumn: match.index + match[0].length
102115
}
103116
})
117+
addToTermCount(match[0])
104118
}
105119
for (const match of line.matchAll(partCRgx)) {
106120
decorations.push({
@@ -131,6 +145,7 @@ export function checkArticles (text) {
131145
endColumn: match.index + match[0].length
132146
}
133147
})
148+
addToTermCount(match[0])
134149
}
135150
for (const match of line.matchAll(partCLFRgx)) {
136151
decorations.push({
@@ -161,13 +176,21 @@ export function checkArticles (text) {
161176
endColumn: match.index + match[0].length
162177
}
163178
})
179+
addToTermCount(match[0])
164180
}
165181
}
166182

167183
decorationsStore.get('articles').set(decorations)
168184

169185
return {
170186
count: decorations.length,
171-
details: sortBy(details, d => d.range.startLineNumber)
187+
details: sortBy(details, d => d.range.startLineNumber),
188+
hasTextOutput: true,
189+
getTextOutput: () => {
190+
return `Articles
191+
-------------
192+
${Object.entries(termCount).map(([k, v]) => `${k} (${v})`).join('\n')}
193+
`
194+
}
172195
}
173196
}

src/tools/hyphenation.js

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ export function checkHyphenation (text) {
1616
const occurences = []
1717
const hyphenTerms = []
1818
const hyphenTermsOccurences = []
19+
const termCount = {}
1920
for (const [lineIdx, line] of textLines.entries()) {
2021
for (const match of line.matchAll(hyphenTermRgx)) {
2122
if (match[0].length > 3) {
22-
if (!hyphenTerms.includes(match[0])) {
23-
hyphenTerms.push(match[0])
23+
const termLower = match[0].toLowerCase()
24+
if (!hyphenTerms.includes(termLower)) {
25+
hyphenTerms.push(termLower)
2426
}
2527
hyphenTermsOccurences.push({
26-
term: match[0],
28+
term: termLower,
2729
range: {
2830
startLineNumber: lineIdx + 1,
2931
startColumn: match.index + 1,
@@ -39,16 +41,17 @@ export function checkHyphenation (text) {
3941
for (const [lineIdx, line] of textLines.entries()) {
4042
for (const term of hyphenTerms) {
4143
const altTerm = term.replaceAll('-', '')
42-
const altTermRgx = new RegExp(`(?:^|[>" ])${altTerm}(?:[. "<]|$)`, 'gi')
44+
const altTermRgx = new RegExp(`(?:^|[>" ])(${altTerm})(?:[. "<]|$)`, 'gi')
4345
for (const match of line.matchAll(altTermRgx)) {
46+
const matchLower = match[1].toLowerCase()
4447
let occIdx = occurences.indexOf(term)
4548
if (occIdx < 0) {
4649
occIdx = occurences.push(term) - 1
4750
for (const termOcc of hyphenTermsOccurences.filter(t => t.term === term)) {
4851
decorations.push({
4952
options: {
5053
hoverMessage: {
51-
value: `[${occIdx}] Inconsistent Hyphenation (Alternate of ${altTerm})`
54+
value: `[${occIdx + 1}] Inconsistent Hyphenation (Alternate of ${altTerm})`
5255
},
5356
className: 'dec-warning',
5457
minimap: {
@@ -61,15 +64,20 @@ export function checkHyphenation (text) {
6164
details.push({
6265
key: crypto.randomUUID(),
6366
group: occIdx + 1,
64-
message: `${term} is alternate of ${altTerm}`,
67+
message: `${term} has alternate term(s)`,
6568
range: termOcc.range
6669
})
6770
}
71+
if (termCount[term]) {
72+
termCount[term]++
73+
} else {
74+
termCount[term] = 1
75+
}
6876
}
6977
decorations.push({
7078
options: {
7179
hoverMessage: {
72-
value: `[${occIdx}] Inconsistent Hyphenation (Alternate of ${term})`
80+
value: `[${occIdx + 1}] Inconsistent Hyphenation (Alternate of ${term})`
7381
},
7482
className: 'dec-warning',
7583
minimap: {
@@ -87,14 +95,19 @@ export function checkHyphenation (text) {
8795
details.push({
8896
key: crypto.randomUUID(),
8997
group: occIdx + 1,
90-
message: `${term} has alternate term(s)`,
98+
message: `${matchLower} is alternate of ${term}`,
9199
range: {
92100
startLineNumber: lineIdx + 1,
93101
startColumn: match.index + 2,
94102
endLineNumber: lineIdx + 1,
95103
endColumn: match.index + match[0].length
96104
}
97105
})
106+
if (termCount[matchLower]) {
107+
termCount[matchLower]++
108+
} else {
109+
termCount[matchLower] = 1
110+
}
98111
}
99112
}
100113
}
@@ -103,7 +116,14 @@ export function checkHyphenation (text) {
103116
decorationsStore.get('hyphenation').set(decorations)
104117

105118
return {
106-
count: occurences.length,
107-
details: sortBy(details, d => d.range.startLineNumber)
119+
count: details.length,
120+
details: sortBy(details, d => d.range.startLineNumber),
121+
hasTextOutput: true,
122+
getTextOutput: () => {
123+
return `Hyphenation
124+
-------------
125+
${Object.entries(termCount).map(([k, v]) => `${k} (${v})`).join('\n')}
126+
`
127+
}
108128
}
109129
}

src/tools/inclusive-language.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export function checkInclusiveLanguage (text) {
4343
const decorations = []
4444
const occurences = []
4545
const details = []
46+
const termCount = {}
4647
for (const [lineIdx, line] of textLines.entries()) {
4748
for (const match of line.matchAll(matchRgx)) {
4849
const term = match[1].toLowerCase()
@@ -80,13 +81,25 @@ export function checkInclusiveLanguage (text) {
8081
endColumn: match.index + 1 + match[0].length
8182
}
8283
})
84+
if (termCount[term]) {
85+
termCount[term]++
86+
} else {
87+
termCount[term] = 1
88+
}
8389
}
8490
}
8591

8692
decorationsStore.get('inclusiveLanguage').set(decorations)
8793

8894
return {
8995
count: decorations.length,
90-
details: sortBy(details, d => d.range.startLineNumber)
96+
details: sortBy(details, d => d.range.startLineNumber),
97+
hasTextOutput: true,
98+
getTextOutput: () => {
99+
return `Inclusive Language
100+
-------------
101+
${Object.entries(termCount).map(([k, v]) => `${k} (${v})`).join('\n')}
102+
`
103+
}
91104
}
92105
}

0 commit comments

Comments
 (0)