spec-up-t 1.1.54 → 1.2.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.
Files changed (52) hide show
  1. package/assets/compiled/body.js +59 -8
  2. package/assets/compiled/head.css +13 -12
  3. package/assets/css/adjust-font-size.css +11 -0
  4. package/assets/css/collapse-meta-info.css +27 -8
  5. package/assets/css/create-term-filter.css +11 -0
  6. package/assets/css/index.css +15 -0
  7. package/assets/css/prism.css +176 -153
  8. package/assets/css/prism.dark.css +157 -0
  9. package/assets/css/prism.default.css +180 -0
  10. package/assets/css/terms-and-definitions.1.css +223 -0
  11. package/assets/css/terms-and-definitions.css +214 -100
  12. package/assets/js/addAnchorsToTerms.js +1 -1
  13. package/assets/js/collapse-definitions.js +2 -1
  14. package/assets/js/collapse-meta-info.js +25 -11
  15. package/assets/js/create-term-filter.js +61 -0
  16. package/assets/js/horizontal-scroll-hint.js +159 -0
  17. package/assets/js/index.1.js +137 -0
  18. package/assets/js/index.js +2 -1
  19. package/assets/js/insert-trefs.js +122 -116
  20. package/assets/js/insert-xrefs.1.js +372 -0
  21. package/assets/js/{show-commit-hashes.js → insert-xrefs.js} +67 -7
  22. package/assets/js/prism.dark.js +24 -0
  23. package/assets/js/prism.default.js +23 -0
  24. package/assets/js/prism.js +4 -5
  25. package/assets/js/search.js +1 -1
  26. package/assets/js/toggle-dense-info.js +40 -0
  27. package/branches.md +4 -29
  28. package/index.js +397 -189
  29. package/index.new.js +662 -0
  30. package/package.json +1 -1
  31. package/src/asset-map.json +9 -5
  32. package/src/collect-external-references.js +16 -9
  33. package/src/collectExternalReferences/fetchTermsFromIndex.js +328 -0
  34. package/src/collectExternalReferences/processXTrefsData.js +73 -18
  35. package/src/create-pdf.js +385 -89
  36. package/src/health-check/external-specs-checker.js +207 -0
  37. package/src/health-check/output-gitignore-checker.js +261 -0
  38. package/src/health-check/specs-configuration-checker.js +274 -0
  39. package/src/health-check/term-references-checker.js +191 -0
  40. package/src/health-check/terms-intro-checker.js +81 -0
  41. package/src/health-check/tref-term-checker.js +463 -0
  42. package/src/health-check.js +445 -0
  43. package/src/install-from-boilerplate/config-scripts-keys.js +2 -0
  44. package/src/install-from-boilerplate/menu.sh +6 -3
  45. package/src/markdown-it-extensions.js +134 -103
  46. package/src/prepare-tref.js +61 -24
  47. package/src/utils/fetch.js +100 -0
  48. package/templates/template.html +12 -7
  49. package/assets/js/css-helper.js +0 -30
  50. package/src/collectExternalReferences/fetchTermsFromGitHubRepository.js +0 -232
  51. package/src/collectExternalReferences/fetchTermsFromGitHubRepository.test.js +0 -385
  52. 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,26 @@ module.exports = async function (options = {}) {
58
189
  return fs.readFileSync(path, 'utf8');
59
190
  }
60
191
  },
192
+ /**
193
+ * Custom replacer for tref tags that converts them directly to HTML definition term elements.
194
+ *
195
+ * This is a critical part of our solution for fixing transcluded terms in definition lists.
196
+ * When a [[tref:spec,term]] tag is found in the markdown, this replacer transforms it into
197
+ * a proper <dt> element with the appropriate structure before the markdown parser processes it.
198
+ *
199
+ * By directly generating the HTML structure (instead of letting the markdown-it parser
200
+ * handle it later), we prevent the issue where transcluded terms break the definition list.
201
+ *
202
+ * @param {string} originalMatch - The original [[tref:spec,term]] tag found in the markdown
203
+ * @param {string} type - The tag type ('tref')
204
+ * @param {string} spec - The specification identifier (e.g., 'wot-1')
205
+ * @param {string} term - The term to transclude (e.g., 'DAR')
206
+ * @returns {string} - HTML representation of the term as a dt element
207
+ */
61
208
  {
62
209
  test: 'tref',
63
210
  transform: function (originalMatch, type, spec, term) {
64
- return `${originalMatch}\n: <span class="placeholder-tref">No custom content found for ${spec}:${term}</span> { .placeholder-dd }\n`;
211
+ return `<dt class="transcluded-xref-term"><span class="transcluded-xref-term" id="term:${term.replace(/\s+/g, '-').toLowerCase()}">${term}</span></dt>`;
65
212
  }
66
213
  }
