spec-up-t 1.3.1 → 1.4.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.
- package/.github/copilot-instructions.md +13 -0
- package/assets/compiled/body.js +17 -11
- package/assets/compiled/head.css +6 -4
- package/assets/css/collapse-definitions.css +0 -1
- package/assets/css/create-pdf.css +4 -2
- package/assets/css/create-term-filter.css +4 -4
- package/assets/css/definition-buttons-container.css +60 -0
- package/assets/css/insert-trefs.css +7 -0
- package/assets/css/sidebar-toc.css +2 -1
- package/assets/css/terms-and-definitions.css +73 -22
- package/assets/js/add-href-to-snapshot-link.js +16 -9
- package/assets/js/addAnchorsToTerms.js +1 -1
- package/assets/js/charts.js +10 -0
- package/assets/js/collapse-definitions.js +13 -2
- package/assets/js/collapse-meta-info.js +11 -9
- package/assets/js/definition-button-container-utils.js +82 -0
- package/assets/js/edit-term-buttons.js +77 -20
- package/assets/js/github-issues.js +35 -0
- package/assets/js/github-repo-info.js +144 -0
- package/assets/js/highlight-heading-plus-sibling-nodes.test.js +18 -0
- package/assets/js/insert-trefs.js +62 -13
- package/assets/js/mermaid-diagrams.js +11 -0
- package/assets/js/terminology-section-utility-container/README.md +107 -0
- package/assets/js/terminology-section-utility-container/create-alphabet-index.js +17 -0
- package/assets/js/{create-term-filter.js → terminology-section-utility-container/create-term-filter.js} +11 -44
- package/assets/js/terminology-section-utility-container/hide-show-utility-container.js +21 -0
- package/assets/js/terminology-section-utility-container/search.js +203 -0
- package/assets/js/terminology-section-utility-container.js +203 -0
- package/assets/js/tooltips.js +283 -0
- package/config/asset-map.json +24 -16
- package/index.js +57 -390
- package/package.json +5 -2
- package/src/add-remove-xref-source.js +20 -21
- package/src/collect-external-references.js +8 -337
- package/src/collect-external-references.test.js +440 -33
- package/src/configure.js +8 -109
- package/src/create-docx.js +7 -6
- package/src/create-pdf.js +15 -14
- package/src/freeze-spec-data.js +46 -0
- package/src/git-info.test.js +76 -0
- package/src/health-check/destination-gitignore-checker.js +5 -3
- package/src/health-check/external-specs-checker.js +5 -4
- package/src/health-check/specs-configuration-checker.js +2 -1
- package/src/health-check/term-references-checker.js +5 -3
- package/src/health-check/terms-intro-checker.js +2 -1
- package/src/health-check/tref-term-checker.js +8 -7
- package/src/health-check.js +8 -7
- package/src/init.js +3 -2
- package/src/install-from-boilerplate/add-gitignore-entries.js +3 -2
- package/src/install-from-boilerplate/add-scripts-keys.js +5 -4
- package/src/install-from-boilerplate/boilerplate/README.md +1 -1
- package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +1 -1
- package/src/install-from-boilerplate/boilerplate/spec/spec-head.md +1 -1
- package/src/install-from-boilerplate/boilerplate/specs.json +2 -1
- package/src/install-from-boilerplate/config-scripts-keys.js +3 -3
- package/src/install-from-boilerplate/copy-boilerplate.js +2 -1
- package/src/install-from-boilerplate/copy-system-files.js +4 -3
- package/src/install-from-boilerplate/custom-update.js +12 -1
- package/src/install-from-boilerplate/help.txt +1 -1
- package/src/install-from-boilerplate/menu.sh +6 -6
- package/src/json-key-validator.js +17 -11
- package/src/markdown-it/README.md +207 -0
- package/src/markdown-it/definition-lists.js +397 -0
- package/src/markdown-it/index.js +83 -0
- package/src/markdown-it/link-enhancement.js +98 -0
- package/src/markdown-it/plugins.js +118 -0
- package/src/markdown-it/table-enhancement.js +97 -0
- package/src/markdown-it/template-tag-syntax.js +152 -0
- package/src/parsers/index.js +16 -0
- package/src/parsers/spec-parser.js +152 -0
- package/src/parsers/spec-parser.test.js +109 -0
- package/src/parsers/template-tag-parser.js +277 -0
- package/src/parsers/template-tag-parser.test.js +107 -0
- package/src/pipeline/configuration/configure-starterpack.js +200 -0
- package/src/{create-external-specs-list.js → pipeline/configuration/create-external-specs-list.js} +13 -12
- package/src/{create-term-index.js → pipeline/configuration/create-term-index.js} +19 -18
- package/src/{create-versions-index.js → pipeline/configuration/create-versions-index.js} +4 -3
- package/src/{insert-term-index.js → pipeline/configuration/insert-term-index.js} +2 -2
- package/src/pipeline/configuration/prepare-spec-configuration.js +70 -0
- package/src/pipeline/parsing/apply-markdown-it-extensions.js +35 -0
- package/src/pipeline/parsing/create-markdown-parser.js +94 -0
- package/src/pipeline/parsing/create-markdown-parser.test.js +49 -0
- package/src/{html-dom-processor.js → pipeline/postprocessing/definition-list-postprocessor.js} +69 -10
- package/src/{escape-handler.js → pipeline/preprocessing/escape-processor.js} +3 -1
- package/src/{fix-markdown-files.js → pipeline/preprocessing/normalize-terminology-markdown.js} +41 -31
- package/src/pipeline/references/collect-external-references.js +307 -0
- package/src/pipeline/references/external-references-service.js +231 -0
- package/src/pipeline/references/fetch-terms-from-index.js +198 -0
- package/src/pipeline/references/match-term.js +34 -0
- package/src/{collectExternalReferences/matchTerm.test.js → pipeline/references/match-term.test.js} +8 -2
- package/src/pipeline/references/process-xtrefs-data.js +94 -0
- package/src/pipeline/references/xtref-utils.js +166 -0
- package/src/pipeline/rendering/render-spec-document.js +146 -0
- package/src/pipeline/rendering/render-utils.js +154 -0
- package/src/utils/LOGGER.md +81 -0
- package/src/utils/{doesUrlExist.js → does-url-exist.js} +4 -3
- package/src/utils/fetch.js +5 -4
- package/src/utils/file-opener.js +3 -2
- package/src/utils/git-info.js +77 -0
- package/src/utils/logger.js +74 -0
- package/src/utils/regex-patterns.js +471 -0
- package/src/utils/regex-patterns.test.js +281 -0
- package/templates/template.html +56 -21
- package/assets/js/create-alphabet-index.js +0 -60
- package/assets/js/hide-show-utility-container.js +0 -16
- package/assets/js/index.js +0 -87
- package/assets/js/search.js +0 -365
- package/src/collectExternalReferences/fetchTermsFromIndex.js +0 -284
- package/src/collectExternalReferences/matchTerm.js +0 -32
- package/src/collectExternalReferences/processXTrefsData.js +0 -108
- package/src/freeze.js +0 -90
- package/src/markdown-it-extensions.js +0 -395
- package/src/references.js +0 -114
- /package/assets/css/{bootstrap.min.css → embedded-libraries/bootstrap.min.css} +0 -0
- /package/assets/css/{prism.css → embedded-libraries/prism.css} +0 -0
- /package/assets/css/{prism.dark.css → embedded-libraries/prism.dark.css} +0 -0
- /package/assets/css/{prism.default.css → embedded-libraries/prism.default.css} +0 -0
- /package/assets/js/{bootstrap.bundle.min.js → embedded-libraries/bootstrap.bundle.min.js} +0 -0
- /package/assets/js/{chart.js → embedded-libraries/chart.js} +0 -0
- /package/assets/js/{diff.min.js → embedded-libraries/diff.min.js} +0 -0
- /package/assets/js/{font-awesome.js → embedded-libraries/font-awesome.js} +0 -0
- /package/assets/js/{mermaid.js → embedded-libraries/mermaid.js} +0 -0
- /package/assets/js/{notyf.js → embedded-libraries/notyf.js} +0 -0
- /package/assets/js/{popper.js → embedded-libraries/popper.js} +0 -0
- /package/assets/js/{prism.dark.js → embedded-libraries/prism.dark.js} +0 -0
- /package/assets/js/{prism.default.js → embedded-libraries/prism.default.js} +0 -0
- /package/assets/js/{prism.js → embedded-libraries/prism.js} +0 -0
- /package/assets/js/{tippy.js → embedded-libraries/tippy.js} +0 -0
- /package/src/{escape-mechanism.js → pipeline/preprocessing/escape-placeholder-utils.js} +0 -0
- /package/src/utils/{isLineWithDefinition.js → is-line-with-definition.js} +0 -0
package/assets/js/search.js
DELETED
|
@@ -1,365 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
Author: Kor Dwarshuis, kor@dwarshuis.com
|
|
3
|
-
Created: 2024-03-29
|
|
4
|
-
Description: In–page search functionality. Styling in /assets/css/search.css
|
|
5
|
-
|
|
6
|
-
Adds "instant search" or "dynamic search" (results while you type). This feature provides users with immediate feedback by displaying search results or suggestions as they input text, enhancing the user experience by making information discovery faster and more interactive. The search results are highlighted in the text. The page scrolls to the first result.
|
|
7
|
-
|
|
8
|
-
SEARCH RESULT STYLES:
|
|
9
|
-
|
|
10
|
-
Different styles for the search results can be configured in the "searchHighlightStyle" specConfig object in the specs.json file. The following styles are available:
|
|
11
|
-
|
|
12
|
-
DIF,ToIP,BTC,KERI,SSI,GLEIF (case insensitive)
|
|
13
|
-
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
function inPageSearch() {
|
|
17
|
-
// Check if the terms and definitions list exists
|
|
18
|
-
// If it doesn't exist, exit the function
|
|
19
|
-
// This prevents errors when the script is run on pages without the terms and definitions list
|
|
20
|
-
// and ensures that the script only runs when necessary
|
|
21
|
-
const termsListElement = document.querySelector(".terms-and-definitions-list");
|
|
22
|
-
const dtElements = termsListElement ? termsListElement.querySelectorAll("dt") : [];
|
|
23
|
-
|
|
24
|
-
if (dtElements.length === 0) {
|
|
25
|
-
return;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
/*****************/
|
|
30
|
-
/* CONFIGURATION */
|
|
31
|
-
|
|
32
|
-
const terminologySectionUtilityContainer = document.getElementById("terminology-section-utility-container");
|
|
33
|
-
|
|
34
|
-
const matchesStyle = specConfig.searchHighlightStyle || 'ssi';
|
|
35
|
-
const antiNameCollisions = 'search';
|
|
36
|
-
const debounceTime = 600;
|
|
37
|
-
const matches = 'matches';// What text to display after the number of matches
|
|
38
|
-
const searchBarPlaceholder = '🔍';
|
|
39
|
-
const searchableContent = document.querySelector('.terms-and-definitions-list');
|
|
40
|
-
|
|
41
|
-
/* END CONFIGURATION */
|
|
42
|
-
/*********************/
|
|
43
|
-
|
|
44
|
-
// Styling of search matches. See styles in /assets/css/search.css
|
|
45
|
-
const matchesStyleSelector = {
|
|
46
|
-
dif: 'highlight-matches-DIF-search',
|
|
47
|
-
toip: 'highlight-matches-ToIP-search',
|
|
48
|
-
btc: 'highlight-matches-BTC-search',
|
|
49
|
-
keri: 'highlight-matches-KERI-search',
|
|
50
|
-
ssi: 'highlight-matches-SSI-search',
|
|
51
|
-
gleif: 'highlight-matches-GLEIF-search'
|
|
52
|
-
};
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
/* Add DOM elements: search container with search bar, back and forth buttons, and results count */
|
|
56
|
-
const searchContainer = document.createElement("div");
|
|
57
|
-
searchContainer.setAttribute("id", `container-${antiNameCollisions}`);
|
|
58
|
-
searchContainer.classList.add("input-group", "mb-1", "d-flex", "align-items-center"); // Bootstrap 5.3 input group with margin bottom
|
|
59
|
-
searchContainer.setAttribute("role", "search"); // ARIA role for search container
|
|
60
|
-
terminologySectionUtilityContainer.appendChild(searchContainer);
|
|
61
|
-
|
|
62
|
-
// Add an input element (for search)
|
|
63
|
-
const searchInput = document.createElement("input");
|
|
64
|
-
searchInput.setAttribute("type", "text");
|
|
65
|
-
searchInput.setAttribute("id", antiNameCollisions);
|
|
66
|
-
searchInput.classList.add("form-control");
|
|
67
|
-
searchInput.setAttribute("placeholder", searchBarPlaceholder);
|
|
68
|
-
searchInput.setAttribute("aria-label", "Search terms"); // ARIA label for screen readers
|
|
69
|
-
searchInput.setAttribute("autocomplete", "off"); // Prevent browser autocomplete
|
|
70
|
-
searchContainer.appendChild(searchInput);
|
|
71
|
-
|
|
72
|
-
// Add a container for the navigation buttons and results
|
|
73
|
-
const buttonGroup = document.createElement("div");
|
|
74
|
-
buttonGroup.classList.add("input-group-append"); // Bootstrap 5.3 button group styling
|
|
75
|
-
|
|
76
|
-
// Add a back button
|
|
77
|
-
const goToPreviousMatchButton = document.createElement("button");
|
|
78
|
-
goToPreviousMatchButton.setAttribute("id", `one-match-backward-${antiNameCollisions}`);
|
|
79
|
-
goToPreviousMatchButton.classList.add("btn", "btn-outline-secondary");
|
|
80
|
-
goToPreviousMatchButton.setAttribute("type", "button"); // Specify button type
|
|
81
|
-
goToPreviousMatchButton.setAttribute("disabled", "true"); // Bootstrap 5 uses "true" instead of "disabled"
|
|
82
|
-
goToPreviousMatchButton.setAttribute("title", "Go to previous match (Left Arrow)");
|
|
83
|
-
goToPreviousMatchButton.setAttribute("aria-label", "Go to previous match");
|
|
84
|
-
goToPreviousMatchButton.innerHTML = '<span aria-hidden="true">▲</span>'; // Hide symbol from screen readers
|
|
85
|
-
buttonGroup.appendChild(goToPreviousMatchButton);
|
|
86
|
-
|
|
87
|
-
// Add a forward button
|
|
88
|
-
const goToNextMatchButton = document.createElement("button");
|
|
89
|
-
goToNextMatchButton.setAttribute("id", `one-match-forward-${antiNameCollisions}`);
|
|
90
|
-
goToNextMatchButton.classList.add("btn", "btn-outline-secondary");
|
|
91
|
-
goToNextMatchButton.setAttribute("type", "button");
|
|
92
|
-
goToNextMatchButton.setAttribute("disabled", "true");
|
|
93
|
-
goToNextMatchButton.setAttribute("title", "Go to next match (Right Arrow)");
|
|
94
|
-
goToNextMatchButton.setAttribute("aria-label", "Go to next match");
|
|
95
|
-
goToNextMatchButton.innerHTML = '<span aria-hidden="true">▼</span>';
|
|
96
|
-
buttonGroup.appendChild(goToNextMatchButton);
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
// Add number of matches with accessibility
|
|
100
|
-
const totalMatchesSpan = document.createElement("span");
|
|
101
|
-
totalMatchesSpan.setAttribute("id", `total-matches-${antiNameCollisions}`);
|
|
102
|
-
totalMatchesSpan.classList.add("input-group-text"); // Bootstrap 5.3 styling
|
|
103
|
-
totalMatchesSpan.innerHTML = `0 ${matches}`;
|
|
104
|
-
totalMatchesSpan.setAttribute("aria-live", "polite"); // Announce changes to screen readers
|
|
105
|
-
totalMatchesSpan.setAttribute("role", "status"); // Define as status region
|
|
106
|
-
searchContainer.appendChild(totalMatchesSpan);
|
|
107
|
-
|
|
108
|
-
// Add the button group to the container
|
|
109
|
-
searchContainer.appendChild(buttonGroup);
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
/* END Add DOM elements */
|
|
113
|
-
|
|
114
|
-
// Add an event listener to the input element
|
|
115
|
-
searchInput.addEventListener("input", function () {
|
|
116
|
-
const shouldScrollToFirstMatch = true; // Scroll to first match when user is actively searching
|
|
117
|
-
debouncedSearchAndHighlight(searchInput.value, shouldScrollToFirstMatch);
|
|
118
|
-
});
|
|
119
|
-
|
|
120
|
-
// The search will run when the user clicks the collapse button, so the search results are updated when the terms are collapsed or expanded. If the definitions are collapsed, the search results in the definitions will be removed.
|
|
121
|
-
document.addEventListener('click', event => {
|
|
122
|
-
if (event.target.classList.contains('collapse-all-defs-button')) {
|
|
123
|
-
const shouldScrollToFirstMatch = true; // Scroll when updating after collapse/expand
|
|
124
|
-
debouncedSearchAndHighlight(searchInput.value, shouldScrollToFirstMatch);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
|
|
128
|
-
const matchesClassName = "highlight-matches-" + antiNameCollisions;
|
|
129
|
-
const matchesStyleSelectorClassName = matchesStyleSelector[matchesStyle.toLowerCase()];
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
let totalMatches = 0;
|
|
133
|
-
let activeMatchIndex = -1;
|
|
134
|
-
|
|
135
|
-
function scrollToElementCenter(element) {
|
|
136
|
-
// First, bring the element into view
|
|
137
|
-
element.scrollIntoView({ behavior: "smooth", block: "start" });
|
|
138
|
-
|
|
139
|
-
// Calculate the necessary adjustment to center the element
|
|
140
|
-
const elementRect = element.getBoundingClientRect();
|
|
141
|
-
const absoluteElementTop = elementRect.top + window.pageYOffset;
|
|
142
|
-
const middleDiff = (window.innerHeight - elementRect.height) / 2;
|
|
143
|
-
const scrollTo = absoluteElementTop - middleDiff;
|
|
144
|
-
|
|
145
|
-
// Apply the adjustment
|
|
146
|
-
window.scrollTo({ top: scrollTo, behavior: "smooth" });
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function debounce(func, wait) {
|
|
150
|
-
let timeout;
|
|
151
|
-
return function executedFunction() {
|
|
152
|
-
const context = this;
|
|
153
|
-
const args = arguments;
|
|
154
|
-
clearTimeout(timeout);
|
|
155
|
-
timeout = setTimeout(() => func.apply(context, args), wait);
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
function removeAllSpans() {
|
|
160
|
-
let spans = document.querySelectorAll('span.' + matchesClassName);
|
|
161
|
-
spans.forEach(span => {
|
|
162
|
-
const childNodes = Array.from(span.childNodes);
|
|
163
|
-
|
|
164
|
-
// Removes child elements (there are currently no child element b.t.w.)
|
|
165
|
-
childNodes.forEach(node => {
|
|
166
|
-
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
167
|
-
span.removeChild(node);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
if (span.classList.contains(matchesClassName)) {
|
|
172
|
-
span.outerHTML = span.innerHTML;
|
|
173
|
-
}
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function handleBackAndForthButtonsDisabledState() {
|
|
178
|
-
// Backward button
|
|
179
|
-
if (activeMatchIndex <= 0) {
|
|
180
|
-
document.getElementById("one-match-backward-" + antiNameCollisions).setAttribute("disabled", "disabled");
|
|
181
|
-
} else {
|
|
182
|
-
document.getElementById("one-match-backward-" + antiNameCollisions).removeAttribute("disabled");
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// Forward button
|
|
186
|
-
if (activeMatchIndex >= totalMatches - 1) {
|
|
187
|
-
document.getElementById("one-match-forward-" + antiNameCollisions).setAttribute("disabled", "disabled");
|
|
188
|
-
} else {
|
|
189
|
-
document.getElementById("one-match-forward-" + antiNameCollisions).removeAttribute("disabled");
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function setTotalMatches() {
|
|
194
|
-
totalMatchesSpan.innerHTML = `${totalMatches} ${matches}`;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Debounce search input to improve performance. This creates a function that will only execute
|
|
198
|
-
// after the user has stopped typing for the specified debounceTime.
|
|
199
|
-
// The function takes two parameters:
|
|
200
|
-
// - searchString: The text to search for
|
|
201
|
-
// - scrollToFirstMatch: Whether to automatically scroll to the first match after highlighting
|
|
202
|
-
const debouncedSearchAndHighlight = debounce(search, debounceTime);
|
|
203
|
-
|
|
204
|
-
goToPreviousMatchButton.addEventListener("click", function () {
|
|
205
|
-
activeMatchIndex--;
|
|
206
|
-
|
|
207
|
-
const extraHighlightedMatch = document.querySelector("#" + antiNameCollisions + "-" + activeMatchIndex);
|
|
208
|
-
if (extraHighlightedMatch) {
|
|
209
|
-
scrollToElementCenter(extraHighlightedMatch);
|
|
210
|
-
}
|
|
211
|
-
extraHighlightedMatch.classList.add("active");
|
|
212
|
-
|
|
213
|
-
// Works in tandem with “transition” in CSS
|
|
214
|
-
setTimeout(() => {
|
|
215
|
-
extraHighlightedMatch.classList.remove("active");
|
|
216
|
-
}, 3000);
|
|
217
|
-
|
|
218
|
-
handleBackAndForthButtonsDisabledState();
|
|
219
|
-
});
|
|
220
|
-
goToNextMatchButton.addEventListener("click", function () {
|
|
221
|
-
activeMatchIndex++;
|
|
222
|
-
|
|
223
|
-
const extraHighlightedMatch = document.querySelector("#" + antiNameCollisions + "-" + activeMatchIndex);
|
|
224
|
-
if (extraHighlightedMatch) {
|
|
225
|
-
scrollToElementCenter(extraHighlightedMatch);
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
extraHighlightedMatch.classList.add("active");
|
|
229
|
-
|
|
230
|
-
// Works in tandem with “transition” in CSS
|
|
231
|
-
setTimeout(() => {
|
|
232
|
-
extraHighlightedMatch.classList.remove("active");
|
|
233
|
-
}, 3000);
|
|
234
|
-
|
|
235
|
-
handleBackAndForthButtonsDisabledState();
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// key bindings
|
|
239
|
-
document.addEventListener('keyup', (event) => {
|
|
240
|
-
switch (event.key) {
|
|
241
|
-
case "ArrowRight":
|
|
242
|
-
goToNextMatchButton.click(); // Simulate a click on button
|
|
243
|
-
break;
|
|
244
|
-
|
|
245
|
-
case "ArrowLeft":
|
|
246
|
-
goToPreviousMatchButton.click(); // Simulate a click on button
|
|
247
|
-
break;
|
|
248
|
-
}
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// Runs after every search input (debounced)
|
|
252
|
-
/**
|
|
253
|
-
* Performs search and highlighting of matches in the document
|
|
254
|
-
* @param {string} searchString - The text to search for
|
|
255
|
-
* @param {boolean} scrollToFirstMatch - Whether to automatically scroll to the first match after highlighting
|
|
256
|
-
*/
|
|
257
|
-
function search(searchString, scrollToFirstMatch) {
|
|
258
|
-
// Start clean
|
|
259
|
-
removeAllSpans();
|
|
260
|
-
totalMatches = 0;
|
|
261
|
-
activeMatchIndex = -1;
|
|
262
|
-
|
|
263
|
-
// Remove outer quotes if present
|
|
264
|
-
if (searchString.length >= 2) {
|
|
265
|
-
if (
|
|
266
|
-
(searchString.startsWith('"') && searchString.endsWith('"')) ||
|
|
267
|
-
(searchString.startsWith("'") && searchString.endsWith("'"))
|
|
268
|
-
) {
|
|
269
|
-
searchString = searchString.substring(1, searchString.length - 1);
|
|
270
|
-
}
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
// If the search string is empty, return
|
|
274
|
-
if (searchString === '') {
|
|
275
|
-
setTotalMatches();
|
|
276
|
-
return;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
let uniqueId = 0;
|
|
280
|
-
|
|
281
|
-
// Highlight the text that matches the search string (case-insensitive) with a span element
|
|
282
|
-
function markAndCountMatches(node) {
|
|
283
|
-
const nodeText = node.nodeValue;
|
|
284
|
-
const regex = new RegExp(searchString, 'gi');
|
|
285
|
-
let match;
|
|
286
|
-
let lastIndex = 0;
|
|
287
|
-
let fragments = document.createDocumentFragment();
|
|
288
|
-
|
|
289
|
-
while ((match = regex.exec(nodeText)) !== null) {
|
|
290
|
-
// Text before match
|
|
291
|
-
fragments.appendChild(document.createTextNode(nodeText.substring(lastIndex, match.index)));
|
|
292
|
-
|
|
293
|
-
// Highlighted text
|
|
294
|
-
const highlightSpan = document.createElement('span');
|
|
295
|
-
highlightSpan.textContent = match[0];
|
|
296
|
-
highlightSpan.classList.add(matchesClassName);
|
|
297
|
-
highlightSpan.classList.add(matchesStyleSelectorClassName);
|
|
298
|
-
highlightSpan.setAttribute("id", antiNameCollisions + "-" + uniqueId);
|
|
299
|
-
fragments.appendChild(highlightSpan);
|
|
300
|
-
|
|
301
|
-
// uniqueId starts at 0, so totalMatches is the number of uniqueId's + 1
|
|
302
|
-
totalMatches = uniqueId + 1;
|
|
303
|
-
|
|
304
|
-
uniqueId++;
|
|
305
|
-
|
|
306
|
-
lastIndex = match.index + match[0].length;
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Remaining text
|
|
310
|
-
fragments.appendChild(document.createTextNode(nodeText.substring(lastIndex)));
|
|
311
|
-
return fragments;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
// Recursive function that searches all nodes in the DOM tree and highlights the text that matches the search string (case-insensitive) with a span element
|
|
315
|
-
function searchNodes(node) {
|
|
316
|
-
/*
|
|
317
|
-
Helper function to check if any ancestor has the 'hidden' class. Why the 'hidden' class? Because we don't want to highlight text that is hidden, and 'hidden' is the class that is used in the JS that collapses and expands the terms and definitions in the specs. The class is applied to the <dd>'s
|
|
318
|
-
*/
|
|
319
|
-
function hasHiddenAncestor(node) {
|
|
320
|
-
while (node) {
|
|
321
|
-
if (node?.classList?.contains('hidden')) {
|
|
322
|
-
return true;
|
|
323
|
-
}
|
|
324
|
-
node = node.parentNode;
|
|
325
|
-
}
|
|
326
|
-
return false;
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
if (node.nodeType === 3 && !hasHiddenAncestor(node)) { // Node.TEXT_NODE
|
|
330
|
-
const fragments = markAndCountMatches(node);
|
|
331
|
-
if (fragments.childNodes.length > 1) {
|
|
332
|
-
// Replace the text node with the fragments if there were matches
|
|
333
|
-
node.parentNode.replaceChild(fragments, node);
|
|
334
|
-
}
|
|
335
|
-
} else if (node.nodeType === 1) { // Node.ELEMENT_NODE
|
|
336
|
-
Array.from(node.childNodes).forEach(searchNodes);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
searchNodes(searchableContent);
|
|
341
|
-
|
|
342
|
-
// Update the total matches counter
|
|
343
|
-
setTotalMatches();
|
|
344
|
-
|
|
345
|
-
// Disable the back and forth buttons if there are no matches
|
|
346
|
-
handleBackAndForthButtonsDisabledState();
|
|
347
|
-
|
|
348
|
-
// Update the active match index
|
|
349
|
-
activeMatchIndex = -1;
|
|
350
|
-
|
|
351
|
-
// Scroll to the first match
|
|
352
|
-
// Using querySelector instead of querySelectorAll because we only want to select the first element
|
|
353
|
-
if (scrollToFirstMatch) {
|
|
354
|
-
let firstHighlight = document.querySelector('.' + matchesStyleSelectorClassName);
|
|
355
|
-
if (firstHighlight !== null) {
|
|
356
|
-
scrollToElementCenter(firstHighlight);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
}
|
|
362
|
-
|
|
363
|
-
document.addEventListener("DOMContentLoaded", function () {
|
|
364
|
-
inPageSearch();
|
|
365
|
-
});
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @file fetchTermsFromIndex.js
|
|
3
|
-
* @description Fetches terms and definitions from external repository's index.html
|
|
4
|
-
* @author Generated with assistance from GitHub Copilot
|
|
5
|
-
* @version 1.0.0
|
|
6
|
-
* @since 2025-04-15
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
const fs = require('fs');
|
|
10
|
-
const path = require('path');
|
|
11
|
-
const { JSDOM } = require('jsdom');
|
|
12
|
-
const axios = require('axios');
|
|
13
|
-
const { addPath, getPath, getAllPaths } = require('../../config/paths');
|
|
14
|
-
|
|
15
|
-
// Directory to store fetched data files
|
|
16
|
-
const CACHE_DIR = getPath('githubcache');
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Fetches the latest commit hash for a specific file in a repository
|
|
22
|
-
* @param {string} token - GitHub API Token
|
|
23
|
-
* @param {string} owner - Repository owner
|
|
24
|
-
* @param {string} repo - Repository name
|
|
25
|
-
* @param {string} filePath - Path to the file in the repository
|
|
26
|
-
* @param {object} headers - Request headers
|
|
27
|
-
* @returns {string|null} - Latest commit hash or null if error
|
|
28
|
-
*/
|
|
29
|
-
async function getFileCommitHash(token, owner, repo, filePath, headers) {
|
|
30
|
-
try {
|
|
31
|
-
// Normalize the file path to ensure it doesn't have leading slash
|
|
32
|
-
const normalizedPath = filePath.replace(/^\//, '');
|
|
33
|
-
|
|
34
|
-
// Construct API URL to get commits for the specific file
|
|
35
|
-
const commitsUrl = `https://api.github.com/repos/${owner}/${repo}/commits?path=${normalizedPath}&per_page=1`;
|
|
36
|
-
console.log(`Fetching latest commit for file: ${commitsUrl}`);
|
|
37
|
-
|
|
38
|
-
const response = await axios.get(commitsUrl, { headers });
|
|
39
|
-
|
|
40
|
-
if (response.status !== 200 || !response.data || response.data.length === 0) {
|
|
41
|
-
console.log(`❌ Could not find commit information for ${filePath}`);
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// Return the SHA of the latest commit
|
|
46
|
-
return response.data[0].sha;
|
|
47
|
-
} catch (error) {
|
|
48
|
-
console.error(`❌ Error fetching commit hash: ${error.message}`);
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/**
|
|
54
|
-
* Fetches all terms and definitions from a repository's GitHub Pages index.html
|
|
55
|
-
* @param {string} token - GitHub API Token
|
|
56
|
-
* @param {string} owner - Repository owner
|
|
57
|
-
* @param {string} repo - Repository name
|
|
58
|
-
* @param {object} options - Additional options including ghPageUrl
|
|
59
|
-
* @returns {object|null} - Object containing all terms or null if error
|
|
60
|
-
*/
|
|
61
|
-
async function fetchAllTermsFromIndex(token, owner, repo, options = {}) {
|
|
62
|
-
try {
|
|
63
|
-
// Configure headers for GitHub API
|
|
64
|
-
const headers = {};
|
|
65
|
-
if (token) {
|
|
66
|
-
headers['Authorization'] = `token ${token}`;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// Use GitHub Pages URL if provided in options, otherwise fallback to raw repository
|
|
70
|
-
let indexHtmlUrl;
|
|
71
|
-
let commitHash = null;
|
|
72
|
-
|
|
73
|
-
if (options.ghPageUrl) {
|
|
74
|
-
// Fetch from GitHub Pages (deployed HTML)
|
|
75
|
-
indexHtmlUrl = options.ghPageUrl.endsWith('/') ?
|
|
76
|
-
`${options.ghPageUrl}index.html` :
|
|
77
|
-
`${options.ghPageUrl}/index.html`;
|
|
78
|
-
console.log(`Fetching index.html from GitHub Pages: ${indexHtmlUrl}`);
|
|
79
|
-
|
|
80
|
-
// For GitHub Pages, we'll try to get the commit hash from the main branch
|
|
81
|
-
try {
|
|
82
|
-
const mainBranchUrl = `https://api.github.com/repos/${owner}/${repo}/branches/main`;
|
|
83
|
-
const branchResponse = await axios.get(mainBranchUrl, { headers });
|
|
84
|
-
if (branchResponse.status === 200) {
|
|
85
|
-
commitHash = branchResponse.data.commit.sha;
|
|
86
|
-
console.log(`✅ Got commit hash from main branch: ${commitHash}`);
|
|
87
|
-
}
|
|
88
|
-
} catch (error) {
|
|
89
|
-
console.log(`⚠️ Could not get commit hash from main branch: ${error.message}`);
|
|
90
|
-
}
|
|
91
|
-
} else {
|
|
92
|
-
// Fallback to raw repository method
|
|
93
|
-
console.log(`⚠️ No GitHub Pages URL provided, falling back to repository method`);
|
|
94
|
-
|
|
95
|
-
// Get the specs.json content from the repository to find the output_path
|
|
96
|
-
const specsJsonUrl = `https://api.github.com/repos/${owner}/${repo}/contents/specs.json`;
|
|
97
|
-
console.log(`Fetching specs.json from: ${specsJsonUrl}`);
|
|
98
|
-
|
|
99
|
-
// Fetch specs.json content
|
|
100
|
-
const specsJsonResponse = await axios.get(specsJsonUrl, { headers });
|
|
101
|
-
if (specsJsonResponse.status !== 200) {
|
|
102
|
-
console.log(`❌ Could not find specs.json in repository ${owner}/${repo}`);
|
|
103
|
-
return null;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Decode specs.json content from base64
|
|
107
|
-
const specsJsonContent = Buffer.from(specsJsonResponse.data.content, 'base64').toString('utf8');
|
|
108
|
-
const specsJson = JSON.parse(specsJsonContent);
|
|
109
|
-
|
|
110
|
-
// Get the output_path from specs.json
|
|
111
|
-
const outputPath = specsJson.specs[0].output_path;
|
|
112
|
-
if (!outputPath) {
|
|
113
|
-
console.log(`❌ No output_path found in specs.json for repository ${owner}/${repo}`);
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// Fix: Properly normalize the output path to ensure it doesn't have leading "./" or trailing "/"
|
|
118
|
-
const normalizedOutputPath = outputPath.replace(/^\.\//, '').replace(/\/$/, '');
|
|
119
|
-
|
|
120
|
-
// Create the path to the index.html file
|
|
121
|
-
const indexHtmlPath = `${normalizedOutputPath}/index.html`;
|
|
122
|
-
|
|
123
|
-
// Fetch the index.html content with properly constructed URL
|
|
124
|
-
indexHtmlUrl = `https://raw.githubusercontent.com/${owner}/${repo}/main/${indexHtmlPath}`;
|
|
125
|
-
console.log(`Fetching index.html from raw repository: ${indexHtmlUrl}`);
|
|
126
|
-
|
|
127
|
-
// Get the commit hash for the index.html file
|
|
128
|
-
commitHash = await getFileCommitHash(token, owner, repo, indexHtmlPath, headers);
|
|
129
|
-
if (!commitHash) {
|
|
130
|
-
console.log(`⚠️ Could not get commit hash for index.html, continuing without it`);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Fetch the index.html content
|
|
135
|
-
const indexHtmlResponse = await axios.get(indexHtmlUrl, { headers });
|
|
136
|
-
if (indexHtmlResponse.status !== 200) {
|
|
137
|
-
console.log(`❌ Could not find index.html at ${indexHtmlUrl}`);
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const htmlContent = indexHtmlResponse.data;
|
|
142
|
-
|
|
143
|
-
// Parse HTML using JSDOM
|
|
144
|
-
const dom = new JSDOM(htmlContent);
|
|
145
|
-
const document = dom.window.document;
|
|
146
|
-
|
|
147
|
-
// Find all term definition lists with class "terms-and-definitions-list"
|
|
148
|
-
const termDlList = document.querySelector('dl.terms-and-definitions-list');
|
|
149
|
-
if (!termDlList) {
|
|
150
|
-
console.log(`❌ No terms-and-definitions-list found in ${indexHtmlUrl}`);
|
|
151
|
-
return null;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// Extract all terms and definitions
|
|
155
|
-
const terms = [];
|
|
156
|
-
let dtElements = termDlList.querySelectorAll('dt');
|
|
157
|
-
|
|
158
|
-
dtElements.forEach(dt => {
|
|
159
|
-
// Find the term span that starts with id="term:
|
|
160
|
-
const termSpan = dt.querySelector('span[id^="term:"]');
|
|
161
|
-
if (!termSpan) return;
|
|
162
|
-
|
|
163
|
-
// Get the term text (all text content, excluding nested spans)
|
|
164
|
-
let termText = '';
|
|
165
|
-
for (let node of termSpan.childNodes) {
|
|
166
|
-
if (node.nodeType === dom.window.Node.TEXT_NODE) {
|
|
167
|
-
termText += node.textContent.trim();
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// If no text found, try to get the full text content
|
|
172
|
-
if (!termText) {
|
|
173
|
-
termText = termSpan.textContent.trim();
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Skip empty terms
|
|
177
|
-
if (!termText) return;
|
|
178
|
-
|
|
179
|
-
// Find all corresponding definition elements
|
|
180
|
-
let dds = [];
|
|
181
|
-
let nextElement = dt.nextElementSibling;
|
|
182
|
-
|
|
183
|
-
// Collect all consecutive <dd> elements until we reach another <dt>
|
|
184
|
-
while (nextElement && nextElement.tagName.toLowerCase() === 'dd') {
|
|
185
|
-
dds.push(nextElement.outerHTML);
|
|
186
|
-
nextElement = nextElement.nextElementSibling;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
terms.push({
|
|
190
|
-
term: termText,
|
|
191
|
-
definition: dds.join('\n')
|
|
192
|
-
});
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
// Store all terms in a JSON file with timestamp
|
|
196
|
-
const timestamp = Date.now();
|
|
197
|
-
const outputDir = path.join(CACHE_DIR);
|
|
198
|
-
if (!fs.existsSync(outputDir)) {
|
|
199
|
-
fs.mkdirSync(outputDir, { recursive: true });
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
// Create output filename with timestamp
|
|
203
|
-
const outputFileName = `${timestamp}-${owner}-${repo}-terms.json`;
|
|
204
|
-
const outputFilePath = path.join(outputDir, outputFileName);
|
|
205
|
-
|
|
206
|
-
// Create the result object
|
|
207
|
-
const result = {
|
|
208
|
-
timestamp,
|
|
209
|
-
repository: `${owner}/${repo}`,
|
|
210
|
-
terms,
|
|
211
|
-
sha: commitHash, // Use the commit hash of the index.html file
|
|
212
|
-
avatarUrl: null,
|
|
213
|
-
outputFileName
|
|
214
|
-
};
|
|
215
|
-
|
|
216
|
-
// Save all terms to file
|
|
217
|
-
fs.writeFileSync(outputFilePath, JSON.stringify(result, null, 2));
|
|
218
|
-
console.log(`✅ Saved ${terms.length} terms to ${outputFilePath}`);
|
|
219
|
-
|
|
220
|
-
return result;
|
|
221
|
-
|
|
222
|
-
} catch (error) {
|
|
223
|
-
if (error.response) {
|
|
224
|
-
if (error.response.status === 404) {
|
|
225
|
-
console.log(`❌ Resource not found: ${error.config.url}`);
|
|
226
|
-
} else if (error.response.status === 403 && error.response.headers['x-ratelimit-remaining'] === '0') {
|
|
227
|
-
const resetTime = new Date(parseInt(error.response.headers['x-ratelimit-reset']) * 1000);
|
|
228
|
-
console.error(`❌ GitHub API rate limit exceeded. Try again after ${resetTime.toLocaleString()}`);
|
|
229
|
-
} else {
|
|
230
|
-
console.error(`❌ Error fetching data: ${error.response.status} ${error.response.statusText}`);
|
|
231
|
-
}
|
|
232
|
-
} else {
|
|
233
|
-
console.error(`❌ Error fetching term: ${error.message}`);
|
|
234
|
-
}
|
|
235
|
-
return null;
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
/**
|
|
240
|
-
* Fetches a specific term from repository's index.html
|
|
241
|
-
* This is a wrapper that uses fetchAllTermsFromIndex for efficiency
|
|
242
|
-
* @param {string} token - GitHub API Token
|
|
243
|
-
* @param {string} term - The specific term to look for
|
|
244
|
-
* @param {string} owner - Repository owner
|
|
245
|
-
* @param {string} repo - Repository name
|
|
246
|
-
* @param {string} termsDir - Directory containing term definitions (not used in this implementation)
|
|
247
|
-
* @param {object} options - Additional options
|
|
248
|
-
* @returns {object|null} - Found term data or null if not found
|
|
249
|
-
*/
|
|
250
|
-
async function fetchTermsFromIndex(token, term, owner, repo, termsDir, options = {}) {
|
|
251
|
-
// First get all terms from the repository (which is cached)
|
|
252
|
-
const allTermsData = await fetchAllTermsFromIndex(token, owner, repo, options);
|
|
253
|
-
|
|
254
|
-
if (!allTermsData || !allTermsData.terms) {
|
|
255
|
-
return null;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Find the specific term
|
|
259
|
-
const foundTerm = allTermsData.terms.find(t => t.term.toLowerCase() === term.toLowerCase());
|
|
260
|
-
|
|
261
|
-
if (foundTerm) {
|
|
262
|
-
console.log(`Found term '${term}' in repository ${owner}/${repo}`);
|
|
263
|
-
return {
|
|
264
|
-
term: foundTerm.term,
|
|
265
|
-
content: foundTerm.definition,
|
|
266
|
-
sha: allTermsData.sha,
|
|
267
|
-
repository: {
|
|
268
|
-
owner: {
|
|
269
|
-
login: owner,
|
|
270
|
-
avatar_url: allTermsData.avatarUrl
|
|
271
|
-
},
|
|
272
|
-
name: repo
|
|
273
|
-
}
|
|
274
|
-
};
|
|
275
|
-
} else {
|
|
276
|
-
console.log(`❌ Term "${term}" not found in repository ${owner}/${repo}`);
|
|
277
|
-
return null;
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
module.exports = {
|
|
282
|
-
fetchTermsFromIndex,
|
|
283
|
-
fetchAllTermsFromIndex // Export the function to fetch all terms as well
|
|
284
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
const isLineWithDefinition = require('../utils/isLineWithDefinition').isLineWithDefinition;
|
|
2
|
-
|
|
3
|
-
function matchTerm(text, term) {
|
|
4
|
-
if (!text || typeof text !== 'string') {
|
|
5
|
-
console.log('Nothing to match for term:', term);
|
|
6
|
-
return false;
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
const firstLine = text.split('\n')[0].trim();
|
|
10
|
-
|
|
11
|
-
if (isLineWithDefinition(firstLine) === false) {
|
|
12
|
-
console.log('String does not start with `[[def:` or end with `]]`');
|
|
13
|
-
return false;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
// Find the closing bracket position instead of assuming it's at the end
|
|
17
|
-
const startPos = firstLine.indexOf('[[def:') + 6;
|
|
18
|
-
const endPos = firstLine.indexOf(']]');
|
|
19
|
-
|
|
20
|
-
if (startPos === -1 || endPos === -1) return false;
|
|
21
|
-
|
|
22
|
-
// Extract text between [[def: and ]]
|
|
23
|
-
let relevantPart = firstLine.substring(startPos, endPos);
|
|
24
|
-
|
|
25
|
-
// Split the string on `,` and trim the array elements
|
|
26
|
-
let termsArray = relevantPart.split(',').map(term => term.trim());
|
|
27
|
-
|
|
28
|
-
// Check if the term is in the array
|
|
29
|
-
return termsArray.includes(term);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
exports.matchTerm = matchTerm;
|