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.
Files changed (130) hide show
  1. package/.github/copilot-instructions.md +13 -0
  2. package/assets/compiled/body.js +17 -11
  3. package/assets/compiled/head.css +6 -4
  4. package/assets/css/collapse-definitions.css +0 -1
  5. package/assets/css/create-pdf.css +4 -2
  6. package/assets/css/create-term-filter.css +4 -4
  7. package/assets/css/definition-buttons-container.css +60 -0
  8. package/assets/css/insert-trefs.css +7 -0
  9. package/assets/css/sidebar-toc.css +2 -1
  10. package/assets/css/terms-and-definitions.css +73 -22
  11. package/assets/js/add-href-to-snapshot-link.js +16 -9
  12. package/assets/js/addAnchorsToTerms.js +1 -1
  13. package/assets/js/charts.js +10 -0
  14. package/assets/js/collapse-definitions.js +13 -2
  15. package/assets/js/collapse-meta-info.js +11 -9
  16. package/assets/js/definition-button-container-utils.js +82 -0
  17. package/assets/js/edit-term-buttons.js +77 -20
  18. package/assets/js/github-issues.js +35 -0
  19. package/assets/js/github-repo-info.js +144 -0
  20. package/assets/js/highlight-heading-plus-sibling-nodes.test.js +18 -0
  21. package/assets/js/insert-trefs.js +62 -13
  22. package/assets/js/mermaid-diagrams.js +11 -0
  23. package/assets/js/terminology-section-utility-container/README.md +107 -0
  24. package/assets/js/terminology-section-utility-container/create-alphabet-index.js +17 -0
  25. package/assets/js/{create-term-filter.js → terminology-section-utility-container/create-term-filter.js} +11 -44
  26. package/assets/js/terminology-section-utility-container/hide-show-utility-container.js +21 -0
  27. package/assets/js/terminology-section-utility-container/search.js +203 -0
  28. package/assets/js/terminology-section-utility-container.js +203 -0
  29. package/assets/js/tooltips.js +283 -0
  30. package/config/asset-map.json +24 -16
  31. package/index.js +57 -390
  32. package/package.json +5 -2
  33. package/src/add-remove-xref-source.js +20 -21
  34. package/src/collect-external-references.js +8 -337
  35. package/src/collect-external-references.test.js +440 -33
  36. package/src/configure.js +8 -109
  37. package/src/create-docx.js +7 -6
  38. package/src/create-pdf.js +15 -14
  39. package/src/freeze-spec-data.js +46 -0
  40. package/src/git-info.test.js +76 -0
  41. package/src/health-check/destination-gitignore-checker.js +5 -3
  42. package/src/health-check/external-specs-checker.js +5 -4
  43. package/src/health-check/specs-configuration-checker.js +2 -1
  44. package/src/health-check/term-references-checker.js +5 -3
  45. package/src/health-check/terms-intro-checker.js +2 -1
  46. package/src/health-check/tref-term-checker.js +8 -7
  47. package/src/health-check.js +8 -7
  48. package/src/init.js +3 -2
  49. package/src/install-from-boilerplate/add-gitignore-entries.js +3 -2
  50. package/src/install-from-boilerplate/add-scripts-keys.js +5 -4
  51. package/src/install-from-boilerplate/boilerplate/README.md +1 -1
  52. package/src/install-from-boilerplate/boilerplate/spec/example-markup-in-markdown.md +1 -1
  53. package/src/install-from-boilerplate/boilerplate/spec/spec-head.md +1 -1
  54. package/src/install-from-boilerplate/boilerplate/specs.json +2 -1
  55. package/src/install-from-boilerplate/config-scripts-keys.js +3 -3
  56. package/src/install-from-boilerplate/copy-boilerplate.js +2 -1
  57. package/src/install-from-boilerplate/copy-system-files.js +4 -3
  58. package/src/install-from-boilerplate/custom-update.js +12 -1
  59. package/src/install-from-boilerplate/help.txt +1 -1
  60. package/src/install-from-boilerplate/menu.sh +6 -6
  61. package/src/json-key-validator.js +17 -11
  62. package/src/markdown-it/README.md +207 -0
  63. package/src/markdown-it/definition-lists.js +397 -0
  64. package/src/markdown-it/index.js +83 -0
  65. package/src/markdown-it/link-enhancement.js +98 -0
  66. package/src/markdown-it/plugins.js +118 -0
  67. package/src/markdown-it/table-enhancement.js +97 -0
  68. package/src/markdown-it/template-tag-syntax.js +152 -0
  69. package/src/parsers/index.js +16 -0
  70. package/src/parsers/spec-parser.js +152 -0
  71. package/src/parsers/spec-parser.test.js +109 -0
  72. package/src/parsers/template-tag-parser.js +277 -0
  73. package/src/parsers/template-tag-parser.test.js +107 -0
  74. package/src/pipeline/configuration/configure-starterpack.js +200 -0
  75. package/src/{create-external-specs-list.js → pipeline/configuration/create-external-specs-list.js} +13 -12
  76. package/src/{create-term-index.js → pipeline/configuration/create-term-index.js} +19 -18
  77. package/src/{create-versions-index.js → pipeline/configuration/create-versions-index.js} +4 -3
  78. package/src/{insert-term-index.js → pipeline/configuration/insert-term-index.js} +2 -2
  79. package/src/pipeline/configuration/prepare-spec-configuration.js +70 -0
  80. package/src/pipeline/parsing/apply-markdown-it-extensions.js +35 -0
  81. package/src/pipeline/parsing/create-markdown-parser.js +94 -0
  82. package/src/pipeline/parsing/create-markdown-parser.test.js +49 -0
  83. package/src/{html-dom-processor.js → pipeline/postprocessing/definition-list-postprocessor.js} +69 -10
  84. package/src/{escape-handler.js → pipeline/preprocessing/escape-processor.js} +3 -1
  85. package/src/{fix-markdown-files.js → pipeline/preprocessing/normalize-terminology-markdown.js} +41 -31
  86. package/src/pipeline/references/collect-external-references.js +307 -0
  87. package/src/pipeline/references/external-references-service.js +231 -0
  88. package/src/pipeline/references/fetch-terms-from-index.js +198 -0
  89. package/src/pipeline/references/match-term.js +34 -0
  90. package/src/{collectExternalReferences/matchTerm.test.js → pipeline/references/match-term.test.js} +8 -2
  91. package/src/pipeline/references/process-xtrefs-data.js +94 -0
  92. package/src/pipeline/references/xtref-utils.js +166 -0
  93. package/src/pipeline/rendering/render-spec-document.js +146 -0
  94. package/src/pipeline/rendering/render-utils.js +154 -0
  95. package/src/utils/LOGGER.md +81 -0
  96. package/src/utils/{doesUrlExist.js → does-url-exist.js} +4 -3
  97. package/src/utils/fetch.js +5 -4
  98. package/src/utils/file-opener.js +3 -2
  99. package/src/utils/git-info.js +77 -0
  100. package/src/utils/logger.js +74 -0
  101. package/src/utils/regex-patterns.js +471 -0
  102. package/src/utils/regex-patterns.test.js +281 -0
  103. package/templates/template.html +56 -21
  104. package/assets/js/create-alphabet-index.js +0 -60
  105. package/assets/js/hide-show-utility-container.js +0 -16
  106. package/assets/js/index.js +0 -87
  107. package/assets/js/search.js +0 -365
  108. package/src/collectExternalReferences/fetchTermsFromIndex.js +0 -284
  109. package/src/collectExternalReferences/matchTerm.js +0 -32
  110. package/src/collectExternalReferences/processXTrefsData.js +0 -108
  111. package/src/freeze.js +0 -90
  112. package/src/markdown-it-extensions.js +0 -395
  113. package/src/references.js +0 -114
  114. /package/assets/css/{bootstrap.min.css → embedded-libraries/bootstrap.min.css} +0 -0
  115. /package/assets/css/{prism.css → embedded-libraries/prism.css} +0 -0
  116. /package/assets/css/{prism.dark.css → embedded-libraries/prism.dark.css} +0 -0
  117. /package/assets/css/{prism.default.css → embedded-libraries/prism.default.css} +0 -0
  118. /package/assets/js/{bootstrap.bundle.min.js → embedded-libraries/bootstrap.bundle.min.js} +0 -0
  119. /package/assets/js/{chart.js → embedded-libraries/chart.js} +0 -0
  120. /package/assets/js/{diff.min.js → embedded-libraries/diff.min.js} +0 -0
  121. /package/assets/js/{font-awesome.js → embedded-libraries/font-awesome.js} +0 -0
  122. /package/assets/js/{mermaid.js → embedded-libraries/mermaid.js} +0 -0
  123. /package/assets/js/{notyf.js → embedded-libraries/notyf.js} +0 -0
  124. /package/assets/js/{popper.js → embedded-libraries/popper.js} +0 -0
  125. /package/assets/js/{prism.dark.js → embedded-libraries/prism.dark.js} +0 -0
  126. /package/assets/js/{prism.default.js → embedded-libraries/prism.default.js} +0 -0
  127. /package/assets/js/{prism.js → embedded-libraries/prism.js} +0 -0
  128. /package/assets/js/{tippy.js → embedded-libraries/tippy.js} +0 -0
  129. /package/src/{escape-mechanism.js → pipeline/preprocessing/escape-placeholder-utils.js} +0 -0
  130. /package/src/utils/{isLineWithDefinition.js → is-line-with-definition.js} +0 -0
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @file Search functionality for terminology section
3
+ * @author Kor Dwarshuis
4
+ * @version 1.0.0
5
+ * @since 2024-08-31
6
+ * @description Handles search logic and highlighting (DOM is constructed in main module)
7
+ */
8
+
9
+ /**
10
+ * Attaches search functionality to the provided DOM elements
11
+ * @param {HTMLInputElement} searchInput - The search input element
12
+ * @param {HTMLButtonElement} goToPreviousMatchButton - Previous match button
13
+ * @param {HTMLButtonElement} goToNextMatchButton - Next match button
14
+ * @param {HTMLSpanElement} totalMatchesSpan - Matches counter element
15
+ * @returns {void}
16
+ */
17
+ function attachSearchFunctionality(searchInput, goToPreviousMatchButton, goToNextMatchButton, totalMatchesSpan) {
18
+ // Check if the terms and definitions list exists
19
+ const termsListElement = document.querySelector(".terms-and-definitions-list");
20
+ const dtElements = termsListElement ? termsListElement.querySelectorAll("dt") : [];
21
+
22
+ if (dtElements.length === 0) {
23
+ return;
24
+ }
25
+
26
+ /*****************/
27
+ /* CONFIGURATION */
28
+ const matchesStyle = specConfig.searchHighlightStyle || 'ssi';
29
+ const debounceTime = 600;
30
+ const matches = 'matches';
31
+ const searchableContent = document.querySelector('.terms-and-definitions-list');
32
+
33
+ // Styling of search matches
34
+ const matchesStyleSelector = {
35
+ dif: 'highlight-matches-DIF-search',
36
+ toip: 'highlight-matches-ToIP-search',
37
+ btc: 'highlight-matches-BTC-search',
38
+ keri: 'highlight-matches-KERI-search',
39
+ ssi: 'highlight-matches-SSI-search',
40
+ gleif: 'highlight-matches-GLEIF-search'
41
+ };
42
+
43
+ const matchesClassName = "highlight-matches-search";
44
+ const matchesStyleSelectorClassName = matchesStyleSelector[matchesStyle.toLowerCase()];
45
+
46
+ let totalMatches = 0;
47
+ let activeMatchIndex = -1;
48
+ let debounceTimeout;
49
+
50
+ /* Helper functions */
51
+ function setTotalMatches() {
52
+ totalMatches = document.querySelectorAll('.' + matchesClassName).length;
53
+ totalMatchesSpan.innerHTML = `${totalMatches} ${matches}`;
54
+ }
55
+
56
+ function handleBackAndForthButtonsDisabledState() {
57
+ const hasMatches = totalMatches > 0;
58
+ goToPreviousMatchButton.disabled = !hasMatches;
59
+ goToNextMatchButton.disabled = !hasMatches;
60
+ }
61
+
62
+ function scrollToElementCenter(element) {
63
+ if (element) {
64
+ element.scrollIntoView({ behavior: 'smooth', block: 'center' });
65
+ }
66
+ }
67
+
68
+ function removeHighlights() {
69
+ const highlighted = document.querySelectorAll('.' + matchesClassName);
70
+ highlighted.forEach(element => {
71
+ const parent = element.parentNode;
72
+ parent.replaceChild(document.createTextNode(element.textContent), element);
73
+ parent.normalize();
74
+ });
75
+ }
76
+
77
+ function highlightMatches(query) {
78
+ if (!query.trim()) {
79
+ removeHighlights();
80
+ setTotalMatches();
81
+ handleBackAndForthButtonsDisabledState();
82
+ return;
83
+ }
84
+
85
+ removeHighlights();
86
+
87
+ const regex = new RegExp(`(${query.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')})`, 'gi');
88
+
89
+ function searchNodes(node) {
90
+ if (node.nodeType === 3) { // Text node
91
+ const text = node.textContent;
92
+ if (regex.test(text)) {
93
+ const fragments = document.createDocumentFragment();
94
+ let lastIndex = 0;
95
+ let match;
96
+
97
+ regex.lastIndex = 0; // Reset regex
98
+ while ((match = regex.exec(text)) !== null) {
99
+ // Add text before match
100
+ if (match.index > lastIndex) {
101
+ fragments.appendChild(document.createTextNode(text.slice(lastIndex, match.index)));
102
+ }
103
+
104
+ // Add highlighted match
105
+ const span = document.createElement('span');
106
+ span.className = `${matchesClassName} ${matchesStyleSelectorClassName}`;
107
+ span.textContent = match[0];
108
+ fragments.appendChild(span);
109
+
110
+ lastIndex = regex.lastIndex;
111
+ }
112
+
113
+ // Add remaining text
114
+ if (lastIndex < text.length) {
115
+ fragments.appendChild(document.createTextNode(text.slice(lastIndex)));
116
+ }
117
+
118
+ node.parentNode.replaceChild(fragments, node);
119
+ }
120
+ } else if (node.nodeType === 1) { // Element node
121
+ Array.from(node.childNodes).forEach(searchNodes);
122
+ }
123
+ }
124
+
125
+ searchNodes(searchableContent);
126
+ setTotalMatches();
127
+ handleBackAndForthButtonsDisabledState();
128
+ activeMatchIndex = -1;
129
+
130
+ // Scroll to first match
131
+ const firstHighlight = document.querySelector('.' + matchesClassName);
132
+ if (firstHighlight) {
133
+ scrollToElementCenter(firstHighlight);
134
+ }
135
+ }
136
+
137
+ function debouncedSearchAndHighlight(query, shouldScrollToFirstMatch = false) {
138
+ clearTimeout(debounceTimeout);
139
+ debounceTimeout = setTimeout(() => {
140
+ highlightMatches(query);
141
+ }, debounceTime);
142
+ }
143
+
144
+ function navigateMatches(direction) {
145
+ const allMatches = document.querySelectorAll('.' + matchesClassName);
146
+ if (allMatches.length === 0) return;
147
+
148
+ if (direction === 'next') {
149
+ activeMatchIndex = (activeMatchIndex + 1) % allMatches.length;
150
+ } else {
151
+ activeMatchIndex = activeMatchIndex <= 0 ? allMatches.length - 1 : activeMatchIndex - 1;
152
+ }
153
+
154
+ // Remove previous active styling
155
+ allMatches.forEach(match => match.classList.remove('active'));
156
+
157
+ // Add active styling and scroll to current match
158
+ allMatches[activeMatchIndex].classList.add('active');
159
+ scrollToElementCenter(allMatches[activeMatchIndex]);
160
+ }
161
+
162
+ /* Event listeners */
163
+ searchInput.addEventListener("input", function () {
164
+ debouncedSearchAndHighlight(searchInput.value, true);
165
+ });
166
+
167
+ goToNextMatchButton.addEventListener('click', () => navigateMatches('next'));
168
+ goToPreviousMatchButton.addEventListener('click', () => navigateMatches('prev'));
169
+
170
+ // Global keyboard navigation (Arrow keys work anywhere on the page)
171
+ document.addEventListener('keyup', (event) => {
172
+ if (totalMatches > 0) {
173
+ switch (event.key) {
174
+ case "ArrowRight":
175
+ goToNextMatchButton.click(); // Simulate a click on button
176
+ break;
177
+ case "ArrowLeft":
178
+ goToPreviousMatchButton.click(); // Simulate a click on button
179
+ break;
180
+ }
181
+ }
182
+ });
183
+
184
+ // Keyboard navigation when search input is focused
185
+ document.addEventListener('keydown', function(event) {
186
+ if (document.activeElement === searchInput) {
187
+ if (event.key === 'ArrowDown' && totalMatches > 0) {
188
+ event.preventDefault();
189
+ navigateMatches('next');
190
+ } else if (event.key === 'ArrowUp' && totalMatches > 0) {
191
+ event.preventDefault();
192
+ navigateMatches('prev');
193
+ }
194
+ }
195
+ });
196
+
197
+ // Re-run search when definitions are collapsed/expanded
198
+ document.addEventListener('click', event => {
199
+ if (event.target.classList.contains('collapse-all-defs-button')) {
200
+ debouncedSearchAndHighlight(searchInput.value, true);
201
+ }
202
+ });
203
+ }
@@ -0,0 +1,203 @@
1
+ /**
2
+ * @file Main module for terminology section utility container functionality
3
+ * @author Kor Dwarshuis
4
+ * @version 1.0.0
5
+ * @since 2024-08-31
6
+ * @description Coordinates all terminology section utility container components
7
+ */
8
+
9
+ /**
10
+ * Initializes the complete terminology section utility container
11
+ * This function coordinates all the components in the correct order
12
+ */
13
+ function initializeTerminologyUtilityContainer() {
14
+ // Check if the terms and definitions list exists
15
+ const termsListElement = document.querySelector(".terms-and-definitions-list");
16
+ const dtElements = termsListElement ? termsListElement.querySelectorAll("dt") : [];
17
+
18
+ if (dtElements.length === 0) {
19
+ // Hide the container if no terms exist
20
+ hideShowUtilityContainer();
21
+ return;
22
+ }
23
+
24
+ const terminologySectionUtilityContainer = document.getElementById("terminology-section-utility-container");
25
+
26
+ // Build alphabet index data
27
+ const alphabetIndex = {};
28
+ dtElements.forEach(dt => {
29
+ const span = dt.querySelector("span");
30
+ if (span?.id) {
31
+ const termId = span.id;
32
+ const firstChar = termId.charAt(termId.indexOf("term:") + 5).toUpperCase();
33
+ if (!alphabetIndex[firstChar]) {
34
+ alphabetIndex[firstChar] = span.id;
35
+ }
36
+ }
37
+ });
38
+
39
+ /*************************************************/
40
+ /* DOM CONSTRUCTION - COMPLETE LAYOUT STRUCTURE */
41
+ /*************************************************/
42
+
43
+ /* ===== ROW 1: ALPHABET INDEX ===== */
44
+ // ALPHABET INDEX TEMPORARILY DISABLED
45
+ // const alphabetRow = document.createElement("div");
46
+ // alphabetRow.className = "row mb-2";
47
+ //
48
+ // const alphabetCol = document.createElement("div");
49
+ // alphabetCol.className = "col-12";
50
+ //
51
+ // const alphabetIndexContainer = document.createElement("div");
52
+ // alphabetIndexContainer.className = "d-flex flex-wrap justify-content-center gap-2";
53
+
54
+ // // Create alphabet links
55
+ // Object.keys(alphabetIndex).sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase())).forEach(char => {
56
+ // const link = document.createElement("a");
57
+ // link.href = `#${alphabetIndex[char]}`;
58
+ // link.textContent = char;
59
+ // link.className = "btn btn-outline-secondary btn-sm";
60
+ // alphabetIndexContainer.appendChild(link);
61
+ // });
62
+
63
+ // alphabetCol.appendChild(alphabetIndexContainer);
64
+ // alphabetRow.appendChild(alphabetCol);
65
+
66
+ /* ===== ROW 2: UTILITIES (TERM COUNT + FILTERS + SEARCH) ===== */
67
+ const utilityRow = document.createElement("div");
68
+ utilityRow.className = "row g-2";
69
+ utilityRow.id = "utility-row";
70
+
71
+ // Left column: Term count
72
+ const leftCol = document.createElement("div");
73
+ leftCol.className = "col-auto d-flex align-items-center";
74
+
75
+ // Term count
76
+ const numberOfTerms = document.createElement("small");
77
+ numberOfTerms.className = "text-muted mb-0";
78
+ numberOfTerms.textContent = `${dtElements.length} terms`;
79
+ leftCol.appendChild(numberOfTerms);
80
+
81
+ // Center column: Filters and Versions button
82
+ const centerCol = document.createElement("div");
83
+ centerCol.className = "col d-flex flex-wrap align-items-center gap-3";
84
+
85
+ // Filter checkboxes container
86
+ const checkboxesContainer = document.createElement('div');
87
+ checkboxesContainer.className = 'd-flex gap-3';
88
+
89
+ // Local terms checkbox
90
+ const localTermsCheckboxDiv = document.createElement('div');
91
+ localTermsCheckboxDiv.className = 'form-check';
92
+ localTermsCheckboxDiv.innerHTML = `
93
+ <input class="form-check-input" type="checkbox" id="showLocalTermsCheckbox" checked>
94
+ <label class="form-check-label" for="showLocalTermsCheckbox">
95
+ Local
96
+ </label>
97
+ `;
98
+
99
+ // External terms checkbox
100
+ const externalTermsCheckboxDiv = document.createElement('div');
101
+ externalTermsCheckboxDiv.className = 'form-check';
102
+ externalTermsCheckboxDiv.innerHTML = `
103
+ <input class="form-check-input" type="checkbox" id="showExternalTermsCheckbox" checked>
104
+ <label class="form-check-label" for="showExternalTermsCheckbox">
105
+ Remote
106
+ </label>
107
+ `;
108
+
109
+ checkboxesContainer.appendChild(localTermsCheckboxDiv);
110
+ checkboxesContainer.appendChild(externalTermsCheckboxDiv);
111
+ centerCol.appendChild(checkboxesContainer);
112
+
113
+ // Snapshot link
114
+ const snapshotLink = document.createElement('a');
115
+ snapshotLink.id = 'snapshot-link-in-content';
116
+ snapshotLink.className = 'btn btn-outline-primary btn-sm';
117
+ // snapshotLink.href = './versions/';
118
+ snapshotLink.href = '#';
119
+ snapshotLink.textContent = 'Versions';
120
+ centerCol.appendChild(snapshotLink);
121
+
122
+ // Right column: Search
123
+ const rightCol = document.createElement("div");
124
+ rightCol.className = "col-auto d-flex justify-content-end";
125
+
126
+ // Search container
127
+ const searchContainer = document.createElement("div");
128
+ searchContainer.setAttribute("id", "container-search");
129
+ searchContainer.classList.add("input-group", "input-group-sm");
130
+ searchContainer.setAttribute("role", "search");
131
+
132
+ // Search input
133
+ const searchInput = document.createElement("input");
134
+ searchInput.setAttribute("type", "text");
135
+ searchInput.setAttribute("id", "search");
136
+ searchInput.classList.add("form-control");
137
+ searchInput.setAttribute("placeholder", "🔍 (terms only)");
138
+ searchInput.setAttribute("aria-label", "Search terms");
139
+ searchInput.setAttribute("autocomplete", "off");
140
+ searchContainer.appendChild(searchInput);
141
+
142
+ // Search button group
143
+ const buttonGroup = document.createElement("div");
144
+ buttonGroup.classList.add("input-group-text", "p-0");
145
+
146
+ // Previous match button
147
+ const goToPreviousMatchButton = document.createElement("button");
148
+ goToPreviousMatchButton.setAttribute("id", "one-match-backward-search");
149
+ goToPreviousMatchButton.classList.add("btn", "btn-outline-secondary");
150
+ goToPreviousMatchButton.setAttribute("type", "button");
151
+ goToPreviousMatchButton.setAttribute("disabled", "true");
152
+ goToPreviousMatchButton.setAttribute("title", "Go to previous match (Left Arrow)");
153
+ goToPreviousMatchButton.setAttribute("aria-label", "Go to previous match");
154
+ goToPreviousMatchButton.innerHTML = '<span aria-hidden="true">▲</span>';
155
+ buttonGroup.appendChild(goToPreviousMatchButton);
156
+
157
+ // Next match button
158
+ const goToNextMatchButton = document.createElement("button");
159
+ goToNextMatchButton.setAttribute("id", "one-match-forward-search");
160
+ goToNextMatchButton.classList.add("btn", "btn-outline-secondary");
161
+ goToNextMatchButton.setAttribute("type", "button");
162
+ goToNextMatchButton.setAttribute("disabled", "true");
163
+ goToNextMatchButton.setAttribute("title", "Go to next match (Right Arrow)");
164
+ goToNextMatchButton.setAttribute("aria-label", "Go to next match");
165
+ goToNextMatchButton.innerHTML = '<span aria-hidden="true">▼</span>';
166
+ buttonGroup.appendChild(goToNextMatchButton);
167
+
168
+ // Matches counter
169
+ const totalMatchesSpan = document.createElement("span");
170
+ totalMatchesSpan.setAttribute("id", "total-matches-search");
171
+ totalMatchesSpan.classList.add("input-group-text");
172
+ totalMatchesSpan.innerHTML = "0 matches";
173
+ totalMatchesSpan.setAttribute("aria-live", "polite");
174
+ totalMatchesSpan.setAttribute("role", "status");
175
+ searchContainer.appendChild(totalMatchesSpan);
176
+
177
+ searchContainer.appendChild(buttonGroup);
178
+ rightCol.appendChild(searchContainer);
179
+
180
+ utilityRow.appendChild(leftCol);
181
+ utilityRow.appendChild(centerCol);
182
+ utilityRow.appendChild(rightCol);
183
+
184
+ /* ===== ASSEMBLE COMPLETE STRUCTURE ===== */
185
+ // ALPHABET INDEX TEMPORARILY DISABLED
186
+ // terminologySectionUtilityContainer.appendChild(alphabetRow);
187
+ terminologySectionUtilityContainer.appendChild(utilityRow);
188
+
189
+ /*****************************************/
190
+ /* INITIALIZE FUNCTIONALITY COMPONENTS */
191
+ /*****************************************/
192
+
193
+ // Initialize functionalities (these will attach to the DOM elements we just created)
194
+ // ALPHABET INDEX TEMPORARILY DISABLED
195
+ // attachAlphabetIndexFunctionality();
196
+ attachTermFilterFunctionality(checkboxesContainer);
197
+ attachSearchFunctionality(searchInput, goToPreviousMatchButton, goToNextMatchButton, totalMatchesSpan);
198
+ }
199
+
200
+ // Initialize when DOM is ready
201
+ document.addEventListener("DOMContentLoaded", function () {
202
+ initializeTerminologyUtilityContainer();
203
+ });
@@ -0,0 +1,283 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ /**
5
+ * TOOLTIP SYSTEM OVERVIEW
6
+ * =======================
7
+ * This module creates interactive tooltips for term references in the specification.
8
+ * It handles two types of content sources:
9
+ *
10
+ * 1. LOCAL TERMS (tref and def): Content from the current document's DOM
11
+ * - These are definition lists <dt>/<dd> or table rows <tr>/<td>
12
+ * - Example: [[def: term, alias]] creates a local definition
13
+ * - Example: [[tref: ., term, alias]] references a local term
14
+ *
15
+ * 2. EXTERNAL TERMS (xref): Content from external specifications
16
+ * - These come from the global allXTrefs data structure
17
+ * - Example: [[tref: externalreference, term]] references an external term
18
+ * - The allXTrefs data is populated by src/pipeline/references/external-references-service.js during build time
19
+ * - External content is fetched from other GitHub-hosted specifications
20
+ */
21
+
22
+ /* Tooltips WeakMap storage for performance optimization */
23
+ // WeakMap prevents memory leaks by automatically garbage collecting
24
+ // when DOM elements are removed
25
+ let tipMap = new WeakMap();
26
+
27
+ /**
28
+ * EVENT DELEGATION FOR TOOLTIP TRIGGERS
29
+ * =====================================
30
+ * Uses delegateEvent (from utils.js) to efficiently handle hover events.
31
+ * Event delegation attaches a single listener to the document rather than
32
+ * individual listeners on each term reference, improving performance.
33
+ *
34
+ * TARGET SELECTORS:
35
+ * - '.term-reference': Generated for [[tref: ...]] and [[def: ...]] constructions
36
+ * - '.spec-reference': Generated for specification references
37
+ *
38
+ * TRIGGER EVENT: 'pointerover'
39
+ * - Fires when mouse enters the element (more reliable than 'mouseenter')
40
+ * - Works on both mouse and touch devices
41
+ */
42
+ delegateEvent('pointerover', '.term-reference, .spec-reference', (e, anchor) => {
43
+ /**
44
+ * EXTRACT TARGET INFORMATION
45
+ * =========================
46
+ * The anchor element contains attributes that tell us what term to show:
47
+ *
48
+ * - data-local-href: For local references (format: #termname or #term:external:termname)
49
+ * - href: Fallback attribute for standard links
50
+ *
51
+ * Examples of generated markup:
52
+ * - [[def: myterm]] creates: <a class="term-reference" data-local-href="#myterm">myterm</a>
53
+ * - [[tref: ., myterm]] creates: <a class="term-reference" data-local-href="#myterm">myterm</a>
54
+ * - [[tref: external, myterm]] creates: <a class="term-reference" data-local-href="#term:external:myterm">myterm</a>
55
+ */
56
+ const id = anchor.getAttribute('data-local-href') || anchor.getAttribute('href') || '';
57
+
58
+ /**
59
+ * ATTEMPT TO FIND LOCAL DOM ELEMENT
60
+ * ================================
61
+ * Try to find a DOM element with the ID (removing the # prefix).
62
+ * This works for local definitions and some tref references.
63
+ */
64
+ let term = document.getElementById(id.replace('#', ''));
65
+
66
+ /**
67
+ * TOOLTIP CONFIGURATION
68
+ * ====================
69
+ * Base configuration for Tippy.js tooltips:
70
+ * - allowHTML: Enables rich HTML content in tooltips
71
+ * - inlinePositioning: Positions tooltip relative to cursor for better UX
72
+ */
73
+ let tip = {
74
+ allowHTML: true,
75
+ inlinePositioning: true
76
+ };
77
+
78
+ /**
79
+ * PREVENT DUPLICATE TOOLTIPS
80
+ * =========================
81
+ * Check if we've already created a tooltip for this anchor element.
82
+ * The WeakMap ensures efficient lookup and automatic cleanup.
83
+ */
84
+ if (tipMap.has(anchor)) return;
85
+
86
+ if (term) {
87
+ /**
88
+ * LOCAL DOM-BASED TOOLTIP CONTENT
89
+ * ===============================
90
+ * When we find a matching DOM element, extract content from it.
91
+ * The spec-up system creates definitions in two main formats:
92
+ *
93
+ * 1. DEFINITION LIST FORMAT:
94
+ * <dt id="myterm">Term Name</dt>
95
+ * <dd>Term definition content goes here</dd>
96
+ *
97
+ * 2. TABLE FORMAT:
98
+ * <table>
99
+ * <thead><tr><th>Term</th><th>Definition</th><th>Source</th></tr></thead>
100
+ * <tbody><tr><td id="myterm">Term Name</td><td>Definition</td><td>Source</td></tr></tbody>
101
+ * </table>
102
+ */
103
+
104
+ // Find the container element that holds the term definition
105
+ // Look for either <dt> (definition term) or first <td> (table cell)
106
+ let container = term.closest('dt, td:first-child');
107
+ if (!container) return;
108
+
109
+ switch (container.tagName) {
110
+ case 'DT':
111
+ /**
112
+ * DEFINITION LIST PROCESSING
113
+ * =========================
114
+ * In a definition list, the <dt> contains the term name,
115
+ * and the next sibling <dd> contains the definition text.
116
+ * We extract the text content from the <dd> element.
117
+ * If the first <dd> has class 'meta-info-content-wrapper', skip it and use the next <dd>.
118
+ */
119
+ let dd = container.nextElementSibling;
120
+ if (dd && dd.tagName === 'DD' && dd.classList.contains('meta-info-content-wrapper')) {
121
+ dd = dd.nextElementSibling;
122
+ }
123
+ if (dd && dd.tagName === 'DD') {
124
+ tip.content = dd.textContent;
125
+ }
126
+ break;
127
+
128
+ case 'TD':
129
+ /**
130
+ * TABLE FORMAT PROCESSING
131
+ * ======================
132
+ * For table-based definitions, we create a mini table showing
133
+ * all the metadata associated with the term. This provides
134
+ * richer context than just the definition text.
135
+ *
136
+ * Process:
137
+ * 1. Find the parent table and get all header cells
138
+ * 2. Get all data cells from the current row
139
+ * 3. Create HTML table mapping headers to values
140
+ * 4. Skip the first column (which contains the term name itself)
141
+ */
142
+ let table = container.closest('table');
143
+ let tds = Array.from(container.closest('tr').children);
144
+ tds.shift(); // Remove first cell (the term name)
145
+
146
+ if (table) {
147
+ let headings = Array.from(table.querySelectorAll('thead th'));
148
+ headings.shift(); // Remove first heading (matches the term name column)
149
+
150
+ if (headings.length) {
151
+ tip.content = `
152
+ <header>${container.textContent}</header>
153
+ <table>
154
+ ${headings.map((th, i) => {
155
+ return `<tr><td>${th.textContent}:</td><td>${tds[i] ? tds[i].textContent : ''}</td></tr>`
156
+ }).join('')}
157
+ </table>`;
158
+ }
159
+ }
160
+ break;
161
+ }
162
+ } else {
163
+ /**
164
+ * EXTERNAL REFERENCE TOOLTIP CONTENT
165
+ * ==================================
166
+ * When no local DOM element is found, this is likely an external reference (xref).
167
+ * External references have the format: #term:externalSpec:termName
168
+ *
169
+ * THE allXTrefs DATA SOURCE:
170
+ * -------------------------
171
+ * The allXTrefs global variable is created during the build process by:
172
+ * 1. src/pipeline/references/external-references-service.js - Orchestrates external reference collection
173
+ * 2. src/pipeline/references/ - Fetches content from external GitHub specs
174
+ * 3. The data is written to a JavaScript file and included in the page
175
+ *
176
+ * DATA STRUCTURE:
177
+ * allXTrefs = {
178
+ * xtrefs: [
179
+ * {
180
+ * externalSpec: "spec-name", // The external specification identifier
181
+ * term: "term-name", // The term being referenced
182
+ * content: "Definition text...", // The definition content from external spec
183
+ * url: "https://...", // Source URL
184
+ * // ... other metadata
185
+ * }
186
+ * ]
187
+ * }
188
+ *
189
+ * EXTERNAL SPEC FETCHING PROCESS:
190
+ * ------------------------------
191
+ * 1. External specs are defined in specs-configuration.yml
192
+ * 2. During build, the system fetches HTML from external GitHub Pages
193
+ * 3. Term definitions are extracted from the external HTML
194
+ * 4. Content is cached in JSON and JS files for runtime use
195
+ * 5. This allows tooltips to show definitions from other specifications
196
+ */
197
+
198
+ // Handle xref terms from allXTrefs data (when no DOM element found)
199
+ const href = anchor.getAttribute('data-local-href') || '';
200
+
201
+ /**
202
+ * PARSE EXTERNAL REFERENCE FORMAT
203
+ * ===============================
204
+ * Expected format: #term:externalSpec:termName
205
+ * Example: #term:KERI:delegator means:
206
+ * - externalSpec: "KERI"
207
+ * - termName: "delegator"
208
+ */
209
+ const match = href.match(/#term:([^:]+):(.+)/);
210
+ if (match) {
211
+ const [, externalSpec, termName] = match;
212
+
213
+ /**
214
+ * LOOKUP IN EXTERNAL REFERENCES DATA
215
+ * =================================
216
+ * Search the global allXTrefs data for matching term.
217
+ * Uses case-insensitive matching to handle inconsistencies
218
+ * between how terms are defined vs. referenced.
219
+ */
220
+ if (typeof allXTrefs !== 'undefined' && allXTrefs.xtrefs && allXTrefs.xtrefs.length > 0) {
221
+ // Look for term with case-insensitive matching to handle case inconsistencies
222
+ const foundTerm = allXTrefs.xtrefs.find(xtref =>
223
+ xtref.externalSpec === externalSpec &&
224
+ xtref.term.toLowerCase() === termName.toLowerCase()
225
+ );
226
+
227
+ if (foundTerm && foundTerm.content) {
228
+ /**
229
+ * CLEAN HTML CONTENT FOR TOOLTIP
230
+ * ==============================
231
+ * The external content may contain HTML markup that's not suitable
232
+ * for tooltips. We create a temporary DOM element to extract
233
+ * clean text content while preserving structure.
234
+ */
235
+ // Strip HTML tags for clean text tooltip
236
+ const tempDiv = document.createElement('div');
237
+ tempDiv.innerHTML = foundTerm.content;
238
+ tip.content = tempDiv.textContent || tempDiv.innerText || '';
239
+ }
240
+ }
241
+ }
242
+ }
243
+
244
+ /**
245
+ * CREATE AND STORE TOOLTIP
246
+ * ========================
247
+ * If we successfully extracted content (from either local DOM or external data),
248
+ * create the actual tooltip using Tippy.js library and store it in our WeakMap
249
+ * for future reference and duplicate prevention.
250
+ *
251
+ * TIPPY.JS INTEGRATION:
252
+ * - tippy() creates the interactive tooltip element
253
+ * - WeakMap storage prevents memory leaks and duplicate creation
254
+ * - Tooltip appears on hover and includes rich formatting
255
+ */
256
+ if (tip.content) {
257
+ tipMap.set(anchor, tippy(anchor, tip))
258
+ };
259
+ }, { passive: true }); // passive: true improves scroll performance
260
+
261
+ /**
262
+ * MODULE SUMMARY
263
+ * =============
264
+ * This tooltip system provides contextual help for specification terms by:
265
+ *
266
+ * 1. LISTENING: Uses event delegation to efficiently handle hover events
267
+ * 2. IDENTIFYING: Determines if term is local (DOM-based) or external (xref)
268
+ * 3. EXTRACTING: Gets content from appropriate source (DOM elements or allXTrefs data)
269
+ * 4. DISPLAYING: Creates rich tooltips with Tippy.js
270
+ * 5. CACHING: Prevents duplicate tooltip creation with WeakMap storage
271
+ *
272
+ * CONTENT SOURCES:
273
+ * - Local definitions: From <dt>/<dd> or table rows in current document
274
+ * - External references: From allXTrefs global data (fetched during build)
275
+ *
276
+ * MARKDOWN SYNTAX SUPPORTED:
277
+ * - [[def: term, alias]] - Creates local definition
278
+ * - [[tref: ., term, alias]] - References local term
279
+ * - [[tref: externalreference, term]] - References external term
280
+ * - [[xref: externalreference, term]] - External reference (alias for tref)
281
+ */
282
+
283
+ })();