spec-up-t 1.2.4 → 1.2.6
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 +11 -8
- package/assets/compiled/head.css +7 -4
- package/assets/css/add-bootstrap-classes-to-images.css +34 -0
- package/assets/css/collapse-definitions.css +41 -0
- package/assets/css/create-pdf.css +339 -0
- package/assets/css/horizontal-scroll-hint.css +6 -0
- package/assets/css/image-full-size.css +40 -0
- package/assets/css/index.css +5 -0
- package/assets/css/search.css +8 -8
- package/assets/css/terms-and-definitions.css +3 -3
- package/assets/js/add-bootstrap-classes-to-images.js +95 -0
- package/assets/js/collapse-definitions.js +259 -34
- package/assets/js/collapse-meta-info.js +37 -10
- package/assets/js/collapsibleMenu.js +0 -24
- package/assets/js/create-alphabet-index.js +11 -2
- package/assets/js/create-term-filter.js +47 -17
- package/assets/js/hide-show-utility-container.js +16 -0
- package/assets/js/horizontal-scroll-hint.js +2 -2
- package/assets/js/image-full-size.js +74 -0
- package/assets/js/insert-trefs.js +135 -49
- package/assets/js/search.js +46 -20
- package/{src → config}/asset-map.json +6 -0
- package/{src/config → config}/paths.js +2 -2
- package/gulpfile.js +1 -1
- package/index.js +5 -5
- package/package.json +1 -1
- package/src/README.md +3 -3
- package/src/collect-external-references.js +2 -2
- package/src/collectExternalReferences/fetchTermsFromIndex.1.js +340 -0
- package/src/collectExternalReferences/fetchTermsFromIndex.js +1 -1
- package/src/collectExternalReferences/processXTrefsData.js +1 -1
- package/src/create-pdf.js +330 -21
- package/src/create-term-index.js +126 -22
- package/src/health-check/tref-term-checker.js +1 -1
- package/src/health-check.js +1 -1
- package/src/init.js +1 -1
- package/src/insert-term-index.js +5 -5
- package/src/install-from-boilerplate/add-scripts-keys.js +3 -1
- package/src/install-from-boilerplate/boilerplate/gitignore +1 -1
- package/src/markdown-it-extensions.js +2 -2
- package/src/utils/fetch.js +14 -14
- package/assets/css/pdf-styles.css +0 -170
package/src/create-pdf.js
CHANGED
|
@@ -29,7 +29,7 @@ const pdfLib = require('pdf-lib');
|
|
|
29
29
|
let bootstrapCss = bootstrapExists ? fs.readFileSync(bootstrapCssPath, 'utf8') : '';
|
|
30
30
|
|
|
31
31
|
// Path to PDF styles CSS
|
|
32
|
-
const pdfStylesPath = path.resolve(process.cwd(), 'assets/css/pdf
|
|
32
|
+
const pdfStylesPath = path.resolve(process.cwd(), 'assets/css/create-pdf.css');
|
|
33
33
|
const pdfStylesExist = fs.existsSync(pdfStylesPath);
|
|
34
34
|
const pdfStylesCss = pdfStylesExist ? fs.readFileSync(pdfStylesPath, 'utf8') : '';
|
|
35
35
|
|
|
@@ -38,8 +38,14 @@ const pdfLib = require('pdf-lib');
|
|
|
38
38
|
|
|
39
39
|
// Clean up unnecessary elements but be careful not to remove styles we need
|
|
40
40
|
await page.evaluate(() => {
|
|
41
|
-
|
|
41
|
+
// Preserve TOC if it exists (skip removing it even if it has display:none)
|
|
42
|
+
document.querySelectorAll('[style*="display: none"]:not(#toc):not(#toc *), .d-print-none:not(#toc):not(#toc *), script').forEach(element => element.remove());
|
|
42
43
|
// Don't remove all style elements as some might be important
|
|
44
|
+
|
|
45
|
+
// If TOC doesn't exist, we'll need to create one
|
|
46
|
+
if (!document.getElementById('toc')) {
|
|
47
|
+
console.log('No TOC found in the document. Will create one.');
|
|
48
|
+
}
|
|
43
49
|
});
|
|
44
50
|
|
|
45
51
|
// Handle dynamically fetched cross-reference terms
|
|
@@ -68,7 +74,7 @@ const pdfLib = require('pdf-lib');
|
|
|
68
74
|
container.style.margin = '0 auto';
|
|
69
75
|
container.style.padding = '0';
|
|
70
76
|
});
|
|
71
|
-
|
|
77
|
+
|
|
72
78
|
// Override any Bootstrap column constraints
|
|
73
79
|
const columns = document.querySelectorAll('[class*="col-"]');
|
|
74
80
|
columns.forEach(col => {
|
|
@@ -77,7 +83,7 @@ const pdfLib = require('pdf-lib');
|
|
|
77
83
|
col.style.paddingLeft = '0';
|
|
78
84
|
col.style.paddingRight = '0';
|
|
79
85
|
});
|
|
80
|
-
|
|
86
|
+
|
|
81
87
|
// Ensure body takes full width
|
|
82
88
|
document.body.style.maxWidth = '100%';
|
|
83
89
|
document.body.style.width = '100%';
|
|
@@ -113,18 +119,18 @@ const pdfLib = require('pdf-lib');
|
|
|
113
119
|
document.querySelectorAll('table').forEach(table => {
|
|
114
120
|
table.classList.add('table', 'table-bordered', 'table-striped', 'table-hover');
|
|
115
121
|
});
|
|
116
|
-
|
|
122
|
+
|
|
117
123
|
// Make sure meta-info is visible and toggle buttons are hidden
|
|
118
124
|
document.querySelectorAll('.meta-info, .term-meta').forEach(meta => {
|
|
119
125
|
meta.style.display = 'block';
|
|
120
126
|
});
|
|
121
|
-
|
|
127
|
+
|
|
122
128
|
document.querySelectorAll('.meta-toggle, button.meta-toggle').forEach(button => {
|
|
123
129
|
button.style.display = 'none';
|
|
124
130
|
});
|
|
125
131
|
});
|
|
126
132
|
|
|
127
|
-
// Inject logo, title, and description
|
|
133
|
+
// Inject logo, title, and description AND handle TOC creation if needed
|
|
128
134
|
await page.evaluate((logo, logoLink, title, description) => {
|
|
129
135
|
const titleWrapper = document.createElement('div');
|
|
130
136
|
titleWrapper.className = 'text-center mb-5 pb-4 border-bottom';
|
|
@@ -155,9 +161,88 @@ const pdfLib = require('pdf-lib');
|
|
|
155
161
|
}
|
|
156
162
|
|
|
157
163
|
document.body.insertBefore(titleWrapper, document.body.firstChild);
|
|
164
|
+
|
|
165
|
+
// Create a Table of Contents if it doesn't exist
|
|
166
|
+
if (!document.getElementById('toc')) {
|
|
167
|
+
// Generate a TOC based on the headings in the document
|
|
168
|
+
const headings = Array.from(document.querySelectorAll('h1:not(.pdf-title), h2, h3, h4, h5, h6')).filter(h => {
|
|
169
|
+
// Only include headings with IDs that can be linked to
|
|
170
|
+
return h.id && h.id.trim() !== '';
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
if (headings.length > 0) {
|
|
174
|
+
// Create TOC container
|
|
175
|
+
const tocContainer = document.createElement('div');
|
|
176
|
+
tocContainer.id = 'toc';
|
|
177
|
+
tocContainer.className = 'toc-container';
|
|
178
|
+
|
|
179
|
+
// Create TOC heading
|
|
180
|
+
const tocHeading = document.createElement('h2');
|
|
181
|
+
tocHeading.textContent = 'Contents';
|
|
182
|
+
tocHeading.className = 'toc-heading';
|
|
183
|
+
tocContainer.appendChild(tocHeading);
|
|
184
|
+
|
|
185
|
+
// Create TOC list
|
|
186
|
+
const tocList = document.createElement('ul');
|
|
187
|
+
tocList.className = 'toc-list';
|
|
188
|
+
tocContainer.appendChild(tocList);
|
|
189
|
+
|
|
190
|
+
// Add all headings to the TOC
|
|
191
|
+
let currentLevel = 0;
|
|
192
|
+
let currentList = tocList;
|
|
193
|
+
let listStack = [tocList];
|
|
194
|
+
|
|
195
|
+
headings.forEach(heading => {
|
|
196
|
+
// Get heading level (1-6 for h1-h6)
|
|
197
|
+
const level = parseInt(heading.tagName[1]);
|
|
198
|
+
|
|
199
|
+
// Navigate to the correct nesting level
|
|
200
|
+
if (level > currentLevel) {
|
|
201
|
+
// Go deeper - create a new nested list
|
|
202
|
+
for (let i = currentLevel; i < level; i++) {
|
|
203
|
+
if (currentList.lastChild) {
|
|
204
|
+
const nestedList = document.createElement('ul');
|
|
205
|
+
currentList.lastChild.appendChild(nestedList);
|
|
206
|
+
listStack.push(nestedList);
|
|
207
|
+
currentList = nestedList;
|
|
208
|
+
} else {
|
|
209
|
+
// If no items exist yet, add a dummy item
|
|
210
|
+
const dummyItem = document.createElement('li');
|
|
211
|
+
const nestedList = document.createElement('ul');
|
|
212
|
+
dummyItem.appendChild(nestedList);
|
|
213
|
+
currentList.appendChild(dummyItem);
|
|
214
|
+
listStack.push(nestedList);
|
|
215
|
+
currentList = nestedList;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
} else if (level < currentLevel) {
|
|
219
|
+
// Go up - pop from the stack
|
|
220
|
+
for (let i = currentLevel; i > level; i--) {
|
|
221
|
+
listStack.pop();
|
|
222
|
+
}
|
|
223
|
+
currentList = listStack[listStack.length - 1];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
currentLevel = level;
|
|
227
|
+
|
|
228
|
+
// Create list item with link
|
|
229
|
+
const listItem = document.createElement('li');
|
|
230
|
+
const link = document.createElement('a');
|
|
231
|
+
link.href = '#' + heading.id;
|
|
232
|
+
link.textContent = heading.textContent;
|
|
233
|
+
listItem.appendChild(link);
|
|
234
|
+
currentList.appendChild(listItem);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
// Insert the TOC after the title section
|
|
238
|
+
document.body.insertBefore(tocContainer, titleWrapper.nextSibling);
|
|
239
|
+
|
|
240
|
+
console.log('Generated a Table of Contents with ' + headings.length + ' entries.');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
158
243
|
}, logo, logoLink, title, description);
|
|
159
|
-
|
|
160
|
-
// Direct manipulation of definition lists to ensure proper styling in PDF
|
|
244
|
+
|
|
245
|
+
// Direct manipulation of definition lists and TOC to ensure proper styling in PDF
|
|
161
246
|
await page.evaluate(() => {
|
|
162
247
|
// Process all definition lists
|
|
163
248
|
const definitionLists = document.querySelectorAll('dl.terms-and-definitions-list');
|
|
@@ -168,7 +253,7 @@ const pdfLib = require('pdf-lib');
|
|
|
168
253
|
// Remove background and borders with !important to override any existing styles
|
|
169
254
|
term.setAttribute('style', term.getAttribute('style') + '; background: transparent !important; background-color: transparent !important; background-image: none !important; border: none !important; border-radius: 0 !important; padding: 0.5rem 0 !important;');
|
|
170
255
|
});
|
|
171
|
-
|
|
256
|
+
|
|
172
257
|
// Ensure all meta-info content is visible
|
|
173
258
|
const metaInfoContents = list.querySelectorAll('dd.meta-info-content-wrapper');
|
|
174
259
|
metaInfoContents.forEach(content => {
|
|
@@ -179,42 +264,266 @@ const pdfLib = require('pdf-lib');
|
|
|
179
264
|
content.style.padding = '0.5rem 0';
|
|
180
265
|
content.style.margin = '0';
|
|
181
266
|
content.style.lineHeight = 'normal';
|
|
182
|
-
|
|
267
|
+
|
|
183
268
|
// Remove the collapsed class if present
|
|
184
269
|
content.classList.remove('collapsed');
|
|
185
270
|
});
|
|
186
|
-
|
|
271
|
+
|
|
187
272
|
// Hide all meta-info toggle buttons
|
|
188
273
|
const toggleButtons = list.querySelectorAll('.meta-info-toggle-button');
|
|
189
274
|
toggleButtons.forEach(button => {
|
|
190
275
|
button.style.display = 'none';
|
|
191
276
|
});
|
|
192
277
|
});
|
|
193
|
-
|
|
278
|
+
|
|
194
279
|
// Special handling for ALL transcluded terms with blue background - no class restrictions
|
|
195
280
|
document.querySelectorAll('.transcluded-xref-term').forEach(el => {
|
|
196
281
|
// Use the most aggressive approach possible to override the blue background
|
|
197
282
|
el.setAttribute('style', el.getAttribute('style') + '; background: transparent !important; background-color: transparent !important; background-image: none !important;');
|
|
198
|
-
|
|
283
|
+
|
|
199
284
|
// Also process any child elements to ensure complete removal of background
|
|
200
285
|
Array.from(el.children).forEach(child => {
|
|
201
286
|
child.setAttribute('style', child.getAttribute('style') + '; background: transparent !important; background-color: transparent !important; background-image: none !important;');
|
|
202
287
|
});
|
|
203
288
|
});
|
|
204
|
-
|
|
289
|
+
|
|
205
290
|
// Remove any inline styles that might be setting backgrounds
|
|
206
291
|
document.querySelectorAll('style').forEach(styleTag => {
|
|
207
292
|
let cssText = styleTag.textContent;
|
|
208
293
|
// If the style tag contains transcluded-xref-term styles, modify them
|
|
209
294
|
if (cssText.includes('transcluded-xref-term') && cssText.includes('background')) {
|
|
210
|
-
cssText = cssText.replace(/dt\.transcluded-xref-term[^}]+}/g,
|
|
211
|
-
|
|
295
|
+
cssText = cssText.replace(/dt\.transcluded-xref-term[^}]+}/g,
|
|
296
|
+
'dt.transcluded-xref-term, dd.transcluded-xref-term { background: transparent !important; background-color: transparent !important; background-image: none !important; }');
|
|
212
297
|
styleTag.textContent = cssText;
|
|
213
298
|
}
|
|
214
|
-
});
|
|
299
|
+
}); // Format Table of Contents for book-like layout
|
|
300
|
+
const toc = document.getElementById('toc');
|
|
301
|
+
if (toc) {
|
|
302
|
+
// Make sure TOC is visible
|
|
303
|
+
toc.style.display = 'block';
|
|
304
|
+
toc.style.visibility = 'visible';
|
|
305
|
+
toc.style.opacity = '1';
|
|
306
|
+
|
|
307
|
+
// Create a new TOC div for the PDF using a completely different approach
|
|
308
|
+
const pdfToc = document.createElement('div');
|
|
309
|
+
pdfToc.id = 'pdf-toc';
|
|
310
|
+
|
|
311
|
+
// Ensure TOC has a header
|
|
312
|
+
const tocHeading = document.createElement('h2');
|
|
313
|
+
tocHeading.textContent = 'Contents';
|
|
314
|
+
tocHeading.style.textAlign = 'center';
|
|
315
|
+
tocHeading.style.fontWeight = 'bold';
|
|
316
|
+
tocHeading.style.marginBottom = '1.5rem';
|
|
317
|
+
tocHeading.style.paddingBottom = '0.5rem';
|
|
318
|
+
tocHeading.style.borderBottom = '1px solid #000';
|
|
319
|
+
pdfToc.appendChild(tocHeading);
|
|
320
|
+
|
|
321
|
+
// Create a fresh TOC structure - completely rebuilding it
|
|
322
|
+
const tocList = document.createElement('ul');
|
|
323
|
+
tocList.style.listStyleType = 'none';
|
|
324
|
+
tocList.style.padding = '0';
|
|
325
|
+
tocList.style.margin = '0';
|
|
326
|
+
pdfToc.appendChild(tocList);
|
|
327
|
+
|
|
328
|
+
// Find all section headings to include in the TOC
|
|
329
|
+
// Look for both original TOC entries and also scan document headings
|
|
330
|
+
const tocOriginalLinks = toc.querySelectorAll('a');
|
|
331
|
+
|
|
332
|
+
// Process each TOC link to create a new TOC item
|
|
333
|
+
tocOriginalLinks.forEach((link, index) => {
|
|
334
|
+
const li = document.createElement('li');
|
|
335
|
+
const rowDiv = document.createElement('div');
|
|
336
|
+
rowDiv.className = 'toc-row';
|
|
337
|
+
|
|
338
|
+
const title = document.createElement('a');
|
|
339
|
+
title.href = link.getAttribute('href');
|
|
340
|
+
title.textContent = link.textContent;
|
|
341
|
+
title.className = 'toc-title';
|
|
342
|
+
title.setAttribute('data-target-id', link.getAttribute('href').substring(1)); // Store the target id
|
|
343
|
+
// Ensure no blue color or underline for TOC links
|
|
344
|
+
title.style.color = '#000';
|
|
345
|
+
title.style.textDecoration = 'none';
|
|
346
|
+
title.style.borderBottom = 'none';
|
|
347
|
+
title.style.backgroundColor = 'transparent';
|
|
348
|
+
|
|
349
|
+
const leader = document.createElement('div');
|
|
350
|
+
leader.className = 'toc-leader';
|
|
351
|
+
|
|
352
|
+
// Create page number placeholder - we'll fill in actual page numbers later
|
|
353
|
+
const pageNumber = document.createElement('span');
|
|
354
|
+
pageNumber.className = 'toc-page-number';
|
|
355
|
+
pageNumber.textContent = ''; // Empty for now
|
|
356
|
+
pageNumber.setAttribute('data-for-id', link.getAttribute('href').substring(1));
|
|
357
|
+
pageNumber.style.position = 'absolute';
|
|
358
|
+
pageNumber.style.right = '0';
|
|
359
|
+
|
|
360
|
+
rowDiv.appendChild(title);
|
|
361
|
+
rowDiv.appendChild(leader);
|
|
362
|
+
li.appendChild(rowDiv);
|
|
363
|
+
li.appendChild(pageNumber);
|
|
364
|
+
|
|
365
|
+
// Determine nesting level from original TOC
|
|
366
|
+
let level = 0;
|
|
367
|
+
let parent = link.closest('li');
|
|
368
|
+
while (parent) {
|
|
369
|
+
const parentList = parent.closest('ul');
|
|
370
|
+
if (parentList && parentList !== toc) {
|
|
371
|
+
level++;
|
|
372
|
+
parent = parentList.closest('li');
|
|
373
|
+
} else {
|
|
374
|
+
break;
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Apply indentation based on level
|
|
379
|
+
if (level > 0) {
|
|
380
|
+
li.style.paddingLeft = (level * 15) + 'px';
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
tocList.appendChild(li);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
// Insert the new TOC at the beginning of the document after the title
|
|
387
|
+
const titleWrapper = document.querySelector('.text-center.mb-5.pb-4.border-bottom');
|
|
388
|
+
if (titleWrapper && titleWrapper.nextSibling) {
|
|
389
|
+
document.body.insertBefore(pdfToc, titleWrapper.nextSibling);
|
|
390
|
+
} else {
|
|
391
|
+
document.body.insertBefore(pdfToc, document.body.firstChild);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Force page break before TOC
|
|
395
|
+
pdfToc.style.breakBefore = 'page';
|
|
396
|
+
pdfToc.style.pageBreakBefore = 'always';
|
|
397
|
+
|
|
398
|
+
// Force page break after TOC
|
|
399
|
+
const tocNext = pdfToc.nextElementSibling;
|
|
400
|
+
if (tocNext) {
|
|
401
|
+
tocNext.style.breakBefore = 'page';
|
|
402
|
+
tocNext.style.pageBreakBefore = 'always';
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Hide the original TOC
|
|
406
|
+
toc.style.display = 'none';
|
|
407
|
+
}
|
|
408
|
+
});
|
|
409
|
+
|
|
410
|
+
console.log('Generating PDF with proper TOC page numbers...');
|
|
411
|
+
|
|
412
|
+
// First, generate a draft PDF to calculate the page positions of each heading
|
|
413
|
+
const draftPdfBuffer = await page.pdf({
|
|
414
|
+
format: 'A4',
|
|
415
|
+
displayHeaderFooter: true,
|
|
416
|
+
footerTemplate: `
|
|
417
|
+
<div style="width: 100%; text-align: center; font-size: 10pt; margin-top: 10mm;">
|
|
418
|
+
Page <span class="pageNumber"></span> of <span class="totalPages"></span>
|
|
419
|
+
</div>
|
|
420
|
+
`,
|
|
421
|
+
headerTemplate: '<div></div>',
|
|
422
|
+
preferCSSPageSize: true,
|
|
423
|
+
printBackground: true,
|
|
424
|
+
margin: { top: '10mm', bottom: '10mm', left: '10mm', right: '10mm' }
|
|
425
|
+
});
|
|
426
|
+
|
|
427
|
+
// Now extract the page numbers from the tooltips and update the TOC entries
|
|
428
|
+
await page.evaluate(() => {
|
|
429
|
+
// Find the PDF TOC
|
|
430
|
+
const pdfToc = document.getElementById('pdf-toc');
|
|
431
|
+
if (!pdfToc) return;
|
|
432
|
+
|
|
433
|
+
const tocEntries = pdfToc.querySelectorAll('.toc-page-number');
|
|
434
|
+
const originalToc = document.getElementById('toc');
|
|
435
|
+
|
|
436
|
+
if (originalToc) {
|
|
437
|
+
// Get all links from the original TOC that have tooltip data
|
|
438
|
+
const originalLinks = originalToc.querySelectorAll('a[title], a[data-bs-title]');
|
|
439
|
+
|
|
440
|
+
// Create a mapping from heading IDs to page numbers based on tooltips
|
|
441
|
+
const idToPageMap = {};
|
|
442
|
+
|
|
443
|
+
originalLinks.forEach(link => {
|
|
444
|
+
// Extract the heading ID from href
|
|
445
|
+
const href = link.getAttribute('href');
|
|
446
|
+
if (!href || !href.startsWith('#')) return;
|
|
447
|
+
|
|
448
|
+
const headingId = href.substring(1);
|
|
449
|
+
|
|
450
|
+
// Extract page number from tooltip text (e.g., "Go to page 5")
|
|
451
|
+
const tooltipText = link.getAttribute('title') || link.getAttribute('data-bs-title');
|
|
452
|
+
if (!tooltipText) return;
|
|
453
|
+
|
|
454
|
+
const pageNumberMatch = tooltipText.match(/Go to page (\d+)/i);
|
|
455
|
+
if (pageNumberMatch && pageNumberMatch[1]) {
|
|
456
|
+
const pageNumber = parseInt(pageNumberMatch[1], 10);
|
|
457
|
+
if (!isNaN(pageNumber)) {
|
|
458
|
+
idToPageMap[headingId] = pageNumber;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Now update the TOC page numbers using the extracted values
|
|
464
|
+
tocEntries.forEach(entry => {
|
|
465
|
+
const targetId = entry.getAttribute('data-for-id');
|
|
466
|
+
if (targetId && idToPageMap[targetId]) {
|
|
467
|
+
entry.textContent = idToPageMap[targetId];
|
|
468
|
+
// Ensure page numbers are clearly visible with proper styling
|
|
469
|
+
entry.style.visibility = 'visible';
|
|
470
|
+
entry.style.opacity = '1';
|
|
471
|
+
entry.style.color = '#000';
|
|
472
|
+
entry.style.background = '#fff';
|
|
473
|
+
entry.style.padding = '0 4px';
|
|
474
|
+
entry.style.fontWeight = 'normal';
|
|
475
|
+
}
|
|
476
|
+
});
|
|
477
|
+
} else {
|
|
478
|
+
// Fallback to old estimation method if original TOC is not available
|
|
479
|
+
console.log('Original TOC not found, using page number estimation method');
|
|
480
|
+
|
|
481
|
+
// Find all headings with IDs (potential TOC targets)
|
|
482
|
+
const headingsWithIds = Array.from(document.querySelectorAll('h1, h2, h3, h4, h5, h6')).filter(h => h.id);
|
|
483
|
+
|
|
484
|
+
// Use real offsets for more accurate page numbers
|
|
485
|
+
const idToPosition = {};
|
|
486
|
+
|
|
487
|
+
headingsWithIds.forEach(heading => {
|
|
488
|
+
const rect = heading.getBoundingClientRect();
|
|
489
|
+
idToPosition[heading.id] = {
|
|
490
|
+
top: rect.top + window.scrollY,
|
|
491
|
+
id: heading.id
|
|
492
|
+
};
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// Sort by vertical position
|
|
496
|
+
const sortedPositions = Object.values(idToPosition).sort((a, b) => a.top - b.top);
|
|
497
|
+
|
|
498
|
+
// A4 page height (mm to pixels at 96 DPI)
|
|
499
|
+
const pageHeight = 297 * 96 / 25.4;
|
|
500
|
+
const effectivePageHeight = pageHeight - 20; // Account for margins
|
|
501
|
+
|
|
502
|
+
// Start on page 3 (after title and TOC)
|
|
503
|
+
let currentPage = 3;
|
|
504
|
+
let currentOffset = 0;
|
|
505
|
+
|
|
506
|
+
// Calculate page numbers based on relative positions
|
|
507
|
+
sortedPositions.forEach(pos => {
|
|
508
|
+
while (pos.top > currentOffset + effectivePageHeight) {
|
|
509
|
+
currentPage++;
|
|
510
|
+
currentOffset += effectivePageHeight;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
idToPosition[pos.id].page = currentPage;
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
// Update TOC entries with calculated page numbers
|
|
517
|
+
tocEntries.forEach(entry => {
|
|
518
|
+
const targetId = entry.getAttribute('data-for-id');
|
|
519
|
+
if (targetId && idToPosition[targetId] && idToPosition[targetId].page) {
|
|
520
|
+
entry.textContent = idToPosition[targetId].page;
|
|
521
|
+
}
|
|
522
|
+
});
|
|
523
|
+
}
|
|
215
524
|
});
|
|
216
525
|
|
|
217
|
-
//
|
|
526
|
+
// Final PDF generation with correct page numbers
|
|
218
527
|
const pdfBuffer = await page.pdf({
|
|
219
528
|
path: path.resolve(process.cwd(), 'docs/index.pdf'),
|
|
220
529
|
format: 'A4',
|
|
@@ -228,7 +537,7 @@ const pdfLib = require('pdf-lib');
|
|
|
228
537
|
preferCSSPageSize: true,
|
|
229
538
|
printBackground: true,
|
|
230
539
|
quality: 100,
|
|
231
|
-
margin: { top: '10mm', bottom: '10mm', left: '
|
|
540
|
+
margin: { top: '10mm', bottom: '10mm', left: '10mm', right: '10mm' }
|
|
232
541
|
});
|
|
233
542
|
|
|
234
543
|
await browser.close();
|
|
@@ -248,4 +557,4 @@ const pdfLib = require('pdf-lib');
|
|
|
248
557
|
} catch (error) {
|
|
249
558
|
console.error('❌ Error generating PDF:', error);
|
|
250
559
|
}
|
|
251
|
-
})();
|
|
560
|
+
})();
|
package/src/create-term-index.js
CHANGED
|
@@ -1,41 +1,145 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Steps:
|
|
3
|
-
* 1. Reads the configuration from 'specs.json'.
|
|
4
|
-
* 2. Extracts the directories containing the specifications and terms.
|
|
5
|
-
* 3. Lists all file names in the specified terms directory.
|
|
3
|
+
* 1. Reads the configuration from 'specs.json', with fallbacks for missing or invalid files.
|
|
4
|
+
* 2. Extracts the directories containing the specifications and terms, with defaults for missing values.
|
|
5
|
+
* 3. Lists all file names in the specified terms directory if it exists.
|
|
6
6
|
* 4. Joins each file name with the terms directory path.
|
|
7
|
-
* 5. Creates an '
|
|
7
|
+
* 5. Creates an '.cache' directory in the project root if it does not exist.
|
|
8
8
|
* 6. Writes the list of file paths to 'term-index.json' in the project root.
|
|
9
9
|
*
|
|
10
|
+
* If any errors occur during the process, appropriate warnings are logged, and an empty term index
|
|
11
|
+
* will be created rather than throwing fatal errors.
|
|
12
|
+
*
|
|
10
13
|
* @requires fs-extra - File system operations with extra methods.
|
|
11
14
|
* @requires path - Utilities for working with file and directory paths.
|
|
12
15
|
* @file src/create-term-index.js
|
|
13
16
|
* @author Kor Dwarshuis
|
|
14
|
-
* @version 1.
|
|
17
|
+
* @version 1.1.0
|
|
15
18
|
* @since 2024-09-02
|
|
16
19
|
*/
|
|
17
20
|
|
|
18
21
|
const { shouldProcessFile } = require('./utils/file-filter');
|
|
19
22
|
|
|
20
23
|
function createTermIndex() {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
24
|
+
try {
|
|
25
|
+
const fs = require('fs-extra');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const configPath = 'specs.json';
|
|
28
|
+
|
|
29
|
+
// Check if specs.json exists
|
|
30
|
+
if (!fs.existsSync(configPath)) {
|
|
31
|
+
console.warn(`Config file '${configPath}' not found. Using default configuration.`);
|
|
32
|
+
var config = { specs: [] };
|
|
33
|
+
} else {
|
|
34
|
+
// Read config with try-catch to handle parsing errors
|
|
35
|
+
try {
|
|
36
|
+
var config = fs.readJsonSync(configPath);
|
|
37
|
+
} catch (readError) {
|
|
38
|
+
console.warn(`Error reading config file: ${readError.message}. Using default configuration.`);
|
|
39
|
+
var config = { specs: [] };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Provide defaults for missing config
|
|
44
|
+
if (!config) {
|
|
45
|
+
console.warn('Config file is empty or invalid. Using defaults.');
|
|
46
|
+
config = { specs: [] };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!config.specs) {
|
|
50
|
+
console.warn('No specs array found in config. Creating empty specs array.');
|
|
51
|
+
config.specs = [];
|
|
52
|
+
} else if (!Array.isArray(config.specs)) {
|
|
53
|
+
console.warn('Config specs is not an array. Converting to array.');
|
|
54
|
+
config.specs = [config.specs]; // Convert to array if it's an object
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// If no valid specs, create an empty term index
|
|
58
|
+
if (config.specs.length === 0) {
|
|
59
|
+
console.warn('No specs found in configuration. Creating an empty term index.');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Extract spec directories with fallback to current directory
|
|
63
|
+
const specDirectories = config.specs.map((spec, index) => {
|
|
64
|
+
if (!spec.spec_directory) {
|
|
65
|
+
console.warn(`Warning: spec_directory missing in specs.json entry #${index + 1}. Using current directory.`);
|
|
66
|
+
return '.'; // Default to current directory
|
|
67
|
+
}
|
|
68
|
+
return spec.spec_directory;
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Extract term directories with fallback to default value
|
|
72
|
+
const specTermDirectoryName = config.specs.map((spec, index) => {
|
|
73
|
+
if (!spec.spec_terms_directory) {
|
|
74
|
+
console.warn(`Warning: spec_terms_directory missing in specs.json entry #${index + 1}. Using default 'terms' directory.`);
|
|
75
|
+
return 'terms'; // Default directory name for terms
|
|
76
|
+
}
|
|
77
|
+
return spec.spec_terms_directory;
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
// Safety check - if we have no valid entries, warn and exit cleanly
|
|
81
|
+
if (specDirectories.length === 0 || specTermDirectoryName.length === 0) {
|
|
82
|
+
console.log('No term directories found in configuration. Creating empty term index.');
|
|
83
|
+
|
|
84
|
+
// Create an empty term index
|
|
85
|
+
const outputPathJSON = path.join('.cache', 'term-index.json');
|
|
86
|
+
if (!fs.existsSync('.cache')) {
|
|
87
|
+
fs.mkdirSync('.cache', { recursive: true });
|
|
88
|
+
}
|
|
89
|
+
fs.writeJsonSync(outputPathJSON, [], { spaces: 2 });
|
|
90
|
+
console.log(`✅ Empty term index created at: ${outputPathJSON}`);
|
|
91
|
+
return; // Exit function early
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Verify that the base spec directory exists
|
|
95
|
+
const baseSpecDir = specDirectories[0];
|
|
96
|
+
if (!baseSpecDir || !fs.existsSync(baseSpecDir)) {
|
|
97
|
+
console.warn(`Spec directory '${baseSpecDir}' does not exist. Creating empty term index.`);
|
|
98
|
+
|
|
99
|
+
// Create an empty term index
|
|
100
|
+
const outputPathJSON = path.join('.cache', 'term-index.json');
|
|
101
|
+
if (!fs.existsSync('.cache')) {
|
|
102
|
+
fs.mkdirSync('.cache', { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
fs.writeJsonSync(outputPathJSON, [], { spaces: 2 });
|
|
105
|
+
console.log(`✅ Empty term index created at: ${outputPathJSON}`);
|
|
106
|
+
return; // Exit function early
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// Verify that the terms directory exists
|
|
110
|
+
const termsDir = path.join(baseSpecDir, specTermDirectoryName[0]);
|
|
111
|
+
|
|
112
|
+
let files = [];
|
|
113
|
+
if (!fs.existsSync(termsDir)) {
|
|
114
|
+
console.warn(`Terms directory '${termsDir}' does not exist. Creating an empty term index.`);
|
|
115
|
+
} else {
|
|
116
|
+
// Get list of files and filter them
|
|
117
|
+
files = fs.readdirSync(termsDir).filter(shouldProcessFile);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (files.length === 0) {
|
|
121
|
+
console.log('Warning: No term files found to process.');
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const filePaths = files.map(file => specTermDirectoryName[0] + '/' + file);
|
|
125
|
+
const outputPathJSON = path.join('.cache', 'term-index.json');
|
|
126
|
+
|
|
127
|
+
// Create .cache directory if it doesn't exist
|
|
128
|
+
if (!fs.existsSync('.cache')) {
|
|
129
|
+
fs.mkdirSync('.cache', { recursive: true });
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Write the term index file
|
|
133
|
+
try {
|
|
134
|
+
fs.writeJsonSync(outputPathJSON, filePaths, { spaces: 2 });
|
|
135
|
+
console.log(`✅ Term index created with ${files.length} terms. Output: ${outputPathJSON}`);
|
|
136
|
+
} catch (writeError) {
|
|
137
|
+
throw new Error(`Failed to write term index file: ${writeError.message}`);
|
|
138
|
+
}
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(`❌ Error creating term index: ${error.message}`);
|
|
141
|
+
throw error;
|
|
34
142
|
}
|
|
35
|
-
|
|
36
|
-
fs.writeJsonSync(outputPathJSON, filePaths, { spaces: 2 });
|
|
37
|
-
|
|
38
|
-
console.log(`✅ The new terms were added. All done.`);
|
|
39
143
|
}
|
|
40
144
|
|
|
41
145
|
module.exports = {
|
|
@@ -321,7 +321,7 @@ async function getProjectConfiguration(projectRoot) {
|
|
|
321
321
|
externalSpecs,
|
|
322
322
|
specDirs,
|
|
323
323
|
valid: true,
|
|
324
|
-
githubCacheDir: path.join(projectRoot, '
|
|
324
|
+
githubCacheDir: path.join(projectRoot, '.cache', 'github-cache')
|
|
325
325
|
};
|
|
326
326
|
}
|
|
327
327
|
|
package/src/health-check.js
CHANGED
|
@@ -15,7 +15,7 @@ const outputGitignoreChecker = require('./health-check/output-gitignore-checker'
|
|
|
15
15
|
const trefTermChecker = require('./health-check/tref-term-checker');
|
|
16
16
|
|
|
17
17
|
// Configuration
|
|
18
|
-
const OUTPUT_DIR = path.join(process.cwd(), '
|
|
18
|
+
const OUTPUT_DIR = path.join(process.cwd(), '.cache');
|
|
19
19
|
|
|
20
20
|
// Create output directory if it doesn't exist
|
|
21
21
|
if (!fs.existsSync(OUTPUT_DIR)) {
|
package/src/init.js
CHANGED