spec-up-t 1.2.7 → 1.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.github/copilot-instructions.md +4 -0
- package/assets/compiled/body.js +2 -1
- package/assets/compiled/head.css +1 -0
- package/assets/compiled/refs.json +1 -1
- package/assets/css/highlight-heading-plus-sibling-nodes.css +6 -0
- package/assets/css/index.css +9 -0
- package/assets/js/collapse-definitions.js +0 -6
- package/assets/js/highlight-heading-plus-sibling-nodes.js +259 -0
- package/config/asset-map.json +2 -0
- package/gulpfile.js +8 -2
- package/index.js +24 -16
- package/package.json +1 -1
- package/src/collect-external-references.js +10 -23
- package/src/escape-handler.js +67 -0
- package/src/escape-mechanism.js +57 -0
- package/src/health-check/specs-configuration-checker.js +178 -144
- package/src/health-check.js +199 -10
- package/src/markdown-it-extensions.js +8 -0
- package/src/collectExternalReferences/fetchTermsFromIndex.1.js +0 -340
package/assets/css/index.css
CHANGED
|
@@ -3,6 +3,15 @@
|
|
|
3
3
|
/* Matches sticky header height */
|
|
4
4
|
}
|
|
5
5
|
|
|
6
|
+
|
|
7
|
+
/*
|
|
8
|
+
- When a URL fragment targets an element,
|
|
9
|
+
the `:target` pseudo-class triggers the corresponding CSS rules.
|
|
10
|
+
- If the body lacks the hashscroll attribute:
|
|
11
|
+
- The targeted element gets highlighted with an animation (highlight-target).
|
|
12
|
+
- Its parent <dt>(if applicable) gets highlighted with a different animation (highlight-target-parent-dt).
|
|
13
|
+
- These animations visually emphasize the targeted element and its parent for better user experience.
|
|
14
|
+
*/
|
|
6
15
|
body:not([hashscroll]) *:target {
|
|
7
16
|
animation: highlight-target 3.5s 0.25s ease;
|
|
8
17
|
}
|
|
@@ -227,12 +227,6 @@ function collapseDefinitions() {
|
|
|
227
227
|
button.style.right = `${window.innerWidth - buttonRect.right}px`;
|
|
228
228
|
button.style.zIndex = '1000';
|
|
229
229
|
|
|
230
|
-
// Add highlight effect
|
|
231
|
-
dtElement.classList.add('highlight');
|
|
232
|
-
setTimeout(() => {
|
|
233
|
-
dtElement.classList.remove('highlight');
|
|
234
|
-
}, 0);
|
|
235
|
-
|
|
236
230
|
// Toggle visibility which might change layout
|
|
237
231
|
toggleVisibility();
|
|
238
232
|
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Provides functionality to highlight headings and their following sibling nodes
|
|
3
|
+
* when anchor links are clicked. This module uses event delegation to handle link clicks
|
|
4
|
+
* and dynamically wraps content sections with highlighting divs.
|
|
5
|
+
*
|
|
6
|
+
* @author Generated by AI Assistant
|
|
7
|
+
* @version 1.0.0
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Highlights a heading and its following sibling nodes until the next heading of the same level.
|
|
12
|
+
* Only processes headings h2-h6, excluding h1 elements.
|
|
13
|
+
*
|
|
14
|
+
* @param {string} anchor - The anchor string (e.g., "#linking-to-this-glossary")
|
|
15
|
+
* @returns {boolean} True if highlighting was successful, false otherwise
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* // Highlight heading with id "section-1" and its content
|
|
19
|
+
* highlightHeadingSection("#section-1");
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* // Use with anchor from URL hash
|
|
23
|
+
* highlightHeadingSection(window.location.hash);
|
|
24
|
+
*/
|
|
25
|
+
function highlightHeadingSection(anchor) {
|
|
26
|
+
// Validate input parameter
|
|
27
|
+
if (!anchor || typeof anchor !== 'string' || !anchor.startsWith('#')) {
|
|
28
|
+
console.warn('Invalid anchor provided:', anchor);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Remove existing highlights before adding new ones
|
|
33
|
+
removeExistingHighlights();
|
|
34
|
+
|
|
35
|
+
// Extract the element ID from the anchor
|
|
36
|
+
const elementId = anchor.substring(1);
|
|
37
|
+
const targetElement = document.getElementById(elementId);
|
|
38
|
+
|
|
39
|
+
if (!targetElement) {
|
|
40
|
+
console.warn('Element with ID not found:', elementId);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check if the target element is a valid heading (h2-h6)
|
|
45
|
+
const headingLevel = getHeadingLevel(targetElement);
|
|
46
|
+
if (headingLevel === null) {
|
|
47
|
+
console.info('Target element is not a valid heading (h2-h6):', elementId);
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Find all sibling nodes until the next heading of the same level
|
|
52
|
+
const nodesToHighlight = collectHeadingSiblings(targetElement, headingLevel);
|
|
53
|
+
|
|
54
|
+
// Wrap the collected nodes with highlight div
|
|
55
|
+
wrapNodesWithHighlight(nodesToHighlight);
|
|
56
|
+
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Determines if an element is a heading and returns its level.
|
|
62
|
+
* Only considers h2-h6 elements as valid headings.
|
|
63
|
+
*
|
|
64
|
+
* @param {Element} element - The DOM element to check
|
|
65
|
+
* @returns {number|null} The heading level (2-6) or null if not a valid heading
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* const level = getHeadingLevel(document.querySelector('h3')); // Returns 3
|
|
69
|
+
* const invalid = getHeadingLevel(document.querySelector('h1')); // Returns null
|
|
70
|
+
*/
|
|
71
|
+
function getHeadingLevel(element) {
|
|
72
|
+
const tagName = element.tagName.toLowerCase();
|
|
73
|
+
const headingMatch = tagName.match(/^h([2-6])$/);
|
|
74
|
+
return headingMatch ? parseInt(headingMatch[1], 10) : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Collects a heading element and all its following sibling nodes until the next
|
|
79
|
+
* heading of the same or higher level in the hierarchy.
|
|
80
|
+
*
|
|
81
|
+
* @param {Element} headingElement - The heading element to start from
|
|
82
|
+
* @param {number} headingLevel - The level of the heading (2-6)
|
|
83
|
+
* @returns {Element[]} Array of elements to be highlighted
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* const h3Element = document.querySelector('#my-section');
|
|
87
|
+
* const siblings = collectHeadingSiblings(h3Element, 3);
|
|
88
|
+
* // Returns [h3Element, ...following siblings until next h3 or higher]
|
|
89
|
+
*/
|
|
90
|
+
function collectHeadingSiblings(headingElement, headingLevel) {
|
|
91
|
+
const nodesToHighlight = [headingElement];
|
|
92
|
+
let currentNode = headingElement.nextElementSibling;
|
|
93
|
+
|
|
94
|
+
while (currentNode) {
|
|
95
|
+
const currentHeadingLevel = getHeadingLevel(currentNode);
|
|
96
|
+
|
|
97
|
+
// Stop if we encounter a heading of the same or higher level
|
|
98
|
+
if (currentHeadingLevel !== null && currentHeadingLevel <= headingLevel) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
nodesToHighlight.push(currentNode);
|
|
103
|
+
currentNode = currentNode.nextElementSibling;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return nodesToHighlight;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Wraps a collection of DOM nodes with a highlighting div element.
|
|
111
|
+
* Creates a div with class "highlight2" and moves all nodes inside it.
|
|
112
|
+
*
|
|
113
|
+
* @param {Element[]} nodes - Array of DOM elements to wrap
|
|
114
|
+
* @returns {Element|null} The created wrapper div or null if no nodes provided
|
|
115
|
+
*
|
|
116
|
+
* @example
|
|
117
|
+
* const nodes = [headingEl, paragraphEl, listEl];
|
|
118
|
+
* const wrapper = wrapNodesWithHighlight(nodes);
|
|
119
|
+
* // Creates: <div class="highlight2">...nodes...</div>
|
|
120
|
+
*/
|
|
121
|
+
function wrapNodesWithHighlight(nodes) {
|
|
122
|
+
if (!nodes || nodes.length === 0) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Create the highlight wrapper div
|
|
127
|
+
const highlightDiv = document.createElement('div');
|
|
128
|
+
highlightDiv.className = 'highlight2';
|
|
129
|
+
|
|
130
|
+
// Insert the wrapper before the first node
|
|
131
|
+
const firstNode = nodes[0];
|
|
132
|
+
firstNode.parentNode.insertBefore(highlightDiv, firstNode);
|
|
133
|
+
|
|
134
|
+
// Move all nodes into the wrapper
|
|
135
|
+
nodes.forEach(node => {
|
|
136
|
+
highlightDiv.appendChild(node);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
return highlightDiv;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Removes all existing highlight wrappers from the document.
|
|
144
|
+
* This ensures only one section is highlighted at a time.
|
|
145
|
+
*
|
|
146
|
+
* @returns {number} The number of highlight divs removed
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* const removedCount = removeExistingHighlights();
|
|
150
|
+
* console.log(`Removed ${removedCount} existing highlights`);
|
|
151
|
+
*/
|
|
152
|
+
function removeExistingHighlights() {
|
|
153
|
+
const existingHighlights = document.querySelectorAll('.highlight2');
|
|
154
|
+
let removedCount = 0;
|
|
155
|
+
|
|
156
|
+
existingHighlights.forEach(highlight => {
|
|
157
|
+
// Move all children back to the parent before removing the wrapper
|
|
158
|
+
const parent = highlight.parentNode;
|
|
159
|
+
while (highlight.firstChild) {
|
|
160
|
+
parent.insertBefore(highlight.firstChild, highlight);
|
|
161
|
+
}
|
|
162
|
+
parent.removeChild(highlight);
|
|
163
|
+
removedCount++;
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return removedCount;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Handles click events on anchor links and triggers heading highlighting.
|
|
171
|
+
* Uses event delegation to handle dynamically added links.
|
|
172
|
+
*
|
|
173
|
+
* @param {Event} event - The click event object
|
|
174
|
+
* @returns {void}
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* // This function is automatically called when anchor links are clicked
|
|
178
|
+
* // due to the event delegation setup in initializeAnchorHighlighting()
|
|
179
|
+
*/
|
|
180
|
+
function handleAnchorClick(event) {
|
|
181
|
+
const target = event.target;
|
|
182
|
+
|
|
183
|
+
// Check if the clicked element is a link with an href containing a hash
|
|
184
|
+
if (target.tagName.toLowerCase() !== 'a') {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const href = target.getAttribute('href');
|
|
189
|
+
if (!href || !href.includes('#')) {
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Extract the anchor part from the href
|
|
194
|
+
const anchorPart = href.substring(href.indexOf('#'));
|
|
195
|
+
|
|
196
|
+
// Only process if it's an internal anchor (starts with #)
|
|
197
|
+
if (anchorPart.startsWith('#') && anchorPart.length > 1) {
|
|
198
|
+
// Small delay to allow browser navigation to complete
|
|
199
|
+
setTimeout(() => {
|
|
200
|
+
highlightHeadingSection(anchorPart);
|
|
201
|
+
}, 100);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Initializes the anchor link highlighting functionality using event delegation.
|
|
207
|
+
* Sets up a single click event listener on the document that handles all anchor clicks.
|
|
208
|
+
* This approach is more efficient than attaching individual listeners to each link.
|
|
209
|
+
*
|
|
210
|
+
* @returns {void}
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* // Initialize the highlighting system
|
|
214
|
+
* initializeAnchorHighlighting();
|
|
215
|
+
*
|
|
216
|
+
* // Now all anchor links will automatically trigger highlighting
|
|
217
|
+
* // when clicked, including dynamically added links
|
|
218
|
+
*/
|
|
219
|
+
function initializeAnchorHighlighting() {
|
|
220
|
+
// Use event delegation to handle all anchor clicks
|
|
221
|
+
document.addEventListener('click', handleAnchorClick);
|
|
222
|
+
|
|
223
|
+
// Also handle direct navigation to anchors (e.g., page load with hash)
|
|
224
|
+
if (window.location.hash) {
|
|
225
|
+
// Delay to ensure DOM is fully loaded
|
|
226
|
+
setTimeout(() => {
|
|
227
|
+
highlightHeadingSection(window.location.hash);
|
|
228
|
+
}, 200);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Handle browser back/forward navigation
|
|
232
|
+
window.addEventListener('hashchange', () => {
|
|
233
|
+
if (window.location.hash) {
|
|
234
|
+
highlightHeadingSection(window.location.hash);
|
|
235
|
+
} else {
|
|
236
|
+
removeExistingHighlights();
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Auto-initialize when DOM is ready
|
|
242
|
+
if (document.readyState === 'loading') {
|
|
243
|
+
document.addEventListener('DOMContentLoaded', initializeAnchorHighlighting);
|
|
244
|
+
} else {
|
|
245
|
+
// DOM is already loaded
|
|
246
|
+
initializeAnchorHighlighting();
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Export functions for potential external use
|
|
250
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
251
|
+
module.exports = {
|
|
252
|
+
highlightHeadingSection,
|
|
253
|
+
getHeadingLevel,
|
|
254
|
+
collectHeadingSiblings,
|
|
255
|
+
wrapNodesWithHighlight,
|
|
256
|
+
removeExistingHighlights,
|
|
257
|
+
initializeAnchorHighlighting
|
|
258
|
+
};
|
|
259
|
+
}
|
package/config/asset-map.json
CHANGED
|
@@ -26,6 +26,7 @@
|
|
|
26
26
|
"assets/css/image-full-size.css",
|
|
27
27
|
"assets/css/add-bootstrap-classes-to-images.css",
|
|
28
28
|
"assets/css/horizontal-scroll-hint.css",
|
|
29
|
+
"assets/css/highlight-heading-plus-sibling-nodes.css",
|
|
29
30
|
"assets/css/index.css"
|
|
30
31
|
],
|
|
31
32
|
"js": [
|
|
@@ -72,6 +73,7 @@
|
|
|
72
73
|
"assets/js/horizontal-scroll-hint.js",
|
|
73
74
|
"assets/js/add-bootstrap-classes-to-images.js",
|
|
74
75
|
"assets/js/image-full-size.js",
|
|
76
|
+
"assets/js/highlight-heading-plus-sibling-nodes.js",
|
|
75
77
|
"assets/js/bootstrap.bundle.min.js"
|
|
76
78
|
]
|
|
77
79
|
}
|
package/gulpfile.js
CHANGED
|
@@ -19,9 +19,15 @@ async function fetchSpecRefs() {
|
|
|
19
19
|
return Promise.all([
|
|
20
20
|
axios.get('https://raw.githubusercontent.com/tobie/specref/master/refs/ietf.json'),
|
|
21
21
|
axios.get('https://raw.githubusercontent.com/tobie/specref/master/refs/w3c.json'),
|
|
22
|
-
axios.get('https://raw.githubusercontent.com/tobie/specref/master/refs/whatwg.json')
|
|
22
|
+
axios.get('https://raw.githubusercontent.com/tobie/specref/master/refs/whatwg.json'),
|
|
23
|
+
axios.get('https://raw.githubusercontent.com/tobie/specref/master/refs/biblio.json')
|
|
23
24
|
]).then(async results => {
|
|
24
|
-
let json = Object.assign(
|
|
25
|
+
let json = Object.assign(
|
|
26
|
+
results[0].data,// IETF
|
|
27
|
+
results[1].data,// W3C
|
|
28
|
+
results[2].data,// WHATWG
|
|
29
|
+
results[3].data// Biblio
|
|
30
|
+
);
|
|
25
31
|
return fs.outputFile(compileLocation + '/refs.json', JSON.stringify(json));
|
|
26
32
|
}).catch(e => console.log(e));
|
|
27
33
|
}
|
package/index.js
CHANGED
|
@@ -20,6 +20,8 @@ module.exports = async function (options = {}) {
|
|
|
20
20
|
const { createTermIndex } = require('./src/create-term-index.js');
|
|
21
21
|
createTermIndex();
|
|
22
22
|
|
|
23
|
+
const { processWithEscapes } = require('./src/escape-handler.js');
|
|
24
|
+
|
|
23
25
|
const { insertTermIndex } = require('./src/insert-term-index.js');
|
|
24
26
|
insertTermIndex();
|
|
25
27
|
|
|
@@ -35,6 +37,7 @@ module.exports = async function (options = {}) {
|
|
|
35
37
|
createVersionsIndex(config.specs[0].output_path);
|
|
36
38
|
|
|
37
39
|
const { fixMarkdownFiles } = require('./src/fix-markdown-files.js');
|
|
40
|
+
const { processEscapedTags, restoreEscapedTags } = require('./src/escape-mechanism.js');
|
|
38
41
|
|
|
39
42
|
let template = fs.readFileSync(path.join(modulePath, 'templates/template.html'), 'utf8');
|
|
40
43
|
let assets = fs.readJsonSync(modulePath + '/config/asset-map.json');
|
|
@@ -181,14 +184,7 @@ module.exports = async function (options = {}) {
|
|
|
181
184
|
return fs.readFileSync(path, 'utf8');
|
|
182
185
|
}
|
|
183
186
|
},
|
|
184
|
-
|
|
185
|
-
test: 'spec',
|
|
186
|
-
transform: function (originalMatch, type, name) {
|
|
187
|
-
// Simply return an empty string or special marker that won't be treated as a definition term
|
|
188
|
-
// The actual rendering will be handled by the markdown-it extension
|
|
189
|
-
return `<span class="spec-marker" data-spec="${name}"></span>`;
|
|
190
|
-
}
|
|
191
|
-
},
|
|
187
|
+
|
|
192
188
|
/**
|
|
193
189
|
* Custom replacer for tref tags that converts them directly to HTML definition term elements.
|
|
194
190
|
*
|
|
@@ -244,13 +240,16 @@ module.exports = async function (options = {}) {
|
|
|
244
240
|
* @returns {string} - The processed document with tags replaced by their HTML equivalents
|
|
245
241
|
*/
|
|
246
242
|
function applyReplacers(doc) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
let
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
243
|
+
// Use the escape handler for three-phase processing
|
|
244
|
+
return processWithEscapes(doc, function(content) {
|
|
245
|
+
return content.replace(replacerRegex, function (match, type, args) {
|
|
246
|
+
let replacer = replacers.find(r => type.trim().match(r.test));
|
|
247
|
+
if (replacer) {
|
|
248
|
+
let argsArray = args ? args.trim().split(replacerArgsRegex) : [];
|
|
249
|
+
return replacer.transform(match, type, ...argsArray);
|
|
250
|
+
}
|
|
251
|
+
return match;
|
|
252
|
+
});
|
|
254
253
|
});
|
|
255
254
|
}
|
|
256
255
|
|
|
@@ -528,7 +527,12 @@ module.exports = async function (options = {}) {
|
|
|
528
527
|
|
|
529
528
|
let doc = docs.join("\n");
|
|
530
529
|
|
|
531
|
-
//
|
|
530
|
+
// Handles backslash escape mechanism for substitution tags
|
|
531
|
+
// Phase 1: Pre-processing - Handle escaped tags
|
|
532
|
+
doc = processEscapedTags(doc);
|
|
533
|
+
|
|
534
|
+
// Handles backslash escape mechanism for substitution tags
|
|
535
|
+
// Phase 2: Tag Processing - Apply normal substitution logic
|
|
532
536
|
doc = applyReplacers(doc);
|
|
533
537
|
|
|
534
538
|
md[spec.katex ? "enable" : "disable"](katexRules);
|
|
@@ -542,6 +546,10 @@ module.exports = async function (options = {}) {
|
|
|
542
546
|
// Sort definition terms case-insensitively before final rendering
|
|
543
547
|
renderedHtml = sortDefinitionTermsInHtml(renderedHtml);
|
|
544
548
|
|
|
549
|
+
// Handles backslash escape mechanism for substitution tags
|
|
550
|
+
// Phase 3: Post-processing - Restore escaped sequences as literals
|
|
551
|
+
renderedHtml = restoreEscapedTags(renderedHtml);
|
|
552
|
+
|
|
545
553
|
// Process external references to ensure they are inserted as raw HTML, not as JSON string
|
|
546
554
|
const externalReferencesHtml = Array.isArray(externalReferences)
|
|
547
555
|
? externalReferences.join('')
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spec-up-t",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.9",
|
|
4
4
|
"description": "Technical specification drafting tool that generates rich specification documents from markdown. Forked from https://github.com/decentralized-identity/spec-up by Daniel Buchner (https://github.com/csuwildcat)",
|
|
5
5
|
"main": "./index",
|
|
6
6
|
"repository": {
|
|
@@ -270,13 +270,17 @@ function processExternalReferences(config, GITHUB_API_TOKEN, options) {
|
|
|
270
270
|
*
|
|
271
271
|
* @description
|
|
272
272
|
* This function performs several key operations:
|
|
273
|
-
* 1.
|
|
273
|
+
* 1. Optionally uses GitHub PAT for better API performance and higher rate limits
|
|
274
274
|
* 2. Checks validity of repository URLs
|
|
275
275
|
* 3. Extracts xref/tref patterns from markdown content
|
|
276
276
|
* 4. Extends references with repository metadata
|
|
277
277
|
* 5. Processes references to fetch commit information
|
|
278
278
|
* 6. Generates output files in both JS and JSON formats
|
|
279
279
|
*
|
|
280
|
+
* Note: The function will run without a GitHub token but may encounter rate limits.
|
|
281
|
+
* For better performance, provide a GitHub Personal Access Token via environment
|
|
282
|
+
* variable or the options parameter.
|
|
283
|
+
*
|
|
280
284
|
* @example
|
|
281
285
|
* // Basic usage
|
|
282
286
|
* collectExternalReferences();
|
|
@@ -289,17 +293,6 @@ function collectExternalReferences(options = {}) {
|
|
|
289
293
|
const externalSpecsRepos = config.specs[0].external_specs;
|
|
290
294
|
const GITHUB_API_TOKEN = options.pat || process.env.GITHUB_API_TOKEN;
|
|
291
295
|
|
|
292
|
-
const explanationPAT =
|
|
293
|
-
`❌ No GitHub Personal Access Token (PAT) was found.
|
|
294
|
-
|
|
295
|
-
GitHub requires you to set up a PAT to retrieve external references.
|
|
296
|
-
|
|
297
|
-
There is no point in continuing without a PAT, so we stop here.
|
|
298
|
-
|
|
299
|
-
Find instructions on how to get a PAT at https://blockchainbird.github.io/spec-up-t-website/docs/getting-started/github-token
|
|
300
|
-
|
|
301
|
-
`;
|
|
302
|
-
|
|
303
296
|
const explanationNoExternalReferences =
|
|
304
297
|
`❌ No external references were found in the specs.json file.
|
|
305
298
|
|
|
@@ -310,19 +303,13 @@ function collectExternalReferences(options = {}) {
|
|
|
310
303
|
`;
|
|
311
304
|
|
|
312
305
|
// First do some checks
|
|
313
|
-
|
|
314
|
-
// Do not run the script if the GitHub API token is not set
|
|
306
|
+
// Show informational message if no token is available
|
|
315
307
|
if (!GITHUB_API_TOKEN) {
|
|
316
|
-
console.log(
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
// React to user pressing any key
|
|
320
|
-
if (userInput.trim() !== '') {
|
|
321
|
-
console.log('ℹ️ Stopping...');
|
|
322
|
-
return;
|
|
323
|
-
}
|
|
308
|
+
console.log('ℹ️ No GitHub Personal Access Token (PAT) found. Running without authentication (may hit rate limits).');
|
|
309
|
+
console.log('💡 For better performance, set up a PAT: https://blockchainbird.github.io/spec-up-t-website/docs/getting-started/github-token\n');
|
|
324
310
|
}
|
|
325
|
-
|
|
311
|
+
|
|
312
|
+
if (externalSpecsRepos.length === 0) {
|
|
326
313
|
// Check if the URLs for the external specs repositories are valid, and prompt the user to abort if they are not.
|
|
327
314
|
console.log(explanationNoExternalReferences);
|
|
328
315
|
const userInput = readlineSync.question('Press any key');
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape handler for substitution tags in Spec-Up
|
|
3
|
+
* Provides mechanism to display literal [[tag]] syntax without processing
|
|
4
|
+
*
|
|
5
|
+
* Implements a three-phase approach:
|
|
6
|
+
* 1. Pre-processing: Convert escaped sequences to temporary placeholders
|
|
7
|
+
* 2. Tag Processing: Existing substitution logic runs normally (placeholders ignored)
|
|
8
|
+
* 3. Post-processing: Restore escaped sequences as literals
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const ESCAPED_PLACEHOLDER = '__SPEC_UP_ESCAPED_TAG__';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pre-processes text to handle escaped substitution tags
|
|
15
|
+
* @param {string} text - Input text with potential escaped tags
|
|
16
|
+
* @returns {string} Text with escaped tags replaced by placeholders
|
|
17
|
+
*/
|
|
18
|
+
function preProcessEscapes(text) {
|
|
19
|
+
if (!text || typeof text !== 'string') {
|
|
20
|
+
return text;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Handle double backslash first: \\[[ → \__PLACEHOLDER__
|
|
24
|
+
let processed = text.replace(/\\\\(\[\[)/g, `\\${ESCAPED_PLACEHOLDER}`);
|
|
25
|
+
|
|
26
|
+
// Handle single backslash: \[[ → __PLACEHOLDER__
|
|
27
|
+
processed = processed.replace(/\\(\[\[)/g, ESCAPED_PLACEHOLDER);
|
|
28
|
+
|
|
29
|
+
return processed;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Post-processes text to restore escaped tags as literals
|
|
34
|
+
* @param {string} text - Text after substitution processing
|
|
35
|
+
* @returns {string} Text with placeholders restored to literal tags
|
|
36
|
+
*/
|
|
37
|
+
function postProcessEscapes(text) {
|
|
38
|
+
if (!text || typeof text !== 'string') {
|
|
39
|
+
return text;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Restore placeholders to literal [[
|
|
43
|
+
return text.replace(new RegExp(ESCAPED_PLACEHOLDER, 'g'), '[[');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Main processing function that wraps existing substitution logic
|
|
48
|
+
* @param {string} content - Raw markdown content
|
|
49
|
+
* @param {Function} processSubstitutions - Existing substitution processor
|
|
50
|
+
* @returns {string} Processed content with escape handling
|
|
51
|
+
*/
|
|
52
|
+
function processWithEscapes(content, processSubstitutions) {
|
|
53
|
+
if (!content || typeof content !== 'string') {
|
|
54
|
+
return content;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const preProcessed = preProcessEscapes(content);
|
|
58
|
+
const substituted = processSubstitutions(preProcessed);
|
|
59
|
+
return postProcessEscapes(substituted);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
module.exports = {
|
|
63
|
+
preProcessEscapes,
|
|
64
|
+
postProcessEscapes,
|
|
65
|
+
processWithEscapes,
|
|
66
|
+
ESCAPED_PLACEHOLDER
|
|
67
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escape Mechanism Module for Spec-Up Substitution Tags
|
|
3
|
+
*
|
|
4
|
+
* This module provides functions to handle backslash escape sequences for substitution tags,
|
|
5
|
+
* allowing users to display tag syntax literally in their documentation.
|
|
6
|
+
*
|
|
7
|
+
* The escape mechanism works in three phases:
|
|
8
|
+
* 1. Pre-processing: Convert escaped sequences to temporary placeholders
|
|
9
|
+
* 2. Tag processing: Normal substitution logic (handled elsewhere)
|
|
10
|
+
* 3. Post-processing: Restore escaped sequences as literals
|
|
11
|
+
*
|
|
12
|
+
* Supported escape pattern:
|
|
13
|
+
* - \[[tag: content]] → displays as literal [[tag: content]]
|
|
14
|
+
*
|
|
15
|
+
* @version 1.0.0
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Handles backslash escape mechanism for substitution tags
|
|
20
|
+
*
|
|
21
|
+
* Use backslash escape sequences to allow literal [[ tags in markdown
|
|
22
|
+
*
|
|
23
|
+
* Phase 1: Pre-processing - Convert escaped sequences to temporary placeholders
|
|
24
|
+
*
|
|
25
|
+
* @param {string} doc - The markdown document to process
|
|
26
|
+
* @returns {string} - Document with escaped sequences converted to placeholders
|
|
27
|
+
*/
|
|
28
|
+
function processEscapedTags(doc) {
|
|
29
|
+
// Replace \[[ with escape placeholder for literal display
|
|
30
|
+
// In markdown: \[[def: term]] should become [[def: term]] (literal tag syntax)
|
|
31
|
+
doc = doc.replace(/\\(\[\[)/g, '__SPEC_UP_ESCAPED_TAG__');
|
|
32
|
+
|
|
33
|
+
return doc;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Handles backslash escape mechanism for substitution tags
|
|
38
|
+
*
|
|
39
|
+
* Use backslash escape sequences to allow literal [[ tags in markdown
|
|
40
|
+
*
|
|
41
|
+
* Phase 3: Post-processing - Restore escaped sequences as literals
|
|
42
|
+
* Converts placeholders back to literal [[ characters
|
|
43
|
+
*
|
|
44
|
+
* @param {string} renderedHtml - The rendered HTML to process
|
|
45
|
+
* @returns {string} - HTML with placeholders restored to literal [[ tags
|
|
46
|
+
*/
|
|
47
|
+
function restoreEscapedTags(renderedHtml) {
|
|
48
|
+
// Replace escaped tag placeholders with literal [[
|
|
49
|
+
renderedHtml = renderedHtml.replace(/__SPEC_UP_ESCAPED_TAG__/g, '[[');
|
|
50
|
+
|
|
51
|
+
return renderedHtml;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
module.exports = {
|
|
55
|
+
processEscapedTags,
|
|
56
|
+
restoreEscapedTags
|
|
57
|
+
};
|