spec-up-t 1.1.55 → 1.2.1
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/assets/compiled/body.js +59 -8
- package/assets/compiled/head.css +13 -12
- package/assets/css/adjust-font-size.css +11 -0
- package/assets/css/collapse-meta-info.css +27 -8
- package/assets/css/create-term-filter.css +11 -0
- package/assets/css/index.css +15 -0
- package/assets/css/prism.css +176 -153
- package/assets/css/prism.dark.css +157 -0
- package/assets/css/prism.default.css +180 -0
- package/assets/css/terms-and-definitions.1.css +223 -0
- package/assets/css/terms-and-definitions.css +214 -100
- package/assets/js/addAnchorsToTerms.js +1 -1
- package/assets/js/collapse-definitions.js +2 -1
- package/assets/js/collapse-meta-info.js +25 -11
- package/assets/js/create-term-filter.js +61 -0
- package/assets/js/horizontal-scroll-hint.js +159 -0
- package/assets/js/index.1.js +137 -0
- package/assets/js/index.js +2 -1
- package/assets/js/insert-trefs.js +122 -116
- package/assets/js/insert-xrefs.1.js +372 -0
- package/assets/js/{show-commit-hashes.js → insert-xrefs.js} +67 -7
- package/assets/js/prism.dark.js +24 -0
- package/assets/js/prism.default.js +23 -0
- package/assets/js/prism.js +4 -5
- package/assets/js/search.js +1 -1
- package/assets/js/toggle-dense-info.js +40 -0
- package/branches.md +4 -24
- package/index.js +429 -190
- package/index.new.js +662 -0
- package/package.json +1 -2
- package/src/asset-map.json +9 -5
- package/src/collect-external-references.js +16 -9
- package/src/collectExternalReferences/fetchTermsFromIndex.js +328 -0
- package/src/collectExternalReferences/processXTrefsData.js +73 -18
- package/src/create-pdf.js +385 -89
- package/src/health-check/term-references-checker.js +3 -2
- package/src/health-check/tref-term-checker.js +18 -17
- package/src/markdown-it-extensions.js +134 -103
- package/src/prepare-tref.js +61 -24
- package/src/utils/fetch.js +100 -0
- package/templates/template.html +12 -7
- package/assets/js/css-helper.js +0 -30
- package/src/collectExternalReferences/fetchTermsFromGitHubRepository.js +0 -232
- package/src/collectExternalReferences/fetchTermsFromGitHubRepository.test.js +0 -385
- package/src/collectExternalReferences/octokitClient.js +0 -96
package/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const { initialize } = require('./src/init');
|
|
2
|
+
const {fetchExternalTerms} = require('./src/utils/fetch');
|
|
2
3
|
|
|
3
4
|
module.exports = async function (options = {}) {
|
|
4
5
|
try {
|
|
@@ -30,6 +31,8 @@ module.exports = async function (options = {}) {
|
|
|
30
31
|
const modulePath = findPkgDir(__dirname);
|
|
31
32
|
let config = fs.readJsonSync('./output/specs-generated.json');
|
|
32
33
|
|
|
34
|
+
const externalTerms = fetchExternalTerms();
|
|
35
|
+
|
|
33
36
|
const createExternalSpecsList = require('./src/create-external-specs-list.js');
|
|
34
37
|
|
|
35
38
|
const externalSpecsList = createExternalSpecsList(config);
|
|
@@ -46,6 +49,134 @@ module.exports = async function (options = {}) {
|
|
|
46
49
|
let externalReferences;
|
|
47
50
|
let references = [];
|
|
48
51
|
let definitions = [];
|
|
52
|
+
var toc;
|
|
53
|
+
var specGroups = {};
|
|
54
|
+
var noticeTitles = {};
|
|
55
|
+
|
|
56
|
+
const noticeTypes = {
|
|
57
|
+
note: 1,
|
|
58
|
+
issue: 1,
|
|
59
|
+
example: 1,
|
|
60
|
+
warning: 1,
|
|
61
|
+
todo: 1
|
|
62
|
+
};
|
|
63
|
+
const spaceRegex = /\s+/g;
|
|
64
|
+
const specNameRegex = /^spec$|^spec[-]*\w+$/i;
|
|
65
|
+
const terminologyRegex = /^def$|^ref$|^xref|^tref$/i;
|
|
66
|
+
const specCorpus = fs.readJsonSync(modulePath + '/assets/compiled/refs.json');
|
|
67
|
+
const containers = require('markdown-it-container');
|
|
68
|
+
|
|
69
|
+
/*
|
|
70
|
+
`const md` is assigned an instance of the markdown-it parser configured with various plugins and extensions. This instance (md) is intended to be used later to parse and render Markdown strings.
|
|
71
|
+
|
|
72
|
+
The md function (which is an instance of the markdown-it parser) takes a Markdown string as its primary argument. It is called elsewhere as follows: `md.render(doc)`
|
|
73
|
+
*/
|
|
74
|
+
const md = require('markdown-it')({
|
|
75
|
+
html: true,
|
|
76
|
+
linkify: true,
|
|
77
|
+
typographer: true
|
|
78
|
+
})
|
|
79
|
+
.use(require('./src/markdown-it-extensions.js'), [
|
|
80
|
+
{
|
|
81
|
+
filter: type => type.match(terminologyRegex),
|
|
82
|
+
parse(token, type, primary) {
|
|
83
|
+
if (!primary) return;
|
|
84
|
+
if (type === 'def') {
|
|
85
|
+
definitions.push(token.info.args);
|
|
86
|
+
return token.info.args.reduce((acc, syn) => {
|
|
87
|
+
return `<span id="term:${syn.replace(spaceRegex, '-').toLowerCase()}">${acc}</span>`;
|
|
88
|
+
}, primary);
|
|
89
|
+
}
|
|
90
|
+
else if (type === 'xref') {
|
|
91
|
+
// Get the URL for the external specification reference, or default to '#' if not found
|
|
92
|
+
const externalSpec = findExternalSpecByKey(config, token.info.args[0]);
|
|
93
|
+
const url = externalSpec?.gh_page || '#';
|
|
94
|
+
|
|
95
|
+
const term = token.info.args[1].replace(spaceRegex, '-').toLowerCase();
|
|
96
|
+
return `<a class="x-term-reference term-reference" data-local-href="#term:${token.info.args[0]}:${term}"
|
|
97
|
+
href="${url}#term:${term}">${token.info.args[1]}</a>`;
|
|
98
|
+
}
|
|
99
|
+
else if (type === 'tref') {
|
|
100
|
+
return `<span class="transcluded-xref-term" id="term:${token.info.args[1]}">${token.info.args[1]}</span>`;
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
references.push(primary);
|
|
104
|
+
return `<a class="term-reference" href="#term:${primary.replace(spaceRegex, '-').toLowerCase()}">${primary}</a>`;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
filter: type => type.match(specNameRegex),
|
|
110
|
+
parse(token, type, name) {
|
|
111
|
+
if (name) {
|
|
112
|
+
let _name = name.replace(spaceRegex, '-').toUpperCase();
|
|
113
|
+
let spec = specCorpus[_name] ||
|
|
114
|
+
specCorpus[_name.toLowerCase()] ||
|
|
115
|
+
specCorpus[name.toLowerCase()] ||
|
|
116
|
+
specCorpus[name];
|
|
117
|
+
if (spec) {
|
|
118
|
+
spec._name = _name;
|
|
119
|
+
let group = specGroups[type] = specGroups[type] || {};
|
|
120
|
+
token.info.spec = group[_name] = spec;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
render(token, type, name) {
|
|
125
|
+
if (name) {
|
|
126
|
+
let spec = token.info.spec;
|
|
127
|
+
if (spec) return `[<a class="spec-reference" href="#ref:${spec._name}">${spec._name}</a>]`;
|
|
128
|
+
}
|
|
129
|
+
else return renderRefGroup(type);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
])
|
|
133
|
+
.use(require('markdown-it-attrs'))
|
|
134
|
+
.use(require('markdown-it-chart').default)
|
|
135
|
+
.use(require('markdown-it-deflist'))
|
|
136
|
+
.use(require('markdown-it-references'))
|
|
137
|
+
.use(require('markdown-it-icons').default, 'font-awesome')
|
|
138
|
+
.use(require('markdown-it-ins'))
|
|
139
|
+
.use(require('markdown-it-mark'))
|
|
140
|
+
.use(require('markdown-it-textual-uml'))
|
|
141
|
+
.use(require('markdown-it-sub'))
|
|
142
|
+
.use(require('markdown-it-sup'))
|
|
143
|
+
.use(require('markdown-it-task-lists'))
|
|
144
|
+
.use(require('markdown-it-multimd-table'), {
|
|
145
|
+
multiline: true,
|
|
146
|
+
rowspan: true,
|
|
147
|
+
headerless: true
|
|
148
|
+
})
|
|
149
|
+
.use(containers, 'notice', {
|
|
150
|
+
validate: function (params) {
|
|
151
|
+
let matches = params.match(/(\w+)\s?(.*)?/);
|
|
152
|
+
return matches && noticeTypes[matches[1]];
|
|
153
|
+
},
|
|
154
|
+
render: function (tokens, idx) {
|
|
155
|
+
let matches = tokens[idx].info.match(/(\w+)\s?(.*)?/);
|
|
156
|
+
if (matches && tokens[idx].nesting === 1) {
|
|
157
|
+
let id;
|
|
158
|
+
let type = matches[1];
|
|
159
|
+
if (matches[2]) {
|
|
160
|
+
id = matches[2].trim().replace(/\s+/g, '-').toLowerCase();
|
|
161
|
+
if (noticeTitles[id]) id += '-' + noticeTitles[id]++;
|
|
162
|
+
else noticeTitles[id] = 1;
|
|
163
|
+
}
|
|
164
|
+
else id = type + '-' + noticeTypes[type]++;
|
|
165
|
+
return `<div id="${id}" class="notice ${type}"><a class="notice-link" href="#${id}">${type.toUpperCase()}</a>`;
|
|
166
|
+
}
|
|
167
|
+
else return '</div>\n';
|
|
168
|
+
}
|
|
169
|
+
})
|
|
170
|
+
.use(require('markdown-it-prism'))
|
|
171
|
+
.use(require('markdown-it-toc-and-anchor').default, {
|
|
172
|
+
tocClassName: 'toc',
|
|
173
|
+
tocFirstLevel: 2,
|
|
174
|
+
tocLastLevel: 4,
|
|
175
|
+
tocCallback: (_md, _tokens, html) => toc = html,
|
|
176
|
+
anchorLinkSymbol: '#', // was: §
|
|
177
|
+
anchorClassName: 'toc-anchor d-print-none'
|
|
178
|
+
})
|
|
179
|
+
.use(require('@traptitech/markdown-it-katex'))
|
|
49
180
|
|
|
50
181
|
const katexRules = ['math_block', 'math_inline'];
|
|
51
182
|
const replacerRegex = /\[\[\s*([^\s\[\]:]+):?\s*([^\]\n]+)?\]\]/img;
|
|
@@ -58,10 +189,34 @@ module.exports = async function (options = {}) {
|
|
|
58
189
|
return fs.readFileSync(path, 'utf8');
|
|
59
190
|
}
|
|
60
191
|
},
|
|
192
|
+
{
|
|
193
|
+
test: 'spec',
|
|
194
|
+
transform: function (originalMatch, type, name) {
|
|
195
|
+
// Simply return an empty string or special marker that won't be treated as a definition term
|
|
196
|
+
// The actual rendering will be handled by the markdown-it extension
|
|
197
|
+
return `<span class="spec-marker" data-spec="${name}"></span>`;
|
|
198
|
+
}
|
|
199
|
+
},
|
|
200
|
+
/**
|
|
201
|
+
* Custom replacer for tref tags that converts them directly to HTML definition term elements.
|
|
202
|
+
*
|
|
203
|
+
* This is a critical part of our solution for fixing transcluded terms in definition lists.
|
|
204
|
+
* When a [[tref:spec,term]] tag is found in the markdown, this replacer transforms it into
|
|
205
|
+
* a proper <dt> element with the appropriate structure before the markdown parser processes it.
|
|
206
|
+
*
|
|
207
|
+
* By directly generating the HTML structure (instead of letting the markdown-it parser
|
|
208
|
+
* handle it later), we prevent the issue where transcluded terms break the definition list.
|
|
209
|
+
*
|
|
210
|
+
* @param {string} originalMatch - The original [[tref:spec,term]] tag found in the markdown
|
|
211
|
+
* @param {string} type - The tag type ('tref')
|
|
212
|
+
* @param {string} spec - The specification identifier (e.g., 'wot-1')
|
|
213
|
+
* @param {string} term - The term to transclude (e.g., 'DAR')
|
|
214
|
+
* @returns {string} - HTML representation of the term as a dt element
|
|
215
|
+
*/
|
|
61
216
|
{
|
|
62
217
|
test: 'tref',
|
|
63
218
|
transform: function (originalMatch, type, spec, term) {
|
|
64
|
-
return
|
|
219
|
+
return `<dt class="transcluded-xref-term"><span class="transcluded-xref-term" id="term:${term.replace(/\s+/g, '-').toLowerCase()}">${term}</span></dt>`;
|
|
65
220
|
}
|
|
66
221
|
}
|
|
67
222
|
];
|
|
@@ -85,6 +240,19 @@ module.exports = async function (options = {}) {
|
|
|
85
240
|
|
|
86
241
|
const xtrefsData = createScriptElementWithXTrefDataForEmbeddingInHtml();
|
|
87
242
|
|
|
243
|
+
/**
|
|
244
|
+
* Processes custom tag patterns in markdown content and applies transformation functions.
|
|
245
|
+
*
|
|
246
|
+
* This function scans the document for special tag patterns like [[tref:spec,term]]
|
|
247
|
+
* and replaces them with the appropriate HTML using the matching replacer.
|
|
248
|
+
*
|
|
249
|
+
* For tref tags, this is where the magic happens - we intercept them before
|
|
250
|
+
* the markdown parser even sees them, and convert them directly to HTML structure
|
|
251
|
+
* that will integrate properly with definition lists.
|
|
252
|
+
*
|
|
253
|
+
* @param {string} doc - The markdown document to process
|
|
254
|
+
* @returns {string} - The processed document with tags replaced by their HTML equivalents
|
|
255
|
+
*/
|
|
88
256
|
function applyReplacers(doc) {
|
|
89
257
|
return doc.replace(replacerRegex, function (match, type, args) {
|
|
90
258
|
let replacer = replacers.find(r => type.trim().match(r.test));
|
|
@@ -95,6 +263,7 @@ module.exports = async function (options = {}) {
|
|
|
95
263
|
return match;
|
|
96
264
|
});
|
|
97
265
|
}
|
|
266
|
+
|
|
98
267
|
function normalizePath(path) {
|
|
99
268
|
return path.trim().replace(/\/$/g, '') + '/';
|
|
100
269
|
}
|
|
@@ -137,212 +306,282 @@ module.exports = async function (options = {}) {
|
|
|
137
306
|
throw Error("katex distribution could not be located");
|
|
138
307
|
}
|
|
139
308
|
|
|
140
|
-
|
|
309
|
+
function sortDefinitionTermsInHtml(html) {
|
|
310
|
+
const { JSDOM } = require('jsdom');
|
|
311
|
+
const dom = new JSDOM(html);
|
|
312
|
+
const document = dom.window.document;
|
|
313
|
+
|
|
314
|
+
// Find the terms and definitions list
|
|
315
|
+
const dlElement = document.querySelector('.terms-and-definitions-list');
|
|
316
|
+
if (!dlElement) return html; // If not found, return the original HTML
|
|
317
|
+
|
|
318
|
+
// Collect all dt/dd pairs
|
|
319
|
+
const pairs = [];
|
|
320
|
+
let currentDt = null;
|
|
321
|
+
let currentDds = [];
|
|
322
|
+
|
|
323
|
+
// Process each child of the dl element
|
|
324
|
+
Array.from(dlElement.children).forEach(child => {
|
|
325
|
+
if (child.tagName === 'DT') {
|
|
326
|
+
// If we already have a dt, save the current pair
|
|
327
|
+
if (currentDt) {
|
|
328
|
+
pairs.push({
|
|
329
|
+
dt: currentDt,
|
|
330
|
+
dds: [...currentDds],
|
|
331
|
+
text: currentDt.textContent.trim().toLowerCase() // Use lowercase for sorting
|
|
332
|
+
});
|
|
333
|
+
currentDds = []; // Reset dds for the next dt
|
|
334
|
+
}
|
|
335
|
+
currentDt = child;
|
|
336
|
+
} else if (child.tagName === 'DD' && currentDt) {
|
|
337
|
+
currentDds.push(child);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
141
340
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
341
|
+
// Add the last pair if exists
|
|
342
|
+
if (currentDt) {
|
|
343
|
+
pairs.push({
|
|
344
|
+
dt: currentDt,
|
|
345
|
+
dds: [...currentDds],
|
|
346
|
+
text: currentDt.textContent.trim().toLowerCase()
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Sort pairs case-insensitively
|
|
351
|
+
pairs.sort((a, b) => a.text.localeCompare(b.text));
|
|
352
|
+
|
|
353
|
+
// Clear the dl element
|
|
354
|
+
while (dlElement.firstChild) {
|
|
355
|
+
dlElement.removeChild(dlElement.firstChild);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Re-append elements in sorted order
|
|
359
|
+
pairs.forEach(pair => {
|
|
360
|
+
dlElement.appendChild(pair.dt);
|
|
361
|
+
pair.dds.forEach(dd => {
|
|
362
|
+
dlElement.appendChild(dd);
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
// Return the modified HTML
|
|
367
|
+
return dom.serialize();
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Function to fix broken definition list structures
|
|
371
|
+
/**
|
|
372
|
+
* This function repairs broken definition list (dl) structures in the HTML output.
|
|
373
|
+
* Specifically, it addresses the issue where transcluded terms (tref tags) break
|
|
374
|
+
* out of the definition list, creating separate lists instead of a continuous one.
|
|
375
|
+
*
|
|
376
|
+
* The strategy:
|
|
377
|
+
* 1. Find all definition lists (dl elements) in the document
|
|
378
|
+
* 2. Use the dl with class 'terms-and-definitions-list' as the main/target list
|
|
379
|
+
* 3. Process each subsequent node after the this main dl:
|
|
380
|
+
* - If another dl is found, merge all its children into the main dl
|
|
381
|
+
* - If a standalone dt is found, move it into the main dl
|
|
382
|
+
* - Remove any empty paragraphs that might be breaking the list continuity
|
|
383
|
+
*
|
|
384
|
+
* This ensures all terms appear in one continuous definition list,
|
|
385
|
+
* regardless of how they were originally rendered in the markdown.
|
|
386
|
+
*
|
|
387
|
+
* @param {string} html - The HTML content to fix
|
|
388
|
+
* @returns {string} - The fixed HTML content with merged definition lists
|
|
389
|
+
*/
|
|
390
|
+
function fixDefinitionListStructure(html) {
|
|
391
|
+
const { JSDOM } = require('jsdom');
|
|
392
|
+
const dom = new JSDOM(html);
|
|
393
|
+
const document = dom.window.document;
|
|
394
|
+
|
|
395
|
+
// Find all dl elements first
|
|
396
|
+
const allDls = Array.from(document.querySelectorAll('dl'));
|
|
397
|
+
|
|
398
|
+
// Then filter to find the one with the terms-and-definitions-list class
|
|
399
|
+
const dlElements = allDls.filter(dl => {
|
|
400
|
+
return dl.classList && dl.classList.contains('terms-and-definitions-list');
|
|
401
|
+
});
|
|
402
|
+
|
|
403
|
+
// Find any transcluded term dt elements anywhere in the document
|
|
404
|
+
const transcludedTerms = document.querySelectorAll('dt.transcluded-xref-term');
|
|
405
|
+
|
|
406
|
+
let mainDl = null;
|
|
407
|
+
|
|
408
|
+
// If we have transcluded terms but no main dl, we need to create one
|
|
409
|
+
if (transcludedTerms.length > 0 && dlElements.length === 0) {
|
|
410
|
+
// Create a new dl element with the right class
|
|
411
|
+
mainDl = document.createElement('dl');
|
|
412
|
+
mainDl.className = 'terms-and-definitions-list';
|
|
413
|
+
|
|
414
|
+
// Find a good location to insert it - use the first transcluded term's parent as reference
|
|
415
|
+
const firstTerm = transcludedTerms[0];
|
|
416
|
+
const insertPoint = firstTerm.parentNode;
|
|
417
|
+
insertPoint.parentNode.insertBefore(mainDl, insertPoint);
|
|
418
|
+
} else if (dlElements.length > 0) {
|
|
419
|
+
// Use the first terms-and-definitions-list as our main container
|
|
420
|
+
mainDl = dlElements[0];
|
|
421
|
+
} else {
|
|
422
|
+
// No dl and no transcluded terms, nothing to fix
|
|
423
|
+
return html;
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Now process all transcluded terms and other dt elements
|
|
427
|
+
transcludedTerms.forEach(dt => {
|
|
428
|
+
// Check if this dt is not already inside our main dl
|
|
429
|
+
if (dt.parentElement !== mainDl) {
|
|
430
|
+
// Move it into the main dl
|
|
431
|
+
const dtClone = dt.cloneNode(true);
|
|
432
|
+
mainDl.appendChild(dtClone);
|
|
433
|
+
dt.parentNode.removeChild(dt);
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
// First special case - handle transcluded-xref-term dt that comes BEFORE the main dl
|
|
438
|
+
const transcludedTermsBeforeMainDl = document.querySelectorAll('dt.transcluded-xref-term');
|
|
439
|
+
|
|
440
|
+
// Special handling for transcluded terms that appear BEFORE the main dl
|
|
441
|
+
transcludedTermsBeforeMainDl.forEach(dt => {
|
|
442
|
+
// Check if this dt is not already inside our main list
|
|
443
|
+
if (dt.parentElement !== mainDl) {
|
|
444
|
+
// This is a dt outside our main list - move it into the main dl
|
|
445
|
+
const dtClone = dt.cloneNode(true);
|
|
446
|
+
mainDl.appendChild(dtClone);
|
|
447
|
+
dt.parentNode.removeChild(dt);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
// Remove any empty dt elements that may exist
|
|
452
|
+
const emptyDts = mainDl.querySelectorAll('dt:empty');
|
|
453
|
+
emptyDts.forEach(emptyDt => {
|
|
454
|
+
emptyDt.parentNode.removeChild(emptyDt);
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
// Process all subsequent content after the main dl
|
|
458
|
+
let currentNode = mainDl.nextSibling;
|
|
459
|
+
|
|
460
|
+
// Process all subsequent content
|
|
461
|
+
while (currentNode) {
|
|
462
|
+
// Save the next node before potentially modifying the DOM
|
|
463
|
+
const nextNode = currentNode.nextSibling;
|
|
464
|
+
|
|
465
|
+
// Handle different node types
|
|
466
|
+
if (currentNode.nodeType === 1) { // 1 = Element node
|
|
467
|
+
if (currentNode.tagName === 'DL') {
|
|
468
|
+
// Found another definition list - move all its children to the main dl
|
|
469
|
+
while (currentNode.firstChild) {
|
|
470
|
+
mainDl.appendChild(currentNode.firstChild);
|
|
218
471
|
}
|
|
472
|
+
// Remove the now-empty dl element
|
|
473
|
+
currentNode.parentNode.removeChild(currentNode);
|
|
219
474
|
}
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
.use(require('markdown-it-icons').default, 'font-awesome')
|
|
226
|
-
.use(require('markdown-it-ins'))
|
|
227
|
-
.use(require('markdown-it-mark'))
|
|
228
|
-
.use(require('markdown-it-textual-uml'))
|
|
229
|
-
.use(require('markdown-it-sub'))
|
|
230
|
-
.use(require('markdown-it-sup'))
|
|
231
|
-
.use(require('markdown-it-task-lists'))
|
|
232
|
-
.use(require('markdown-it-multimd-table'), {
|
|
233
|
-
multiline: true,
|
|
234
|
-
rowspan: true,
|
|
235
|
-
headerless: true
|
|
236
|
-
})
|
|
237
|
-
.use(containers, 'notice', {
|
|
238
|
-
validate: function (params) {
|
|
239
|
-
let matches = params.match(/(\w+)\s?(.*)?/);
|
|
240
|
-
return matches && noticeTypes[matches[1]];
|
|
241
|
-
},
|
|
242
|
-
render: function (tokens, idx) {
|
|
243
|
-
let matches = tokens[idx].info.match(/(\w+)\s?(.*)?/);
|
|
244
|
-
if (matches && tokens[idx].nesting === 1) {
|
|
245
|
-
let id;
|
|
246
|
-
let type = matches[1];
|
|
247
|
-
if (matches[2]) {
|
|
248
|
-
id = matches[2].trim().replace(/\s+/g, '-').toLowerCase();
|
|
249
|
-
if (noticeTitles[id]) id += '-' + noticeTitles[id]++;
|
|
250
|
-
else noticeTitles[id] = 1;
|
|
251
|
-
}
|
|
252
|
-
else id = type + '-' + noticeTypes[type]++;
|
|
253
|
-
return `<div id="${id}" class="notice ${type}"><a class="notice-link" href="#${id}">${type.toUpperCase()}</a>`;
|
|
254
|
-
}
|
|
255
|
-
else return '</div>\n';
|
|
475
|
+
else if (currentNode.tagName === 'DT') {
|
|
476
|
+
// Found a standalone dt - move it into the main dl
|
|
477
|
+
const dtClone = currentNode.cloneNode(true);
|
|
478
|
+
mainDl.appendChild(dtClone);
|
|
479
|
+
currentNode.parentNode.removeChild(currentNode);
|
|
256
480
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
tocFirstLevel: 2,
|
|
262
|
-
tocLastLevel: 4,
|
|
263
|
-
tocCallback: (_md, _tokens, html) => toc = html,
|
|
264
|
-
anchorLinkSymbol: '#', // was: §
|
|
265
|
-
anchorClassName: 'toc-anchor'
|
|
266
|
-
})
|
|
267
|
-
.use(require('@traptitech/markdown-it-katex'))
|
|
268
|
-
|
|
269
|
-
async function render(spec, assets) {
|
|
270
|
-
try {
|
|
271
|
-
noticeTitles = {};
|
|
272
|
-
specGroups = {};
|
|
273
|
-
console.log('ℹ️ Rendering: ' + spec.title);
|
|
274
|
-
|
|
275
|
-
function interpolate(template, variables) {
|
|
276
|
-
return template.replace(/\${(.*?)}/g, (match, p1) => variables[p1.trim()]);
|
|
481
|
+
else if (currentNode.tagName === 'P' &&
|
|
482
|
+
(!currentNode.textContent || currentNode.textContent.trim() === '')) {
|
|
483
|
+
// Remove empty paragraphs - these break the list structure
|
|
484
|
+
currentNode.parentNode.removeChild(currentNode);
|
|
277
485
|
}
|
|
486
|
+
}
|
|
278
487
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
)
|
|
283
|
-
);
|
|
488
|
+
// Move to the next node we saved earlier
|
|
489
|
+
currentNode = nextNode;
|
|
490
|
+
}
|
|
284
491
|
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
}
|
|
492
|
+
// Return the fixed HTML
|
|
493
|
+
return dom.serialize();
|
|
494
|
+
}
|
|
289
495
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
}
|
|
496
|
+
async function render(spec, assets) {
|
|
497
|
+
try {
|
|
498
|
+
noticeTitles = {};
|
|
499
|
+
specGroups = {};
|
|
500
|
+
console.log('ℹ️ Rendering: ' + spec.title);
|
|
296
501
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
doc = applyReplacers(doc);
|
|
301
|
-
|
|
302
|
-
md[spec.katex ? "enable" : "disable"](katexRules);
|
|
303
|
-
|
|
304
|
-
// `render` is the rendered HTML
|
|
305
|
-
const render = md.render(doc);
|
|
306
|
-
|
|
307
|
-
const templateInterpolated = interpolate(template, {
|
|
308
|
-
title: spec.title,
|
|
309
|
-
description: spec.description,
|
|
310
|
-
author: spec.author,
|
|
311
|
-
toc: toc,
|
|
312
|
-
render: render,
|
|
313
|
-
assetsHead: assets.head,
|
|
314
|
-
assetsBody: assets.body,
|
|
315
|
-
assetsSvg: assets.svg,
|
|
316
|
-
features: Object.keys(features).join(' '),
|
|
317
|
-
externalReferences: JSON.stringify(externalReferences),
|
|
318
|
-
xtrefsData: xtrefsData,
|
|
319
|
-
specLogo: spec.logo,
|
|
320
|
-
specFavicon: spec.favicon,
|
|
321
|
-
specLogoLink: spec.logo_link,
|
|
322
|
-
spec: JSON.stringify(spec),
|
|
323
|
-
externalSpecsList: externalSpecsList,
|
|
324
|
-
});
|
|
502
|
+
function interpolate(template, variables) {
|
|
503
|
+
return template.replace(/\${(.*?)}/g, (match, p1) => variables[p1.trim()]);
|
|
504
|
+
}
|
|
325
505
|
|
|
326
|
-
|
|
327
|
-
|
|
506
|
+
const docs = await Promise.all(
|
|
507
|
+
(spec.markdown_paths || ['spec.md']).map(_path =>
|
|
508
|
+
fs.readFile(spec.spec_directory + _path, 'utf8')
|
|
509
|
+
)
|
|
510
|
+
);
|
|
328
511
|
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
512
|
+
const features = (({ source, logo }) => ({ source, logo }))(spec);
|
|
513
|
+
if (spec.external_specs && !externalReferences) {
|
|
514
|
+
externalReferences = await fetchExternalSpecs(spec);
|
|
515
|
+
}
|
|
332
516
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
throw e;
|
|
517
|
+
// Find the index of the terms-and-definitions-intro.md file
|
|
518
|
+
const termsIndex = (spec.markdown_paths || ['spec.md']).indexOf('terms-and-definitions-intro.md');
|
|
519
|
+
if (termsIndex !== -1) {
|
|
520
|
+
// Append the HTML string to the content of terms-and-definitions-intro.md. This string is used to create a div that is used to insert an alphabet index, and a div that is used as the starting point of the terminology index. The newlines are essential for the correct rendering of the markdown.
|
|
521
|
+
docs[termsIndex] += '\n\n<div id="terminology-section-start-h7vc6omi2hr2880"></div>\n\n';
|
|
339
522
|
}
|
|
523
|
+
|
|
524
|
+
let doc = docs.join("\n");
|
|
525
|
+
|
|
526
|
+
// `doc` is markdown
|
|
527
|
+
doc = applyReplacers(doc);
|
|
528
|
+
|
|
529
|
+
md[spec.katex ? "enable" : "disable"](katexRules);
|
|
530
|
+
|
|
531
|
+
// `render` is the rendered HTML
|
|
532
|
+
let renderedHtml = md.render(doc);
|
|
533
|
+
|
|
534
|
+
// Apply the fix for broken definition list structures
|
|
535
|
+
renderedHtml = fixDefinitionListStructure(renderedHtml);
|
|
536
|
+
|
|
537
|
+
// Sort definition terms case-insensitively before final rendering
|
|
538
|
+
renderedHtml = sortDefinitionTermsInHtml(renderedHtml);
|
|
539
|
+
|
|
540
|
+
// Process external references to ensure they are inserted as raw HTML, not as JSON string
|
|
541
|
+
const externalReferencesHtml = Array.isArray(externalReferences)
|
|
542
|
+
? externalReferences.join('')
|
|
543
|
+
: (externalReferences || '');
|
|
544
|
+
|
|
545
|
+
const templateInterpolated = interpolate(template, {
|
|
546
|
+
title: spec.title,
|
|
547
|
+
description: spec.description,
|
|
548
|
+
author: spec.author,
|
|
549
|
+
toc: toc,
|
|
550
|
+
render: renderedHtml,
|
|
551
|
+
assetsHead: assets.head,
|
|
552
|
+
assetsBody: assets.body,
|
|
553
|
+
assetsSvg: assets.svg,
|
|
554
|
+
features: Object.keys(features).join(' '),
|
|
555
|
+
externalReferences: externalReferencesHtml,
|
|
556
|
+
xtrefsData: xtrefsData,
|
|
557
|
+
specLogo: spec.logo,
|
|
558
|
+
specFavicon: spec.favicon,
|
|
559
|
+
specLogoLink: spec.logo_link,
|
|
560
|
+
spec: JSON.stringify(spec),
|
|
561
|
+
externalSpecsList: externalSpecsList,
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const outputPath = path.join(spec.destination, 'index.html');
|
|
565
|
+
console.log('ℹ️ Attempting to write to:', outputPath);
|
|
566
|
+
|
|
567
|
+
// Use promisified version instead of callback
|
|
568
|
+
await fs.promises.writeFile(outputPath, templateInterpolated, 'utf8');
|
|
569
|
+
console.log(`✅ Successfully wrote ${outputPath}`);
|
|
570
|
+
|
|
571
|
+
validateReferences(references, definitions, renderedHtml);
|
|
572
|
+
references = [];
|
|
573
|
+
definitions = [];
|
|
574
|
+
} catch (e) {
|
|
575
|
+
console.error("❌ Render error: " + e.message);
|
|
576
|
+
throw e;
|
|
340
577
|
}
|
|
578
|
+
}
|
|
341
579
|
|
|
580
|
+
try {
|
|
342
581
|
config.specs.forEach(spec => {
|
|
343
582
|
spec.spec_directory = normalizePath(spec.spec_directory);
|
|
344
583
|
spec.destination = normalizePath(spec.output_path || spec.spec_directory);
|
|
345
|
-
|
|
584
|
+
|
|
346
585
|
if (!fs.existsSync(spec.destination)) {
|
|
347
586
|
try {
|
|
348
587
|
fs.mkdirSync(spec.destination, { recursive: true });
|
|
@@ -423,7 +662,7 @@ module.exports = async function (options = {}) {
|
|
|
423
662
|
console.error('❌ Render failed:', e.message);
|
|
424
663
|
process.exit(1);
|
|
425
664
|
});
|
|
426
|
-
|
|
665
|
+
|
|
427
666
|
if (!options.nowatch) {
|
|
428
667
|
gulp.watch(
|
|
429
668
|
[spec.spec_directory + '**/*', '!' + path.join(spec.destination, 'index.html')],
|