spec-up-t 1.2.9 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +2 -1
- package/assets/compiled/body.js +5 -5
- package/assets/compiled/head.css +1 -0
- package/assets/css/counter.css +104 -0
- package/assets/js/addAnchorsToTerms.js +13 -5
- package/assets/js/collapse-definitions.js +0 -3
- package/assets/js/custom-elements.js +25 -27
- package/assets/js/fix-last-dd.js +6 -3
- package/assets/js/highlight-heading-plus-sibling-nodes.js +0 -1
- package/assets/js/highlight-heading-plus-sibling-nodes.test.js +120 -0
- package/assets/js/insert-trefs.js +32 -28
- package/config/asset-map.json +1 -0
- package/index.js +33 -227
- package/package.json +4 -2
- package/sonar-project.properties +7 -0
- package/src/collect-external-references.js +22 -11
- package/src/collect-external-references.test.js +153 -2
- package/src/collectExternalReferences/fetchTermsFromIndex.js +65 -110
- package/src/collectExternalReferences/processXTrefsData.js +9 -11
- package/src/create-docx.js +332 -0
- package/src/create-pdf.js +243 -122
- package/src/fix-markdown-files.js +31 -34
- package/src/html-dom-processor.js +290 -0
- package/src/init.js +3 -0
- package/src/install-from-boilerplate/boilerplate/.github/workflows/menu.yml +51 -36
- package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +0 -1
- package/src/install-from-boilerplate/boilerplate/spec/terms-and-definitions-intro.md +1 -5
- package/src/install-from-boilerplate/config-scripts-keys.js +4 -4
- package/src/install-from-boilerplate/menu.sh +6 -6
- package/src/markdown-it-extensions.js +54 -33
- package/src/references.js +18 -6
- package/templates/template.html +2 -0
|
@@ -27,11 +27,11 @@ function insertTrefs(allXTrefs) {
|
|
|
27
27
|
* @type {Array<{element: Element, textContent: string, dt: Element, parent: Element}>}
|
|
28
28
|
*/
|
|
29
29
|
const allTerms = [];
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
30
|
+
|
|
31
|
+
document.querySelectorAll('dl.terms-and-definitions-list dt span.transcluded-xref-term').forEach((termElement) => {
|
|
32
|
+
// Get the full text content including any nested spans (for aliases) of a term (dt)
|
|
33
|
+
// In case of `[[tref:toip1, agency, ag]]`, this will return `agency`
|
|
34
|
+
const textContent = termElement.textContent.trim();
|
|
35
35
|
|
|
36
36
|
// Find the dt element once outside the loop
|
|
37
37
|
const dt = termElement.closest('dt');
|
|
@@ -68,7 +68,7 @@ function insertTrefs(allXTrefs) {
|
|
|
68
68
|
|
|
69
69
|
// Find the first matching xref to avoid duplicates
|
|
70
70
|
const xref = xtrefsData.xtrefs.find(x => x.term === textContent);
|
|
71
|
-
|
|
71
|
+
|
|
72
72
|
// Create a DocumentFragment to hold all new elements for this term
|
|
73
73
|
const fragment = document.createDocumentFragment();
|
|
74
74
|
|
|
@@ -93,16 +93,22 @@ function insertTrefs(allXTrefs) {
|
|
|
93
93
|
metaInfoEl.innerHTML = md.render(metaInfo);
|
|
94
94
|
fragment.appendChild(metaInfoEl);
|
|
95
95
|
|
|
96
|
-
// Clean up markdown content
|
|
96
|
+
// Clean up the markdown content in the term definition
|
|
97
|
+
// Part A: clean up via regex
|
|
97
98
|
let content = xref.content
|
|
98
|
-
.replace(/\[\[def:[^\]]*?\]\]/g, '') // Remove [[def: ...]] patterns regardless of trailing chars
|
|
99
99
|
.split('\n')
|
|
100
100
|
.map(line => line.replace(/^\s*~\s*/, '')) // Remove leading ~ and spaces
|
|
101
101
|
.join('\n')
|
|
102
|
-
.replace(/\[\[ref:/g, '') // Remove [[ref: ...]]
|
|
103
102
|
.replace(/\]\]/g, '');
|
|
104
103
|
|
|
105
|
-
//
|
|
104
|
+
// Clean up the markdown content in the term definition
|
|
105
|
+
// Part B: Remove all <a> elements from the content via a temporary div and DOM manipulation
|
|
106
|
+
const tempDivForLinks = document.createElement('div');
|
|
107
|
+
tempDivForLinks.innerHTML = md.render(content);
|
|
108
|
+
tempDivForLinks.querySelectorAll('a').forEach(a => a.replaceWith(...a.childNodes));
|
|
109
|
+
content = tempDivForLinks.innerHTML;
|
|
110
|
+
|
|
111
|
+
// Parse the rendered HTML to check for dd elements. xref.content is a string that contains HTML, in the form of <dd>...</dd>'s
|
|
106
112
|
const tempDiv = document.createElement('div');
|
|
107
113
|
tempDiv.innerHTML = md.render(content);
|
|
108
114
|
|
|
@@ -119,14 +125,17 @@ function insertTrefs(allXTrefs) {
|
|
|
119
125
|
fragment.appendChild(clonedDD);
|
|
120
126
|
});
|
|
121
127
|
} else {
|
|
122
|
-
|
|
128
|
+
/*
|
|
129
|
+
No dd elements found, create one to hold the conten. Explanation: this is the content in case nothing was found:
|
|
130
|
+
`"content": "This term was not found in the external repository"`
|
|
131
|
+
*/
|
|
123
132
|
const contentEl = document.createElement('dd');
|
|
124
133
|
contentEl.classList.add('transcluded-xref-term', 'transcluded-xref-term-embedded');
|
|
125
134
|
contentEl.innerHTML = tempDiv.innerHTML;
|
|
126
135
|
fragment.appendChild(contentEl);
|
|
127
136
|
}
|
|
128
137
|
} else {
|
|
129
|
-
//
|
|
138
|
+
// When the [[tref]] is not valid, for example `[[tref: transferable, transferable]]`, where `transferable` is not an external repo in specs.json
|
|
130
139
|
metaInfoEl.innerHTML = md.render(`
|
|
131
140
|
| Property | Value |
|
|
132
141
|
| -------- | ----- |
|
|
@@ -138,7 +147,7 @@ function insertTrefs(allXTrefs) {
|
|
|
138
147
|
|
|
139
148
|
// Create not found message
|
|
140
149
|
const notFoundEl = document.createElement('dd');
|
|
141
|
-
|
|
150
|
+
|
|
142
151
|
notFoundEl.innerHTML = '<p>This term was not found in the external repository.</p>';
|
|
143
152
|
fragment.appendChild(notFoundEl);
|
|
144
153
|
}
|
|
@@ -160,28 +169,20 @@ function insertTrefs(allXTrefs) {
|
|
|
160
169
|
const { dt, parent, fragment } = change;
|
|
161
170
|
parent.insertBefore(fragment, dt.nextSibling);
|
|
162
171
|
});
|
|
163
|
-
|
|
172
|
+
|
|
164
173
|
// Dispatch a custom event when all DOM modifications are complete
|
|
165
174
|
// This allows other scripts to know exactly when our work is done
|
|
166
175
|
/**
|
|
167
176
|
* Dispatches a custom event to signal that trefs insertion is complete
|
|
168
177
|
* @fires trefs-inserted
|
|
169
178
|
*/
|
|
170
|
-
document.dispatchEvent(new CustomEvent('trefs-inserted', {
|
|
171
|
-
detail: { count: domChanges.length }
|
|
179
|
+
document.dispatchEvent(new CustomEvent('trefs-inserted', {
|
|
180
|
+
detail: { count: domChanges.length }
|
|
172
181
|
}));
|
|
173
182
|
});
|
|
174
183
|
}
|
|
175
184
|
|
|
176
|
-
|
|
177
|
-
processTerms(allXTrefs);
|
|
178
|
-
} else {
|
|
179
|
-
console.error('allXTrefs is undefined or missing xtrefs property');
|
|
180
|
-
// Dispatch event even when there are no xrefs, so waiting code knows we're done
|
|
181
|
-
document.dispatchEvent(new CustomEvent('trefs-inserted', {
|
|
182
|
-
detail: { count: 0, error: 'Missing xtrefs data' }
|
|
183
|
-
}));
|
|
184
|
-
}
|
|
185
|
+
processTerms(allXTrefs);
|
|
185
186
|
}
|
|
186
187
|
|
|
187
188
|
/**
|
|
@@ -231,11 +232,14 @@ function initializeOnTrefsInserted(initCallback) {
|
|
|
231
232
|
* @listens DOMContentLoaded
|
|
232
233
|
*/
|
|
233
234
|
document.addEventListener('DOMContentLoaded', () => {
|
|
234
|
-
|
|
235
|
-
if (typeof allXTrefs !== 'undefined') {
|
|
235
|
+
if (typeof allXTrefs !== 'undefined' && allXTrefs?.xtrefs) {
|
|
236
236
|
insertTrefs(allXTrefs);
|
|
237
237
|
} else {
|
|
238
|
-
console.
|
|
238
|
+
console.error('allXTrefs is undefined or missing xtrefs property');
|
|
239
|
+
// Dispatch event even when there are no xrefs, so waiting code knows we're done
|
|
240
|
+
document.dispatchEvent(new CustomEvent('trefs-inserted', {
|
|
241
|
+
detail: { count: 0, error: 'Missing xtrefs data' }
|
|
242
|
+
}));
|
|
239
243
|
}
|
|
240
244
|
});
|
|
241
245
|
|
package/config/asset-map.json
CHANGED
package/index.js
CHANGED
|
@@ -38,6 +38,7 @@ module.exports = async function (options = {}) {
|
|
|
38
38
|
|
|
39
39
|
const { fixMarkdownFiles } = require('./src/fix-markdown-files.js');
|
|
40
40
|
const { processEscapedTags, restoreEscapedTags } = require('./src/escape-mechanism.js');
|
|
41
|
+
const { sortDefinitionTermsInHtml, fixDefinitionListStructure } = require('./src/html-dom-processor.js');
|
|
41
42
|
|
|
42
43
|
let template = fs.readFileSync(path.join(modulePath, 'templates/template.html'), 'utf8');
|
|
43
44
|
let assets = fs.readJsonSync(modulePath + '/config/asset-map.json');
|
|
@@ -71,7 +72,13 @@ module.exports = async function (options = {}) {
|
|
|
71
72
|
linkify: true,
|
|
72
73
|
typographer: true
|
|
73
74
|
})
|
|
75
|
+
/*
|
|
76
|
+
Configures a Markdown-it plugin by passing it an array of extension objects, each responsible for handling specific custom syntax in Markdown documents.
|
|
77
|
+
*/
|
|
74
78
|
.use(require('./src/markdown-it-extensions.js'), [
|
|
79
|
+
/*
|
|
80
|
+
The first extension (= the first configuration object = the first element of the array) focuses on terminology-related constructs, using a filter to match types against a regular expression (terminologyRegex).
|
|
81
|
+
*/
|
|
75
82
|
{
|
|
76
83
|
filter: type => type.match(terminologyRegex),
|
|
77
84
|
parse(token, type, primary) {
|
|
@@ -92,7 +99,20 @@ module.exports = async function (options = {}) {
|
|
|
92
99
|
href="${url}#term:${term}">${token.info.args[1]}</a>`;
|
|
93
100
|
}
|
|
94
101
|
else if (type === 'tref') {
|
|
95
|
-
|
|
102
|
+
// Support tref with optional alias: [[tref: spec, term, alias]]
|
|
103
|
+
const termName = token.info.args[1];
|
|
104
|
+
const alias = token.info.args[2]; // Optional alias
|
|
105
|
+
|
|
106
|
+
// Create IDs for both the original term and the alias to enable referencing by either
|
|
107
|
+
const termId = `term:${termName.replace(spaceRegex, '-').toLowerCase()}`;
|
|
108
|
+
const aliasId = alias ? `term:${alias.replace(spaceRegex, '-').toLowerCase()}` : '';
|
|
109
|
+
|
|
110
|
+
// Return the term structure similar to def, so it can be processed by markdown-it's definition list parser
|
|
111
|
+
if (aliasId && alias !== termName) {
|
|
112
|
+
return `<span class="transcluded-xref-term" id="${termId}"><span id="${aliasId}">${termName}</span></span>`;
|
|
113
|
+
} else {
|
|
114
|
+
return `<span class="transcluded-xref-term" id="${termId}">${termName}</span>`;
|
|
115
|
+
}
|
|
96
116
|
}
|
|
97
117
|
else {
|
|
98
118
|
references.push(primary);
|
|
@@ -100,6 +120,9 @@ module.exports = async function (options = {}) {
|
|
|
100
120
|
}
|
|
101
121
|
}
|
|
102
122
|
},
|
|
123
|
+
/*
|
|
124
|
+
The second extension is designed for handling specification references.
|
|
125
|
+
*/
|
|
103
126
|
{
|
|
104
127
|
filter: type => type.match(specNameRegex),
|
|
105
128
|
parse(token, type, name) {
|
|
@@ -174,7 +197,7 @@ module.exports = async function (options = {}) {
|
|
|
174
197
|
.use(require('@traptitech/markdown-it-katex'))
|
|
175
198
|
|
|
176
199
|
const katexRules = ['math_block', 'math_inline'];
|
|
177
|
-
const replacerRegex = /\[\[\s*([^\s
|
|
200
|
+
const replacerRegex = /\[\[\s*([^\s[\]:]+):?\s*([^\]\n]+)?\]\]/img;
|
|
178
201
|
const replacerArgsRegex = /\s*,+\s*/;
|
|
179
202
|
const replacers = [
|
|
180
203
|
{
|
|
@@ -183,29 +206,6 @@ module.exports = async function (options = {}) {
|
|
|
183
206
|
if (!path) return '';
|
|
184
207
|
return fs.readFileSync(path, 'utf8');
|
|
185
208
|
}
|
|
186
|
-
},
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Custom replacer for tref tags that converts them directly to HTML definition term elements.
|
|
190
|
-
*
|
|
191
|
-
* This is a critical part of our solution for fixing transcluded terms in definition lists.
|
|
192
|
-
* When a [[tref:spec,term]] tag is found in the markdown, this replacer transforms it into
|
|
193
|
-
* a proper <dt> element with the appropriate structure before the markdown parser processes it.
|
|
194
|
-
*
|
|
195
|
-
* By directly generating the HTML structure (instead of letting the markdown-it parser
|
|
196
|
-
* handle it later), we prevent the issue where transcluded terms break the definition list.
|
|
197
|
-
*
|
|
198
|
-
* @param {string} originalMatch - The original [[tref:spec,term]] tag found in the markdown
|
|
199
|
-
* @param {string} type - The tag type ('tref')
|
|
200
|
-
* @param {string} spec - The specification identifier (e.g., 'wot-1')
|
|
201
|
-
* @param {string} term - The term to transclude (e.g., 'DAR')
|
|
202
|
-
* @returns {string} - HTML representation of the term as a dt element
|
|
203
|
-
*/
|
|
204
|
-
{
|
|
205
|
-
test: 'tref',
|
|
206
|
-
transform: function (originalMatch, type, spec, term) {
|
|
207
|
-
return `<dt class="transcluded-xref-term"><span class="transcluded-xref-term" id="term:${term.replace(/\s+/g, '-').toLowerCase()}">${term}</span></dt>`;
|
|
208
|
-
}
|
|
209
209
|
}
|
|
210
210
|
];
|
|
211
211
|
|
|
@@ -295,208 +295,6 @@ module.exports = async function (options = {}) {
|
|
|
295
295
|
throw Error("katex distribution could not be located");
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
function sortDefinitionTermsInHtml(html) {
|
|
299
|
-
const { JSDOM } = require('jsdom');
|
|
300
|
-
const dom = new JSDOM(html);
|
|
301
|
-
const document = dom.window.document;
|
|
302
|
-
|
|
303
|
-
// Find the terms and definitions list
|
|
304
|
-
const dlElement = document.querySelector('.terms-and-definitions-list');
|
|
305
|
-
if (!dlElement) return html; // If not found, return the original HTML
|
|
306
|
-
|
|
307
|
-
// Collect all dt/dd pairs
|
|
308
|
-
const pairs = [];
|
|
309
|
-
let currentDt = null;
|
|
310
|
-
let currentDds = [];
|
|
311
|
-
|
|
312
|
-
// Process each child of the dl element
|
|
313
|
-
Array.from(dlElement.children).forEach(child => {
|
|
314
|
-
if (child.tagName === 'DT') {
|
|
315
|
-
// If we already have a dt, save the current pair
|
|
316
|
-
if (currentDt) {
|
|
317
|
-
pairs.push({
|
|
318
|
-
dt: currentDt,
|
|
319
|
-
dds: [...currentDds],
|
|
320
|
-
text: currentDt.textContent.trim().toLowerCase() // Use lowercase for sorting
|
|
321
|
-
});
|
|
322
|
-
currentDds = []; // Reset dds for the next dt
|
|
323
|
-
}
|
|
324
|
-
currentDt = child;
|
|
325
|
-
} else if (child.tagName === 'DD' && currentDt) {
|
|
326
|
-
currentDds.push(child);
|
|
327
|
-
}
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
// Add the last pair if exists
|
|
331
|
-
if (currentDt) {
|
|
332
|
-
pairs.push({
|
|
333
|
-
dt: currentDt,
|
|
334
|
-
dds: [...currentDds],
|
|
335
|
-
text: currentDt.textContent.trim().toLowerCase()
|
|
336
|
-
});
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
// Sort pairs case-insensitively
|
|
340
|
-
pairs.sort((a, b) => a.text.localeCompare(b.text));
|
|
341
|
-
|
|
342
|
-
// Clear the dl element
|
|
343
|
-
while (dlElement.firstChild) {
|
|
344
|
-
dlElement.removeChild(dlElement.firstChild);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// Re-append elements in sorted order
|
|
348
|
-
pairs.forEach(pair => {
|
|
349
|
-
dlElement.appendChild(pair.dt);
|
|
350
|
-
pair.dds.forEach(dd => {
|
|
351
|
-
dlElement.appendChild(dd);
|
|
352
|
-
});
|
|
353
|
-
});
|
|
354
|
-
|
|
355
|
-
// Return the modified HTML
|
|
356
|
-
return dom.serialize();
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// Function to fix broken definition list structures
|
|
360
|
-
/**
|
|
361
|
-
* This function repairs broken definition list (dl) structures in the HTML output.
|
|
362
|
-
* Specifically, it addresses the issue where transcluded terms (tref tags) break
|
|
363
|
-
* out of the definition list, creating separate lists instead of a continuous one.
|
|
364
|
-
*
|
|
365
|
-
* The strategy:
|
|
366
|
-
* 1. Find all definition lists (dl elements) in the document
|
|
367
|
-
* 2. Use the dl with class 'terms-and-definitions-list' as the main/target list
|
|
368
|
-
* 3. Process each subsequent node after the this main dl:
|
|
369
|
-
* - If another dl is found, merge all its children into the main dl
|
|
370
|
-
* - If a standalone dt is found, move it into the main dl
|
|
371
|
-
* - Remove any empty paragraphs that might be breaking the list continuity
|
|
372
|
-
*
|
|
373
|
-
* This ensures all terms appear in one continuous definition list,
|
|
374
|
-
* regardless of how they were originally rendered in the markdown.
|
|
375
|
-
*
|
|
376
|
-
* @param {string} html - The HTML content to fix
|
|
377
|
-
* @returns {string} - The fixed HTML content with merged definition lists
|
|
378
|
-
*/
|
|
379
|
-
function fixDefinitionListStructure(html) {
|
|
380
|
-
const { JSDOM } = require('jsdom');
|
|
381
|
-
const dom = new JSDOM(html);
|
|
382
|
-
const document = dom.window.document;
|
|
383
|
-
|
|
384
|
-
// Find all dl elements first
|
|
385
|
-
const allDls = Array.from(document.querySelectorAll('dl'));
|
|
386
|
-
|
|
387
|
-
// Then filter to find the one with the terms-and-definitions-list class
|
|
388
|
-
const dlElements = allDls.filter(dl => {
|
|
389
|
-
return dl?.classList?.contains('terms-and-definitions-list');
|
|
390
|
-
});
|
|
391
|
-
|
|
392
|
-
// Find any transcluded term dt elements anywhere in the document
|
|
393
|
-
const transcludedTerms = document.querySelectorAll('dt.transcluded-xref-term');
|
|
394
|
-
|
|
395
|
-
let mainDl = null;
|
|
396
|
-
|
|
397
|
-
// If we have an existing dl with the terms-and-definitions-list class, use it
|
|
398
|
-
if (dlElements.length > 0) {
|
|
399
|
-
mainDl = dlElements[0]; // Use the first one
|
|
400
|
-
}
|
|
401
|
-
// If we have transcluded terms but no main dl, we need to create one
|
|
402
|
-
else if (transcludedTerms.length > 0) {
|
|
403
|
-
// Create a new dl element with the right class
|
|
404
|
-
mainDl = document.createElement('dl');
|
|
405
|
-
mainDl.className = 'terms-and-definitions-list';
|
|
406
|
-
|
|
407
|
-
// Look for the marker
|
|
408
|
-
const marker = document.getElementById('terminology-section-start');
|
|
409
|
-
|
|
410
|
-
if (marker) {
|
|
411
|
-
// Insert the new dl right after the marker
|
|
412
|
-
if (marker.nextSibling) {
|
|
413
|
-
marker.parentNode.insertBefore(mainDl, marker.nextSibling);
|
|
414
|
-
} else {
|
|
415
|
-
marker.parentNode.appendChild(mainDl);
|
|
416
|
-
}
|
|
417
|
-
} else {
|
|
418
|
-
// Fallback to the original approach if marker isn't found
|
|
419
|
-
const firstTerm = transcludedTerms[0];
|
|
420
|
-
const insertPoint = firstTerm.parentNode;
|
|
421
|
-
insertPoint.parentNode.insertBefore(mainDl, insertPoint);
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Safety check - if we still don't have a mainDl, exit early to avoid null reference errors
|
|
426
|
-
if (!mainDl) {
|
|
427
|
-
return html; // Return the original HTML without modifications
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Now process all transcluded terms and other dt elements
|
|
431
|
-
transcludedTerms.forEach(dt => {
|
|
432
|
-
// Check if this dt is not already inside our main dl
|
|
433
|
-
if (dt.parentElement !== mainDl) {
|
|
434
|
-
// Move it into the main dl
|
|
435
|
-
const dtClone = dt.cloneNode(true);
|
|
436
|
-
mainDl.appendChild(dtClone);
|
|
437
|
-
dt.parentNode.removeChild(dt);
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
// First special case - handle transcluded-xref-term dt that comes BEFORE the main dl
|
|
442
|
-
const transcludedTermsBeforeMainDl = document.querySelectorAll('dt.transcluded-xref-term');
|
|
443
|
-
|
|
444
|
-
// Special handling for transcluded terms that appear BEFORE the main dl
|
|
445
|
-
transcludedTermsBeforeMainDl.forEach(dt => {
|
|
446
|
-
// Check if this dt is not already inside our main list
|
|
447
|
-
if (dt.parentElement !== mainDl) {
|
|
448
|
-
// This is a dt outside our main list - move it into the main dl
|
|
449
|
-
const dtClone = dt.cloneNode(true);
|
|
450
|
-
mainDl.appendChild(dtClone);
|
|
451
|
-
dt.parentNode.removeChild(dt);
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
// Remove any empty dt elements that may exist
|
|
456
|
-
const emptyDts = mainDl.querySelectorAll('dt:empty');
|
|
457
|
-
emptyDts.forEach(emptyDt => {
|
|
458
|
-
emptyDt.parentNode.removeChild(emptyDt);
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
// Process all subsequent content after the main dl
|
|
462
|
-
let currentNode = mainDl.nextSibling;
|
|
463
|
-
|
|
464
|
-
// Process all subsequent content
|
|
465
|
-
while (currentNode) {
|
|
466
|
-
// Save the next node before potentially modifying the DOM
|
|
467
|
-
const nextNode = currentNode.nextSibling;
|
|
468
|
-
|
|
469
|
-
// Handle different node types
|
|
470
|
-
if (currentNode.nodeType === 1) { // 1 = Element node
|
|
471
|
-
if (currentNode.tagName === 'DL') {
|
|
472
|
-
// Found another definition list - move all its children to the main dl
|
|
473
|
-
while (currentNode.firstChild) {
|
|
474
|
-
mainDl.appendChild(currentNode.firstChild);
|
|
475
|
-
}
|
|
476
|
-
// Remove the now-empty dl element
|
|
477
|
-
currentNode.parentNode.removeChild(currentNode);
|
|
478
|
-
}
|
|
479
|
-
else if (currentNode.tagName === 'DT') {
|
|
480
|
-
// Found a standalone dt - move it into the main dl
|
|
481
|
-
const dtClone = currentNode.cloneNode(true);
|
|
482
|
-
mainDl.appendChild(dtClone);
|
|
483
|
-
currentNode.parentNode.removeChild(currentNode);
|
|
484
|
-
}
|
|
485
|
-
else if (currentNode.tagName === 'P' &&
|
|
486
|
-
(!currentNode.textContent || currentNode.textContent.trim() === '')) {
|
|
487
|
-
// Remove empty paragraphs - these break the list structure
|
|
488
|
-
currentNode.parentNode.removeChild(currentNode);
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// Move to the next node we saved earlier
|
|
493
|
-
currentNode = nextNode;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Return the fixed HTML
|
|
497
|
-
return dom.serialize();
|
|
498
|
-
}
|
|
499
|
-
|
|
500
298
|
async function render(spec, assets) {
|
|
501
299
|
try {
|
|
502
300
|
noticeTitles = {};
|
|
@@ -507,6 +305,13 @@ module.exports = async function (options = {}) {
|
|
|
507
305
|
return template.replace(/\${(.*?)}/g, (match, p1) => variables[p1.trim()]);
|
|
508
306
|
}
|
|
509
307
|
|
|
308
|
+
// Add current date in 'DD Month YYYY' format for template injection
|
|
309
|
+
const date = new Date();
|
|
310
|
+
const day = String(date.getDate()).padStart(2, '0');
|
|
311
|
+
const month = date.toLocaleString('en-US', { month: 'long' });
|
|
312
|
+
const year = date.getFullYear();
|
|
313
|
+
const currentDate = `${day} ${month} ${year}`;
|
|
314
|
+
|
|
510
315
|
const docs = await Promise.all(
|
|
511
316
|
(spec.markdown_paths || ['spec.md']).map(_path =>
|
|
512
317
|
fs.readFile(spec.spec_directory + _path, 'utf8')
|
|
@@ -572,6 +377,7 @@ module.exports = async function (options = {}) {
|
|
|
572
377
|
specLogoLink: spec.logo_link,
|
|
573
378
|
spec: JSON.stringify(spec),
|
|
574
379
|
externalSpecsList: externalSpecsList,
|
|
380
|
+
currentDate: currentDate
|
|
575
381
|
});
|
|
576
382
|
|
|
577
383
|
const outputPath = path.join(spec.destination, 'index.html');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-up-t",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Technical specification drafting tool that generates rich specification documents from markdown. Forked from https://github.com/decentralized-identity/spec-up by Daniel Buchner (https://github.com/csuwildcat)",
|
|
5
5
|
"main": "./index",
|
|
6
6
|
"repository": {
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"axios": "^1.7.7",
|
|
29
29
|
"dedent": "^1.5.3",
|
|
30
30
|
"diff": "^7.0.0",
|
|
31
|
+
"docx": "^8.5.0",
|
|
31
32
|
"dotenv": "^16.4.7",
|
|
32
33
|
"find-pkg-dir": "^2.0.0",
|
|
33
34
|
"fs-extra": "^11.3.0",
|
|
@@ -67,7 +68,8 @@
|
|
|
67
68
|
"braces": ">=3.0.3"
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
|
-
"jest": "^29.7.0"
|
|
71
|
+
"jest": "^29.7.0",
|
|
72
|
+
"jest-environment-jsdom": "^30.0.5"
|
|
71
73
|
},
|
|
72
74
|
"scripts": {
|
|
73
75
|
"test": "jest",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# Temporarily disabled for Automatic Analysis
|
|
2
|
+
# sonar.projectKey=blockchainbird_spec-up-t
|
|
3
|
+
# sonar.organization=blockchainbird
|
|
4
|
+
# sonar.host.url=https://sonarcloud.io
|
|
5
|
+
# sonar.token=SONAR_TOKEN
|
|
6
|
+
# sonar.exclusions=assets/compiled/refs.json
|
|
7
|
+
# sonar.cpd.exclusions=**/*.test.js
|
|
@@ -54,26 +54,37 @@ const readlineSync = require('readline-sync');
|
|
|
54
54
|
* @returns {boolean} True if the xtref is found in the content
|
|
55
55
|
*/
|
|
56
56
|
function isXTrefInMarkdown(xtref, markdownContent) {
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
// Escape special regex characters in externalSpec and term
|
|
58
|
+
const escapedSpec = xtref.externalSpec.replace(/[.*+?^${}()|[\]\\-]/g, '\\$&');
|
|
59
|
+
const escapedTerm = xtref.term.replace(/[.*+?^${}()|[\]\\-]/g, '\\$&');
|
|
60
|
+
|
|
61
|
+
// Check for both the term and with any alias (accounting for spaces)
|
|
62
|
+
const regexTerm = new RegExp(`\\[\\[(?:x|t)ref:\\s*${escapedSpec},\\s*${escapedTerm}(?:,\\s*[^\\]]+)?\\]\\]`, 'g');
|
|
63
|
+
return regexTerm.test(markdownContent);
|
|
59
64
|
}
|
|
60
65
|
|
|
61
66
|
/**
|
|
62
67
|
* Helper function to process an XTref string and return an object.
|
|
63
68
|
*
|
|
64
69
|
* @param {string} xtref - The xtref string to process
|
|
65
|
-
* @returns {Object} An object with externalSpec and
|
|
70
|
+
* @returns {Object} An object with externalSpec, term, and optional alias properties
|
|
66
71
|
*/
|
|
67
72
|
function processXTref(xtref) {
|
|
68
|
-
|
|
73
|
+
const parts = xtref
|
|
69
74
|
.replace(/\[\[(?:xref|tref):/, '')
|
|
70
75
|
.replace(/\]\]/, '')
|
|
71
76
|
.trim()
|
|
72
|
-
.split(
|
|
77
|
+
.split(/,/);
|
|
78
|
+
|
|
73
79
|
const xtrefObject = {
|
|
74
|
-
externalSpec:
|
|
75
|
-
term:
|
|
80
|
+
externalSpec: parts[0].trim(),
|
|
81
|
+
term: parts[1].trim()
|
|
76
82
|
};
|
|
83
|
+
|
|
84
|
+
// Add alias if provided (third parameter)
|
|
85
|
+
if (parts.length > 2 && parts[2].trim()) {
|
|
86
|
+
xtrefObject.alias = parts[2].trim();
|
|
87
|
+
}
|
|
77
88
|
|
|
78
89
|
return xtrefObject;
|
|
79
90
|
}
|
|
@@ -129,6 +140,7 @@ function extendXTrefs(config, xtrefs) {
|
|
|
129
140
|
xtref.owner = urlParts[1];
|
|
130
141
|
xtref.repo = urlParts[2];
|
|
131
142
|
xtref.avatarUrl = repo.avatar_url;
|
|
143
|
+
xtref.ghPageUrl = repo.gh_page; // Add GitHub Pages URL
|
|
132
144
|
}
|
|
133
145
|
});
|
|
134
146
|
|
|
@@ -149,9 +161,8 @@ function extendXTrefs(config, xtrefs) {
|
|
|
149
161
|
*
|
|
150
162
|
* @param {Object} config - The configuration object from specs.json
|
|
151
163
|
* @param {string} GITHUB_API_TOKEN - The GitHub API token
|
|
152
|
-
* @param {Object} options - Configuration options
|
|
153
164
|
*/
|
|
154
|
-
function processExternalReferences(config, GITHUB_API_TOKEN
|
|
165
|
+
function processExternalReferences(config, GITHUB_API_TOKEN) {
|
|
155
166
|
const { processXTrefsData } = require('./collectExternalReferences/processXTrefsData.js');
|
|
156
167
|
const { doesUrlExist } = require('./utils/doesUrlExist.js');
|
|
157
168
|
const externalSpecsRepos = config.specs[0].external_specs;
|
|
@@ -257,7 +268,7 @@ function processExternalReferences(config, GITHUB_API_TOKEN, options) {
|
|
|
257
268
|
// }
|
|
258
269
|
// ]
|
|
259
270
|
|
|
260
|
-
processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, outputPathJS, outputPathJSTimeStamped
|
|
271
|
+
processXTrefsData(allXTrefs, GITHUB_API_TOKEN, outputPathJSON, outputPathJS, outputPathJSTimeStamped);
|
|
261
272
|
}
|
|
262
273
|
|
|
263
274
|
/**
|
|
@@ -320,7 +331,7 @@ function collectExternalReferences(options = {}) {
|
|
|
320
331
|
return;
|
|
321
332
|
}
|
|
322
333
|
} else {
|
|
323
|
-
processExternalReferences(config, GITHUB_API_TOKEN
|
|
334
|
+
processExternalReferences(config, GITHUB_API_TOKEN);
|
|
324
335
|
}
|
|
325
336
|
}
|
|
326
337
|
|