spec-up-t 1.3.0 → 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.
Files changed (150) hide show
  1. package/.github/copilot-instructions.md +13 -0
  2. package/assets/compiled/body.js +18 -12
  3. package/assets/compiled/head.css +8 -6
  4. package/assets/css/collapse-definitions.css +0 -1
  5. package/assets/css/counter.css +10 -22
  6. package/assets/css/create-pdf.css +4 -2
  7. package/assets/css/create-term-filter.css +4 -4
  8. package/assets/css/definition-buttons-container.css +60 -0
  9. package/assets/css/{pdf-download.css → download-pdf-docx.css} +9 -5
  10. package/assets/css/insert-trefs.css +7 -0
  11. package/assets/css/sidebar-toc.css +2 -1
  12. package/assets/css/terms-and-definitions.css +73 -22
  13. package/assets/js/add-href-to-snapshot-link.js +16 -9
  14. package/assets/js/addAnchorsToTerms.js +2 -2
  15. package/assets/js/charts.js +10 -0
  16. package/assets/js/collapse-definitions.js +13 -2
  17. package/assets/js/collapse-meta-info.js +11 -9
  18. package/assets/js/definition-button-container-utils.js +82 -0
  19. package/assets/js/download-pdf-docx.js +68 -0
  20. package/assets/js/edit-term-buttons.js +77 -20
  21. package/assets/js/github-issues.js +35 -0
  22. package/assets/js/github-repo-info.js +144 -0
  23. package/assets/js/highlight-heading-plus-sibling-nodes.test.js +18 -0
  24. package/assets/js/insert-trefs.js +62 -13
  25. package/assets/js/mermaid-diagrams.js +11 -0
  26. package/assets/js/terminology-section-utility-container/README.md +107 -0
  27. package/assets/js/terminology-section-utility-container/create-alphabet-index.js +17 -0
  28. package/assets/js/{create-term-filter.js → terminology-section-utility-container/create-term-filter.js} +11 -44
  29. package/assets/js/terminology-section-utility-container/hide-show-utility-container.js +21 -0
  30. package/assets/js/terminology-section-utility-container/search.js +203 -0
  31. package/assets/js/terminology-section-utility-container.js +203 -0
  32. package/assets/js/tooltips.js +283 -0
  33. package/config/asset-map.json +26 -18
  34. package/index.js +57 -390
  35. package/package.json +5 -2
  36. package/src/add-remove-xref-source.js +20 -21
  37. package/src/collect-external-references.js +8 -337
  38. package/src/collect-external-references.test.js +440 -33
  39. package/src/configure.js +8 -109
  40. package/src/create-docx.js +7 -6
  41. package/src/create-pdf.js +15 -14
  42. package/src/freeze-spec-data.js +46 -0
  43. package/src/git-info.test.js +76 -0
  44. package/src/health-check/destination-gitignore-checker.js +5 -3
  45. package/src/health-check/external-specs-checker.js +5 -4
  46. package/src/health-check/specs-configuration-checker.js +2 -1
  47. package/src/health-check/term-references-checker.js +5 -3
  48. package/src/health-check/terms-intro-checker.js +2 -1
  49. package/src/health-check/tref-term-checker.js +8 -7
  50. package/src/health-check.js +8 -7
  51. package/src/init.js +3 -2
  52. package/src/install-from-boilerplate/add-gitignore-entries.js +3 -2
  53. package/src/install-from-boilerplate/add-scripts-keys.js +5 -4
  54. package/src/install-from-boilerplate/boilerplate/.github/workflows/menu.yml +74 -97
  55. package/src/install-from-boilerplate/boilerplate/README.md +1 -1
  56. package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +1 -1
  57. package/src/install-from-boilerplate/boilerplate/spec/spec-head.md +2 -2
  58. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/composability.md +3 -0
  59. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/compost.md +3 -0
  60. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/fertilizer.md +3 -0
  61. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/mulch.md +3 -0
  62. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/pruning.md +3 -0
  63. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/seedling.md +3 -0
  64. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/soil.md +11 -0
  65. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/watering.md +3 -0
  66. package/src/install-from-boilerplate/boilerplate/specs.json +24 -10
  67. package/src/install-from-boilerplate/config-scripts-keys.js +3 -3
  68. package/src/install-from-boilerplate/config-system-files.js +0 -1
  69. package/src/install-from-boilerplate/copy-boilerplate.js +2 -1
  70. package/src/install-from-boilerplate/copy-system-files.js +4 -3
  71. package/src/install-from-boilerplate/custom-update.js +12 -1
  72. package/src/install-from-boilerplate/help.txt +1 -1
  73. package/src/install-from-boilerplate/menu.sh +6 -6
  74. package/src/json-key-validator.js +17 -11
  75. package/src/markdown-it/README.md +207 -0
  76. package/src/markdown-it/definition-lists.js +397 -0
  77. package/src/markdown-it/index.js +83 -0
  78. package/src/markdown-it/link-enhancement.js +98 -0
  79. package/src/markdown-it/plugins.js +118 -0
  80. package/src/markdown-it/table-enhancement.js +97 -0
  81. package/src/markdown-it/template-tag-syntax.js +152 -0
  82. package/src/parsers/index.js +16 -0
  83. package/src/parsers/spec-parser.js +152 -0
  84. package/src/parsers/spec-parser.test.js +109 -0
  85. package/src/parsers/template-tag-parser.js +277 -0
  86. package/src/parsers/template-tag-parser.test.js +107 -0
  87. package/src/pipeline/configuration/configure-starterpack.js +200 -0
  88. package/src/{create-external-specs-list.js → pipeline/configuration/create-external-specs-list.js} +13 -12
  89. package/src/{create-term-index.js → pipeline/configuration/create-term-index.js} +19 -18
  90. package/src/{create-versions-index.js → pipeline/configuration/create-versions-index.js} +4 -3
  91. package/src/{insert-term-index.js → pipeline/configuration/insert-term-index.js} +2 -2
  92. package/src/pipeline/configuration/prepare-spec-configuration.js +70 -0
  93. package/src/pipeline/parsing/apply-markdown-it-extensions.js +35 -0
  94. package/src/pipeline/parsing/create-markdown-parser.js +94 -0
  95. package/src/pipeline/parsing/create-markdown-parser.test.js +49 -0
  96. package/src/{html-dom-processor.js → pipeline/postprocessing/definition-list-postprocessor.js} +69 -10
  97. package/src/{escape-handler.js → pipeline/preprocessing/escape-processor.js} +3 -1
  98. package/src/{fix-markdown-files.js → pipeline/preprocessing/normalize-terminology-markdown.js} +41 -31
  99. package/src/pipeline/references/collect-external-references.js +307 -0
  100. package/src/pipeline/references/external-references-service.js +231 -0
  101. package/src/pipeline/references/fetch-terms-from-index.js +198 -0
  102. package/src/pipeline/references/match-term.js +34 -0
  103. package/src/{collectExternalReferences/matchTerm.test.js → pipeline/references/match-term.test.js} +8 -2
  104. package/src/pipeline/references/process-xtrefs-data.js +94 -0
  105. package/src/pipeline/references/xtref-utils.js +166 -0
  106. package/src/pipeline/rendering/render-spec-document.js +146 -0
  107. package/src/pipeline/rendering/render-utils.js +154 -0
  108. package/src/utils/LOGGER.md +81 -0
  109. package/src/utils/{doesUrlExist.js → does-url-exist.js} +4 -3
  110. package/src/utils/fetch.js +5 -4
  111. package/src/utils/file-opener.js +3 -2
  112. package/src/utils/git-info.js +77 -0
  113. package/src/utils/logger.js +74 -0
  114. package/src/utils/regex-patterns.js +471 -0
  115. package/src/utils/regex-patterns.test.js +281 -0
  116. package/templates/template.html +56 -21
  117. package/assets/js/create-alphabet-index.js +0 -60
  118. package/assets/js/hide-show-utility-container.js +0 -16
  119. package/assets/js/index.js +0 -87
  120. package/assets/js/pdf-download.js +0 -46
  121. package/assets/js/search.js +0 -365
  122. package/src/collectExternalReferences/fetchTermsFromIndex.js +0 -284
  123. package/src/collectExternalReferences/matchTerm.js +0 -32
  124. package/src/collectExternalReferences/processXTrefsData.js +0 -108
  125. package/src/freeze.js +0 -90
  126. package/src/install-from-boilerplate/boilerplate/.github/workflows/fetch-and-push-xrefs.yml.old +0 -42
  127. package/src/install-from-boilerplate/boilerplate/.github/workflows/render-specs.yml +0 -47
  128. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/term-1.md +0 -13
  129. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/term-2.md +0 -3
  130. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/term-3.md +0 -3
  131. package/src/install-from-boilerplate/boilerplate/spec/terms-definitions/term-4.md +0 -3
  132. package/src/markdown-it-extensions.js +0 -395
  133. package/src/references.js +0 -114
  134. /package/assets/css/{bootstrap.min.css → embedded-libraries/bootstrap.min.css} +0 -0
  135. /package/assets/css/{prism.css → embedded-libraries/prism.css} +0 -0
  136. /package/assets/css/{prism.dark.css → embedded-libraries/prism.dark.css} +0 -0
  137. /package/assets/css/{prism.default.css → embedded-libraries/prism.default.css} +0 -0
  138. /package/assets/js/{bootstrap.bundle.min.js → embedded-libraries/bootstrap.bundle.min.js} +0 -0
  139. /package/assets/js/{chart.js → embedded-libraries/chart.js} +0 -0
  140. /package/assets/js/{diff.min.js → embedded-libraries/diff.min.js} +0 -0
  141. /package/assets/js/{font-awesome.js → embedded-libraries/font-awesome.js} +0 -0
  142. /package/assets/js/{mermaid.js → embedded-libraries/mermaid.js} +0 -0
  143. /package/assets/js/{notyf.js → embedded-libraries/notyf.js} +0 -0
  144. /package/assets/js/{popper.js → embedded-libraries/popper.js} +0 -0
  145. /package/assets/js/{prism.dark.js → embedded-libraries/prism.dark.js} +0 -0
  146. /package/assets/js/{prism.default.js → embedded-libraries/prism.default.js} +0 -0
  147. /package/assets/js/{prism.js → embedded-libraries/prism.js} +0 -0
  148. /package/assets/js/{tippy.js → embedded-libraries/tippy.js} +0 -0
  149. /package/src/{escape-mechanism.js → pipeline/preprocessing/escape-placeholder-utils.js} +0 -0
  150. /package/src/utils/{isLineWithDefinition.js → is-line-with-definition.js} +0 -0
@@ -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;