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.
Files changed (45) 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 -24
  28. package/index.js +429 -190
  29. package/index.new.js +662 -0
  30. package/package.json +1 -2
  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/term-references-checker.js +3 -2
  37. package/src/health-check/tref-term-checker.js +18 -17
  38. package/src/markdown-it-extensions.js +134 -103
  39. package/src/prepare-tref.js +61 -24
  40. package/src/utils/fetch.js +100 -0
  41. package/templates/template.html +12 -7
  42. package/assets/js/css-helper.js +0 -30
  43. package/src/collectExternalReferences/fetchTermsFromGitHubRepository.js +0 -232
  44. package/src/collectExternalReferences/fetchTermsFromGitHubRepository.test.js +0 -385
  45. 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 `${originalMatch}\n: <span class="placeholder-tref">No custom content found for ${spec}:${term}</span> { .placeholder-dd }\n`;
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
- try {
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
- 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>`;
193
- }
194
- }
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);
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
- .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>`;
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
- .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()]);
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
- const docs = await Promise.all(
280
- (spec.markdown_paths || ['spec.md']).map(_path =>
281
- fs.readFile(spec.spec_directory + _path, 'utf8')
282
- )
283
- );
488
+ // Move to the next node we saved earlier
489
+ currentNode = nextNode;
490
+ }
284
491
 
285
- const features = (({ source, logo }) => ({ source, logo }))(spec);
286
- if (spec.external_specs && !externalReferences) {
287
- externalReferences = await fetchExternalSpecs(spec);
288
- }
492
+ // Return the fixed HTML
493
+ return dom.serialize();
494
+ }
289
495
 
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
- }
496
+ async function render(spec, assets) {
497
+ try {
498
+ noticeTitles = {};
499
+ specGroups = {};
500
+ console.log('ℹ️ Rendering: ' + spec.title);
296
501
 
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
- });
502
+ function interpolate(template, variables) {
503
+ return template.replace(/\${(.*?)}/g, (match, p1) => variables[p1.trim()]);
504
+ }
325
505
 
326
- const outputPath = path.join(spec.destination, 'index.html');
327
- console.log('ℹ️ Attempting to write to:', outputPath);
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
- // Use promisified version instead of callback
330
- await fs.promises.writeFile(outputPath, templateInterpolated, 'utf8');
331
- console.log(`✅ Successfully wrote ${outputPath}`);
512
+ const features = (({ source, logo }) => ({ source, logo }))(spec);
513
+ if (spec.external_specs && !externalReferences) {
514
+ externalReferences = await fetchExternalSpecs(spec);
515
+ }
332
516
 
333
- validateReferences(references, definitions, render);
334
- references = [];
335
- definitions = [];
336
- } catch (e) {
337
- console.error("❌ Render error: " + e.message);
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')],