67
214
  ];
@@ -85,6 +232,19 @@ module.exports = async function (options = {}) {
85
232
 
86
233
  const xtrefsData = createScriptElementWithXTrefDataForEmbeddingInHtml();
87
234
 
235
+ /**
236
+ * Processes custom tag patterns in markdown content and applies transformation functions.
237
+ *
238
+ * This function scans the document for special tag patterns like [[tref:spec,term]]
239
+ * and replaces them with the appropriate HTML using the matching replacer.
240
+ *
241
+ * For tref tags, this is where the magic happens - we intercept them before
242
+ * the markdown parser even sees them, and convert them directly to HTML structure
243
+ * that will integrate properly with definition lists.
244
+ *
245
+ * @param {string} doc - The markdown document to process
246
+ * @returns {string} - The processed document with tags replaced by their HTML equivalents
247
+ */
88
248
  function applyReplacers(doc) {
89
249
  return doc.replace(replacerRegex, function (match, type, args) {
90
250
  let replacer = replacers.find(r => type.trim().match(r.test));
@@ -95,6 +255,7 @@ module.exports = async function (options = {}) {
95
255
  return match;
96
256
  });
97
257
  }
258
+
98
259
  function normalizePath(path) {
99
260
  return path.trim().replace(/\/$/g, '') + '/';
100
261
  }
@@ -137,212 +298,259 @@ module.exports = async function (options = {}) {
137
298
  throw Error("katex distribution could not be located");
138
299
  }
139
300
 
140
- try {
301
+ function sortDefinitionTermsInHtml(html) {
302
+ const { JSDOM } = require('jsdom');
303
+ const dom = new JSDOM(html);
304
+ const document = dom.window.document;
305
+
306
+ // Find the terms and definitions list
307
+ const dlElement = document.querySelector('.terms-and-definitions-list');
308
+ if (!dlElement) return html; // If not found, return the original HTML
309
+
310
+ // Collect all dt/dd pairs
311
+ const pairs = [];
312
+ let currentDt = null;
313
+ let currentDds = [];
314
+
315
+ // Process each child of the dl element
316
+ Array.from(dlElement.children).forEach(child => {
317
+ if (child.tagName === 'DT') {
318
+ // If we already have a dt, save the current pair
319
+ if (currentDt) {
320
+ pairs.push({
321
+ dt: currentDt,
322
+ dds: [...currentDds],
323
+ text: currentDt.textContent.trim().toLowerCase() // Use lowercase for sorting
324
+ });
325
+ currentDds = []; // Reset dds for the next dt
326
+ }
327
+ currentDt = child;
328
+ } else if (child.tagName === 'DD' && currentDt) {
329
+ currentDds.push(child);
330
+ }
331
+ });
141
332
 
142
- var toc;
143
- var specGroups = {};
144
- const noticeTypes = {
145
- note: 1,
146
- issue: 1,
147
- example: 1,
148
- warning: 1,
149
- todo: 1
150
- };
151
- const spaceRegex = /\s+/g;
152
- const specNameRegex = /^spec$|^spec[-]*\w+$/i;
153
- const terminologyRegex = /^def$|^ref$|^xref|^tref$/i;
154
- const specCorpus = fs.readJsonSync(modulePath + '/assets/compiled/refs.json');
155
- const containers = require('markdown-it-container');
156
-
157
- /*
158
- `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.
159
-
160
- 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)`
161
- */
162
- const md = require('markdown-it')({
163
- html: true,
164
- linkify: true,
165
- typographer: true
166
- })
167
- .use(require('./src/markdown-it-extensions.js'), [
168
- {
169
- filter: type => type.match(terminologyRegex),
170
- parse(token, type, primary) {
171
- if (!primary) return;
172
- if (type === 'def') {
173
- definitions.push(token.info.args);
174
- return token.info.args.reduce((acc, syn) => {
175
- return `<span id="term:${syn.replace(spaceRegex, '-').toLowerCase()}">${acc}</span>`;
176
- }, primary);
177
- }
178
- else if (type === 'xref') {
179
- // Get the URL for the external specification reference, or default to '#' if not found
180
- const externalSpec = findExternalSpecByKey(config, token.info.args[0]);
181
- const url = externalSpec?.gh_page || '#';
182
-
183
- const term = token.info.args[1].replace(spaceRegex, '-').toLowerCase();
184
- return `<a class="x-term-reference term-reference" data-local-href="#term:${token.info.args[0]}:${term}"
185
- href="${url}#term:${term}">${token.info.args[1]}</a>`;
186
- }
187
- else if (type === 'tref') {
188
- return `<span class="transcluded-xref-term" id="term:${token.info.args[1]}">${token.info.args[1]}</span>`;
189
- }
190
- else {
191
- references.push(primary);
192
- return `<a class="term-reference" href="#term:${primary.replace(spaceRegex, '-').toLowerCase()}">${primary}</a>`;
333
+ // Add the last pair if exists
334
+ if (currentDt) {
335
+ pairs.push({
336
+ dt: currentDt,
337
+ dds: [...currentDds],
338
+ text: currentDt.textContent.trim().toLowerCase()
339
+ });
340
+ }
341
+
342
+ // Sort pairs case-insensitively
343
+ pairs.sort((a, b) => a.text.localeCompare(b.text));
344
+
345
+ // Clear the dl element
346
+ while (dlElement.firstChild) {
347
+ dlElement.removeChild(dlElement.firstChild);
348
+ }
349
+
350
+ // Re-append elements in sorted order
351
+ pairs.forEach(pair => {
352
+ dlElement.appendChild(pair.dt);
353
+ pair.dds.forEach(dd => {
354
+ dlElement.appendChild(dd);
355
+ });
356
+ });
357
+
358
+ // Return the modified HTML
359
+ return dom.serialize();
360
+ }
361
+
362
+ // Function to fix broken definition list structures
363
+ /**
364
+ * This function repairs broken definition list (dl) structures in the HTML output.
365
+ * Specifically, it addresses the issue where transcluded terms (tref tags) break
366
+ * out of the definition list, creating separate lists instead of a continuous one.
367
+ *
368
+ * The strategy:
369
+ * 1. Find all definition lists (dl elements) in the document
370
+ * 2. Use the dl with class 'terms-and-definitions-list' as the main/target list
371
+ * 3. Process each subsequent node after the this main dl:
372
+ * - If another dl is found, merge all its children into the main dl
373
+ * - If a standalone dt is found, move it into the main dl
374
+ * - Remove any empty paragraphs that might be breaking the list continuity
375
+ *
376
+ * This ensures all terms appear in one continuous definition list,
377
+ * regardless of how they were originally rendered in the markdown.
378
+ *
379
+ * @param {string} html - The HTML content to fix
380
+ * @returns {string} - The fixed HTML content with merged definition lists
381
+ */
382
+ function fixDefinitionListStructure(html) {
383
+ const { JSDOM } = require('jsdom');
384
+ const dom = new JSDOM(html);
385
+ const document = dom.window.document;
386
+
387
+ // Find all dl elements first
388
+ const allDls = Array.from(document.querySelectorAll('dl'));
389
+
390
+ // Then filter to find the one with the terms-and-definitions-list class
391
+ const dlElements = allDls.filter(dl => {
392
+ return dl.classList && dl.classList.contains('terms-and-definitions-list');
393
+ });
394
+
395
+ // First special case - handle transcluded-xref-term dt that comes BEFORE the main dl
396
+ const transcludedTermsBeforeMainDl = document.querySelectorAll('dt.transcluded-xref-term');
397
+ let mainDl = null;
398
+
399
+ if (dlElements.length > 0) {
400
+ // Use the first terms-and-definitions-list as our main container
401
+ mainDl = dlElements[0];
402
+
403
+ // Special handling for transcluded terms that appear BEFORE the main dl
404
+ transcludedTermsBeforeMainDl.forEach(dt => {
405
+ // Check if this dt is not already inside a dl.terms-and-definitions-list
406
+ if (!dt.parentElement.classList.contains('terms-and-definitions-list')) {
407
+ // This is a dt outside our main list - move it into the main dl at the beginning
408
+ const dtClone = dt.cloneNode(true);
409
+ mainDl.insertBefore(dtClone, mainDl.firstChild);
410
+ dt.parentNode.removeChild(dt);
411
+ }
412
+ });
413
+
414
+ // Remove any empty dt elements that may exist
415
+ const emptyDts = mainDl.querySelectorAll('dt:empty');
416
+ emptyDts.forEach(emptyDt => {
417
+ emptyDt.parentNode.removeChild(emptyDt);
418
+ });
419
+
420
+ // Process all subsequent content after the main dl
421
+ let currentNode = mainDl.nextSibling;
422
+
423
+ // Process all subsequent content
424
+ while (currentNode) {
425
+ // Save the next node before potentially modifying the DOM
426
+ // (This is important because modifying the DOM can invalidate our references)
427
+ const nextNode = currentNode.nextSibling;
428
+
429
+ // Handle different node types
430
+ if (currentNode.nodeType === 1) { // 1 = Element node
431
+ if (currentNode.tagName === 'DL') {
432
+ // Found another definition list - move all its children to the main dl
433
+ // This effectively merges the two lists into one
434
+ while (currentNode.firstChild) {
435
+ mainDl.appendChild(currentNode.firstChild);
193
436
  }
437
+
438
+ // Remove the now-empty dl element
439
+ currentNode.parentNode.removeChild(currentNode);
194
440
  }
195
- },
196
- {
197
- filter: type => type.match(specNameRegex),
198
- parse(token, type, name) {
199
- if (name) {
200
- let _name = name.replace(spaceRegex, '-').toUpperCase();
201
- let spec = specCorpus[_name] ||
202
- specCorpus[_name.toLowerCase()] ||
203
- specCorpus[name.toLowerCase()] ||
204
- specCorpus[name];
205
- if (spec) {
206
- spec._name = _name;
207
- let group = specGroups[type] = specGroups[type] || {};
208
- token.info.spec = group[_name] = spec;
209
- }
210
- }
211
- },
212
- render(token, type, name) {
213
- if (name) {
214
- let spec = token.info.spec;
215
- if (spec) return `[<a class="spec-reference" href="#ref:${spec._name}">${spec._name}</a>]`;
216
- }
217
- else return renderRefGroup(type);
441
+ else if (currentNode.tagName === 'DT') {
442
+ // Found a standalone dt (like our transcluded tref terms)
443
+ // Move it into the main dl to maintain continuity
444
+ const dtClone = currentNode.cloneNode(true);
445
+ mainDl.appendChild(dtClone);
446
+ currentNode.parentNode.removeChild(currentNode);
218
447
  }
219
- }
220
- ])
221
- .use(require('markdown-it-attrs'))
222
- .use(require('markdown-it-chart').default)
223
- .use(require('markdown-it-deflist'))
224
- .use(require('markdown-it-references'))
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>`;
448
+ else if (currentNode.tagName === 'P' &&
449
+ (!currentNode.textContent || currentNode.textContent.trim() === '')) {
450
+ // Remove empty paragraphs - these break the list structure
451
+ // Empty <p></p> tags often appear between dl elements
452
+ currentNode.parentNode.removeChild(currentNode);
254
453
  }
255
- else return '</div>\n';
256
- }
257
- })
258
- .use(require('markdown-it-prism'))
259
- .use(require('markdown-it-toc-and-anchor').default, {
260
- tocClassName: 'toc',
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()]);
277
454
  }
278
455
 
279
- const docs = await Promise.all(
280
- (spec.markdown_paths || ['spec.md']).map(_path =>
281
- fs.readFile(spec.spec_directory + _path, 'utf8')
282
- )
283
- );
456
+ // Move to the next node we saved earlier
457
+ currentNode = nextNode;
458
+ }
459
+ }
284
460
 
285
- const features = (({ source, logo }) => ({ source, logo }))(spec);
286
- if (spec.external_specs && !externalReferences) {
287
- externalReferences = await fetchExternalSpecs(spec);
288
- }
461
+ // Return the fixed HTML
462
+ return dom.serialize();
463
+ }
289
464
 
290
- // Find the index of the terms-and-definitions-intro.md file
291
- const termsIndex = (spec.markdown_paths || ['spec.md']).indexOf('terms-and-definitions-intro.md');
292
- if (termsIndex !== -1) {
293
- // 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.
294
- docs[termsIndex] += '\n\n<div id="terminology-section-start-h7vc6omi2hr2880"></div>\n\n';
295
- }
465
+ async function render(spec, assets) {
466
+ try {
467
+ noticeTitles = {};
468
+ specGroups = {};
469
+ console.log('ℹ️ Rendering: ' + spec.title);
296
470
 
297
- let doc = docs.join("\n");
298
-
299
- // `doc` is markdown
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
- });
471
+ function interpolate(template, variables) {
472
+ return template.replace(/\${(.*?)}/g, (match, p1) => variables[p1.trim()]);
473
+ }
325
474
 
326
- const outputPath = path.join(spec.destination, 'index.html');
327
- console.log('ℹ️ Attempting to write to:', outputPath);
475
+ const docs = await Promise.all(
476
+ (spec.markdown_paths || ['spec.md']).map(_path =>
477
+ fs.readFile(spec.spec_directory + _path, 'utf8')
478
+ )
479
+ );
328
480
 
329
- // Use promisified version instead of callback
330
- await fs.promises.writeFile(outputPath, templateInterpolated, 'utf8');
331
- console.log(`✅ Successfully wrote ${outputPath}`);
481
+ const features = (({ source, logo }) => ({ source, logo }))(spec);
482
+ if (spec.external_specs && !externalReferences) {
483
+ externalReferences = await fetchExternalSpecs(spec);
484
+ }
332
485
 
333
- validateReferences(references, definitions, render);
334
- references = [];
335
- definitions = [];
336
- } catch (e) {
337
- console.error("❌ Render error: " + e.message);
338
- throw e;
486
+ // Find the index of the terms-and-definitions-intro.md file
487
+ const termsIndex = (spec.markdown_paths || ['spec.md']).indexOf('terms-and-definitions-intro.md');
488
+ if (termsIndex !== -1) {
489
+ // 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.
490
+ docs[termsIndex] += '\n\n<div id="terminology-section-start-h7vc6omi2hr2880"></div>\n\n';
339
491
  }
492
+
493
+ let doc = docs.join("\n");
494
+
495
+ // `doc` is markdown
496
+ doc = applyReplacers(doc);
497
+
498
+ md[spec.katex ? "enable" : "disable"](katexRules);
499
+
500
+ // `render` is the rendered HTML
501
+ let renderedHtml = md.render(doc);
502
+
503
+ // Apply the fix for broken definition list structures
504
+ renderedHtml = fixDefinitionListStructure(renderedHtml);
505
+
506
+ // Sort definition terms case-insensitively before final rendering
507
+ renderedHtml = sortDefinitionTermsInHtml(renderedHtml);
508
+
509
+ // Process external references to ensure they are inserted as raw HTML, not as JSON string
510
+ const externalReferencesHtml = Array.isArray(externalReferences)
511
+ ? externalReferences.join('')
512
+ : (externalReferences || '');
513
+
514
+ const templateInterpolated = interpolate(template, {
515
+ title: spec.title,
516
+ description: spec.description,
517
+ author: spec.author,
518
+ toc: toc,
519
+ render: renderedHtml,
520
+ assetsHead: assets.head,
521
+ assetsBody: assets.body,
522
+ assetsSvg: assets.svg,
523
+ features: Object.keys(features).join(' '),
524
+ externalReferences: externalReferencesHtml,
525
+ xtrefsData: xtrefsData,
526
+ specLogo: spec.logo,
527
+ specFavicon: spec.favicon,
528
+ specLogoLink: spec.logo_link,
529
+ spec: JSON.stringify(spec),
530
+ externalSpecsList: externalSpecsList,
531
+ });
532
+
533
+ const outputPath = path.join(spec.destination, 'index.html');
534
+ console.log('ℹ️ Attempting to write to:', outputPath);
535
+
536
+ // Use promisified version instead of callback
537
+ await fs.promises.writeFile(outputPath, templateInterpolated, 'utf8');
538
+ console.log(`✅ Successfully wrote ${outputPath}`);
539
+
540
+ validateReferences(references, definitions, renderedHtml);
541
+ references = [];
542
+ definitions = [];
543
+ } catch (e) {
544
+ console.error("❌ Render error: " + e.message);
545
+ throw e;
340
546
  }
547
+ }
341
548
 
549
+ try {
342
550
  config.specs.forEach(spec => {
343
551
  spec.spec_directory = normalizePath(spec.spec_directory);
344
552
  spec.destination = normalizePath(spec.output_path || spec.spec_directory);
345
-
553
+
346
554
  if (!fs.existsSync(spec.destination)) {
347
555
  try {
348
556
  fs.mkdirSync(spec.destination, { recursive: true });
@@ -423,7 +631,7 @@ module.exports = async function (options = {}) {
423
631
  console.error('❌ Render failed:', e.message);
424
632
  process.exit(1);
425
633
  });
426
-
634
+
427
635
  if (!options.nowatch) {
428
636
  gulp.watch(
429
637
  [spec.spec_directory + '**/*', '!' + path.join(spec.destination, 'index.html')],