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
@@ -0,0 +1,144 @@
1
+ /**
2
+ * GitHub Repository Information Utility
3
+ *
4
+ * This client-side utility provides easy access to the GitHub repository
5
+ * information embedded in the HTML meta tag by Spec-Up-T.
6
+ *
7
+ * Usage:
8
+ * const repoInfo = getGithubRepoInfo();
9
+ * if (repoInfo) {
10
+ * console.log(`Repository: ${repoInfo.username}/${repoInfo.repo} on ${repoInfo.branch}`);
11
+ * }
12
+ */
13
+
14
+ /**
15
+ * Extracts GitHub repository information from the meta tag
16
+ * @returns {Object|null} Repository information object or null if not found
17
+ * @returns {string} returns.username - GitHub username/account
18
+ * @returns {string} returns.repo - Repository name
19
+ * @returns {string} returns.branch - Git branch name
20
+ */
21
+ function getGithubRepoInfo() {
22
+ try {
23
+ const metaTag = document.querySelector('meta[property="spec-up-t:github-repo-info"]');
24
+ if (!metaTag) {
25
+ console.warn('GitHub repository meta tag not found');
26
+ return null;
27
+ }
28
+
29
+ const content = metaTag.getAttribute('content');
30
+ if (!content) {
31
+ console.warn('GitHub repository meta tag has no content');
32
+ return null;
33
+ }
34
+
35
+ const [username, repo, branch] = content.split(',');
36
+ if (!username || !repo || !branch) {
37
+ console.warn('Invalid GitHub repository meta tag format');
38
+ return null;
39
+ }
40
+
41
+ return {
42
+ username: username.trim(),
43
+ repo: repo.trim(),
44
+ branch: branch.trim()
45
+ };
46
+ } catch (error) {
47
+ console.error('Error extracting GitHub repository information:', error);
48
+ return null;
49
+ }
50
+ }
51
+
52
+ /**
53
+ * Creates a GitHub URL from the repository information
54
+ * @param {string} path - Optional path within the repository (e.g., 'issues', 'blob/main/README.md')
55
+ * @returns {string|null} GitHub URL or null if repository info not available
56
+ */
57
+ function getGithubUrl(path = '') {
58
+ const repoInfo = getGithubRepoInfo();
59
+ if (!repoInfo) {
60
+ return null;
61
+ }
62
+
63
+ const baseUrl = `https://github.com/${repoInfo.username}/${repoInfo.repo}`;
64
+ if (path) {
65
+ return `${baseUrl}/${path}`;
66
+ }
67
+ return baseUrl;
68
+ }
69
+
70
+ /**
71
+ * Gets the GitHub URL for the current branch
72
+ * @returns {string|null} GitHub branch URL or null if repository info not available
73
+ */
74
+ function getCurrentBranchUrl() {
75
+ const repoInfo = getGithubRepoInfo();
76
+ if (!repoInfo) {
77
+ return null;
78
+ }
79
+
80
+ return `https://github.com/${repoInfo.username}/${repoInfo.repo}/tree/${repoInfo.branch}`;
81
+ }
82
+
83
+ /**
84
+ * Populates the repository information in the settings menu
85
+ * This function is called when the page loads to display repo info
86
+ */
87
+ function populateRepoInfoInSettings() {
88
+ const repoInfo = getGithubRepoInfo();
89
+
90
+ // Get DOM elements for repository info display
91
+ const accountElement = document.getElementById('repo-account');
92
+ const nameElement = document.getElementById('repo-name');
93
+ const branchElement = document.getElementById('repo-branch');
94
+ const urlElement = document.getElementById('repo-url');
95
+
96
+ if (!accountElement || !nameElement || !branchElement || !urlElement) {
97
+ console.warn('Repository info elements not found in settings menu');
98
+ return;
99
+ }
100
+
101
+ if (repoInfo) {
102
+ // Populate the information
103
+ accountElement.textContent = repoInfo.username;
104
+ nameElement.textContent = repoInfo.repo;
105
+ branchElement.textContent = repoInfo.branch;
106
+
107
+ // Set up the GitHub link
108
+ const repoUrl = getGithubUrl();
109
+ if (repoUrl) {
110
+ urlElement.href = repoUrl;
111
+ urlElement.style.display = 'inline-block';
112
+ } else {
113
+ urlElement.style.display = 'none';
114
+ }
115
+ } else {
116
+ // Handle case where no repository info is available
117
+ accountElement.textContent = 'Not available';
118
+ nameElement.textContent = 'Not available';
119
+ branchElement.textContent = 'Not available';
120
+ urlElement.style.display = 'none';
121
+ }
122
+ }
123
+
124
+ // Initialize repository info when DOM is ready
125
+ document.addEventListener('DOMContentLoaded', function() {
126
+ populateRepoInfoInSettings();
127
+ });
128
+
129
+ // Make functions available globally if not using modules
130
+ if (typeof window !== 'undefined') {
131
+ window.getGithubRepoInfo = getGithubRepoInfo;
132
+ window.getGithubUrl = getGithubUrl;
133
+ window.getCurrentBranchUrl = getCurrentBranchUrl;
134
+ window.populateRepoInfoInSettings = populateRepoInfoInSettings;
135
+ }
136
+
137
+ // Export for module usage
138
+ if (typeof module !== 'undefined' && module.exports) {
139
+ module.exports = {
140
+ getGithubRepoInfo,
141
+ getGithubUrl,
142
+ getCurrentBranchUrl
143
+ };
144
+ }
@@ -14,6 +14,7 @@ const {
14
14
  initializeAnchorHighlighting
15
15
  } = require('./highlight-heading-plus-sibling-nodes');
16
16
 
17
+ // Tests for highlighting heading sections and their related content
17
18
  describe('highlight-heading-plus-sibling-nodes', () => {
18
19
  let container;
19
20
 
@@ -36,11 +37,14 @@ describe('highlight-heading-plus-sibling-nodes', () => {
36
37
  document.body.innerHTML = '';
37
38
  });
38
39
 
40
+ // Tests for determining heading levels (h2-h6)
39
41
  describe('getHeadingLevel', () => {
42
+ // Test: Can the system correctly identify heading levels?
40
43
  it('returns correct level for h2-h6', () => {
41
44
  expect(getHeadingLevel(document.getElementById('h2-1'))).toBe(2);
42
45
  expect(getHeadingLevel(document.getElementById('h3-1'))).toBe(3);
43
46
  });
47
+ // Test: Does the system properly reject invalid headings?
44
48
  it('returns null for h1 or non-heading', () => {
45
49
  const h1 = document.createElement('h1');
46
50
  expect(getHeadingLevel(h1)).toBeNull();
@@ -48,12 +52,15 @@ describe('highlight-heading-plus-sibling-nodes', () => {
48
52
  });
49
53
  });
50
54
 
55
+ // Tests for collecting heading sections (heading + related content)
51
56
  describe('collectHeadingSiblings', () => {
57
+ // Test: Can the system collect all content belonging to a heading section?
52
58
  it('collects heading and following siblings until next heading of same or higher level', () => {
53
59
  const h2 = document.getElementById('h2-1');
54
60
  const siblings = collectHeadingSiblings(h2, 2);
55
61
  expect(siblings.map(n => n.id)).toEqual(['h2-1', 'p1', 'ul1', 'h3-1', 'p2']);
56
62
  });
63
+ // Test: Does the system stop collecting at the right boundary?
57
64
  it('stops at next heading of same or higher level', () => {
58
65
  const h3 = document.getElementById('h3-1');
59
66
  const siblings = collectHeadingSiblings(h3, 3);
@@ -61,7 +68,9 @@ describe('highlight-heading-plus-sibling-nodes', () => {
61
68
  });
62
69
  });
63
70
 
71
+ // Tests for wrapping content with highlight styling
64
72
  describe('wrapNodesWithHighlight', () => {
73
+ // Test: Can the system wrap selected content with highlight styling?
65
74
  it('wraps nodes in a div.highlight2', () => {
66
75
  const nodes = [document.getElementById('h2-1'), document.getElementById('p1')];
67
76
  const wrapper = wrapNodesWithHighlight(nodes);
@@ -70,12 +79,15 @@ describe('highlight-heading-plus-sibling-nodes', () => {
70
79
  expect(wrapper.contains(nodes[0])).toBe(true);
71
80
  expect(wrapper.contains(nodes[1])).toBe(true);
72
81
  });
82
+ // Test: Does the system handle empty input gracefully?
73
83
  it('returns null if nodes is empty', () => {
74
84
  expect(wrapNodesWithHighlight([])).toBeNull();
75
85
  });
76
86
  });
77
87
 
88
+ // Tests for cleaning up existing highlights
78
89
  describe('removeExistingHighlights', () => {
90
+ // Test: Can the system remove highlights and restore original content structure?
79
91
  it('removes all .highlight2 wrappers and restores children', () => {
80
92
  const nodes = [document.getElementById('h2-1'), document.getElementById('p1')];
81
93
  wrapNodesWithHighlight(nodes);
@@ -89,24 +101,30 @@ describe('highlight-heading-plus-sibling-nodes', () => {
89
101
  });
90
102
  });
91
103
 
104
+ // Tests for the main highlighting functionality
92
105
  describe('highlightHeadingSection', () => {
106
+ // Test: Can the system highlight a section when given a valid anchor?
93
107
  it('highlights section for valid anchor', () => {
94
108
  expect(highlightHeadingSection('#h2-1')).toBe(true);
95
109
  const highlight = document.querySelector('.highlight2');
96
110
  expect(highlight).not.toBeNull();
97
111
  expect(highlight.contains(document.getElementById('h2-1'))).toBe(true);
98
112
  });
113
+ // Test: Does the system properly reject invalid anchors?
99
114
  it('returns false for invalid anchor', () => {
100
115
  expect(highlightHeadingSection('#does-not-exist')).toBe(false);
101
116
  expect(highlightHeadingSection('not-a-hash')).toBe(false);
102
117
  expect(highlightHeadingSection('#')).toBe(false);
103
118
  });
119
+ // Test: Does the system only work with heading elements?
104
120
  it('returns false for non-heading element', () => {
105
121
  expect(highlightHeadingSection('#p1')).toBe(false);
106
122
  });
107
123
  });
108
124
 
125
+ // Tests for setting up automatic highlighting based on URL anchors
109
126
  describe('initializeAnchorHighlighting', () => {
127
+ // Test: Does the system automatically highlight sections based on URL hash changes?
110
128
  it('sets up event listeners and highlights on hash', () => {
111
129
  window.location.hash = '#h2-1';
112
130
  // Remove highlights if any
@@ -7,7 +7,7 @@
7
7
  /**
8
8
  * Inserts transcluded external references (trefs) into the document.
9
9
  * This function processes the allXTrefs data and inserts definition content into the document
10
- * for terms marked with the 'transcluded-xref-term' class.
10
+ * for terms marked with the 'term-external' class.
11
11
  * @param {Object} allXTrefs - The object containing all external references data
12
12
  * @param {Array} allXTrefs.xtrefs - Array of external reference objects, each containing term definitions
13
13
  */
@@ -28,10 +28,21 @@ function insertTrefs(allXTrefs) {
28
28
  */
29
29
  const allTerms = [];
30
30
 
31
- document.querySelectorAll('dl.terms-and-definitions-list dt span.transcluded-xref-term').forEach((termElement) => {
31
+ document.querySelectorAll('dl.terms-and-definitions-list dt span.term-external').forEach((termElement) => {
32
+
32
33
  // Get the full text content including any nested spans (for aliases) of a term (dt)
33
34
  // In case of `[[tref:toip1, agency, ag]]`, this will return `agency`
34
- const textContent = termElement.textContent.trim();
35
+ // const textContent = termElement.textContent.trim();
36
+
37
+ /*
38
+ We used to do this: Get the full text content including any nested spans (for aliases) of a term (dt):
39
+ `const textContent = termElement.textContent.trim();`
40
+ (In case of `[[tref:toip1, agency, ag]]`, this will return `agency`)
41
+
42
+ We cannot use the textContent directly anymore because it may not match the original term, since that can be the alias. That is why we use data-original-term.
43
+ In case of `[[tref:toip1, agency, ag]]`, this will always return `agency`, regardless if the alias is used or not, since the original term is stored in data-original-term
44
+ */
45
+ const originalTerm = termElement.dataset.originalTerm;
35
46
 
36
47
  // Find the dt element once outside the loop
37
48
  const dt = termElement.closest('dt');
@@ -39,7 +50,7 @@ function insertTrefs(allXTrefs) {
39
50
  // Skip if the term has already been processed
40
51
  if (dt) {
41
52
  const nextElement = dt.nextElementSibling;
42
- if (nextElement?.classList.contains('transcluded-xref-term') &&
53
+ if (nextElement?.classList.contains('term-external') &&
43
54
  nextElement.classList.contains('meta-info-content-wrapper')) {
44
55
  return; // Already processed
45
56
  }
@@ -48,7 +59,7 @@ function insertTrefs(allXTrefs) {
48
59
  // Only add terms that haven't been processed yet
49
60
  allTerms.push({
50
61
  element: termElement,
51
- textContent: textContent,
62
+ textContent: originalTerm,
52
63
  dt: dt,
53
64
  parent: dt?.parentNode
54
65
  });
@@ -60,21 +71,21 @@ function insertTrefs(allXTrefs) {
60
71
  * @type {Array<{dt: Element, parent: Element, fragment: DocumentFragment}>}
61
72
  */
62
73
  const domChanges = allTerms.map(termData => {
63
- const { textContent, dt, parent } = termData;
74
+ const { textContent: originalTerm, dt, parent } = termData;
64
75
 
65
76
  if (!dt || !parent) {
66
77
  return null; // Skip invalid entries
67
78
  }
68
79
 
69
80
  // Find the first matching xref to avoid duplicates
70
- const xref = xtrefsData.xtrefs.find(x => x.term === textContent);
71
-
81
+ const xref = xtrefsData.xtrefs.find(x => x.term === originalTerm);
82
+
72
83
  // Create a DocumentFragment to hold all new elements for this term
73
84
  const fragment = document.createDocumentFragment();
74
85
 
75
86
  // Create meta info element
76
87
  const metaInfoEl = document.createElement('dd');
77
- metaInfoEl.classList.add('transcluded-xref-term', 'meta-info-content-wrapper', 'collapsed');
88
+ metaInfoEl.classList.add('term-external', 'meta-info-content-wrapper', 'collapsed');
78
89
 
79
90
  if (xref) {
80
91
  // Generate meta info content
@@ -82,10 +93,13 @@ function insertTrefs(allXTrefs) {
82
93
  const owner = xref.owner || 'Unknown';
83
94
  const repo = xref.repo && xref.repoUrl ? `[${xref.repo}](${xref.repoUrl})` : 'Unknown';
84
95
  const commitHash = xref.commitHash || 'Unknown';
96
+ const linkToOriginalTerm = xref.ghPageUrl ? new URL(`#term:${xref.term}`, xref.ghPageUrl).href : 'Unknown';
85
97
 
86
98
  const metaInfo = `
87
99
  | Property | Value |
88
100
  | -------- | ----- |
101
+ | Original Term | ${originalTerm} |
102
+ | Link | ${linkToOriginalTerm} |
89
103
  | Owner | ${avatar} ${owner} |
90
104
  | Repo | ${repo} |
91
105
  | Commit hash | ${commitHash} |
@@ -102,10 +116,45 @@ function insertTrefs(allXTrefs) {
102
116
  .replace(/\]\]/g, '');
103
117
 
104
118
  // Clean up the markdown content in the term definition
105
- // Part B: Remove all <a> elements from the content via a temporary div and DOM manipulation
119
+ // Part B: Remove some <a> elements from the content. We can turn the fact that the inserted URLs are relative (which previously seemed to be a disadvantage) into an advantage, since the fact that the URL then points to a possible local variant is actually an advantage. To this end, if the local variant does indeed exist, we leave the link intact.
106
120
  const tempDivForLinks = document.createElement('div');
107
121
  tempDivForLinks.innerHTML = md.render(content);
108
- tempDivForLinks.querySelectorAll('a').forEach(a => a.replaceWith(...a.childNodes));
122
+ tempDivForLinks.querySelectorAll('a').forEach(a => {
123
+ // Helper function to safely check if an element exists by ID
124
+ // This handles IDs with special characters (like colons) that are invalid in CSS selectors
125
+ const elementExistsById = (id) => {
126
+ try {
127
+ return document.getElementById(id) !== null;
128
+ } catch {
129
+ return false;
130
+ }
131
+ };
132
+
133
+ try {
134
+ const url = new URL(a.href);
135
+
136
+ // Keep links to different domains
137
+ if (url.hostname !== window.location.hostname) {
138
+ return;
139
+ }
140
+
141
+ // Keep links with valid local hash anchors
142
+ if (url.hash && elementExistsById(url.hash.slice(1))) {
143
+ return;
144
+ }
145
+
146
+ // Remove links to same domain without valid hash
147
+ a.replaceWith(...a.childNodes);
148
+ } catch {
149
+ // Handle relative URLs or invalid URL formats
150
+ if (a.href.startsWith('#') && elementExistsById(a.href.slice(1))) {
151
+ return;
152
+ }
153
+
154
+ // Remove invalid or non-local links
155
+ a.replaceWith(...a.childNodes);
156
+ }
157
+ });
109
158
  content = tempDivForLinks.innerHTML;
110
159
 
111
160
  // Parse the rendered HTML to check for dd elements. xref.content is a string that contains HTML, in the form of <dd>...</dd>'s
@@ -120,7 +169,7 @@ function insertTrefs(allXTrefs) {
120
169
  // Clone the node to avoid removing it from tempDiv during insertion
121
170
  const clonedDD = dd.cloneNode(true);
122
171
  // Add necessary classes
123
- clonedDD.classList.add('transcluded-xref-term', 'transcluded-xref-term-embedded');
172
+ clonedDD.classList.add('term-external', 'term-external-embedded');
124
173
  // Add to fragment
125
174
  fragment.appendChild(clonedDD);
126
175
  });
@@ -130,7 +179,7 @@ function insertTrefs(allXTrefs) {
130
179
  `"content": "This term was not found in the external repository"`
131
180
  */
132
181
  const contentEl = document.createElement('dd');
133
- contentEl.classList.add('transcluded-xref-term', 'transcluded-xref-term-embedded');
182
+ contentEl.classList.add('term-external', 'term-external-embedded');
134
183
  contentEl.innerHTML = tempDiv.innerHTML;
135
184
  fragment.appendChild(contentEl);
136
185
  }
@@ -0,0 +1,11 @@
1
+ (function () {
2
+ 'use strict';
3
+
4
+ /* Mermaid Diagrams */
5
+
6
+ mermaid.initialize({
7
+ startOnLoad: true,
8
+ theme: 'neutral'
9
+ });
10
+
11
+ })();
@@ -0,0 +1,107 @@
1
+ # Terminology Section Utility Container
2
+
3
+ This directory contains the modular components for the terminology section utility container functionality.
4
+
5
+ ## Architecture
6
+
7
+ The architecture follows a clear separation of concerns:
8
+
9
+ - **DOM Construction**: All layout and structure in `../terminology-section-utility-container.js`
10
+ - **Functionality**: Individual behavior modules in this directory
11
+
12
+ ## Structure
13
+
14
+ ```text
15
+ terminology-section-utility-container/
16
+ ├── README.md # This documentation
17
+ ├── hide-show-utility-container.js # Removes container when no terms
18
+ ├── create-alphabet-index.js # Alphabet navigation functionality
19
+ ├── create-term-filter.js # Local/Remote filter functionality
20
+ └── search.js # Search functionality
21
+ ```
22
+
23
+ ## Main Module (`../terminology-section-utility-container.js`)
24
+
25
+ The main coordination module contains:
26
+
27
+ ### DOM Construction Section
28
+ Complete layout definition in one place:
29
+
30
+ ```text
31
+ /* ===== ROW 1: ALPHABET INDEX ===== */
32
+ - Bootstrap row/col structure
33
+ - Alphabet navigation buttons
34
+ - Sorted character links
35
+
36
+ /* ===== ROW 2: UTILITIES ===== */
37
+ - Term count display
38
+ - Local/Remote filter checkboxes
39
+ - Search input with navigation buttons
40
+ - Matches counter
41
+ ```
42
+
43
+ ### Functionality Coordination
44
+ Calls the individual modules:
45
+
46
+ 1. `attachAlphabetIndexFunctionality()`
47
+ 2. `attachTermFilterFunctionality(checkboxesContainer)`
48
+ 3. `attachSearchFunctionality(searchInput, prevBtn, nextBtn, counter)`
49
+
50
+ ## Sub-Modules (Functionality Only)
51
+
52
+ Each module receives DOM elements as parameters and attaches behavior:
53
+
54
+ ### `create-alphabet-index.js`
55
+ - Currently minimal (standard anchor links)
56
+ - Ready for future enhancements (smooth scroll, analytics)
57
+
58
+ ### `create-term-filter.js`
59
+ - Checkbox change event handling
60
+ - "At least one checked" enforcement logic
61
+ - HTML class toggling for hide/show terms
62
+
63
+ ### `search.js`
64
+ - Input event handling with debouncing
65
+ - Text highlighting and regex matching
66
+ - Keyboard navigation (Arrow keys)
67
+ - Match counting and navigation
68
+
69
+ ## Dependencies
70
+
71
+ All modules depend on:
72
+
73
+ - Bootstrap 5.3+ (for styling classes)
74
+ - `specConfig` global object (for search styling configuration)
75
+
76
+ ## Usage
77
+
78
+ The modules are automatically loaded and initialized via the asset compilation process. The main module coordinates everything when the DOM is ready.
79
+
80
+ ## Layout Result
81
+
82
+ **Row 1 (Alphabet):**
83
+
84
+ ```text
85
+ [A] [B] [C] [D] [E] [F] [G] [H] [I] [J] [K] [L] [M] [N] [O] [P] [Q] [R] [S] [T] [U] [V] [W] [X] [Y] [Z]
86
+ ```
87
+
88
+ **Row 2 (Controls):**
89
+
90
+ ```text
91
+ 25 terms ☑ Local ☑ Remote [🔍 Search] [0 matches] [▲] [▼]
92
+ ```
93
+
94
+ ## Benefits of This Architecture
95
+
96
+ ✅ **Layout Visibility**: Complete DOM structure visible in one file
97
+ ✅ **Modular Logic**: Functionality separated by concern
98
+ ✅ **Maintainability**: Easy to modify layout or individual behaviors
99
+ ✅ **Testability**: Functions receive dependencies as parameters
100
+ ✅ **Reusability**: Logic modules could be used elsewhere
101
+
102
+ ## Maintenance Notes
103
+
104
+ - Order in `asset-map.json` matters: functionality modules before main module
105
+ - All modules use traditional function declarations for Gulp compatibility
106
+ - DOM elements are passed as parameters to avoid tight coupling
107
+ - Bootstrap classes are used extensively to minimize custom CSS
@@ -0,0 +1,17 @@
1
+ /**
2
+ * @file Alphabet index functionality
3
+ * @author Kor Dwarshuis
4
+ * @version 1.0.0
5
+ * @since 2024-08-31
6
+ * @description Handles alphabet navigation functionality (DOM is constructed in main module)
7
+ */
8
+
9
+ /**
10
+ * Attaches functionality to the alphabet index
11
+ * @returns {void}
12
+ */
13
+ function attachAlphabetIndexFunctionality() {
14
+ // Currently, alphabet links are just standard anchor links
15
+ // Functionality can be added here in the future if needed
16
+ // (e.g., smooth scrolling, analytics tracking, etc.)
17
+ }
@@ -1,14 +1,18 @@
1
1
  /**
2
- * @file This file creates an alphabet index for the terms
2
+ * @file Term filter functionality
3
3
  * @author Kor Dwarshuis
4
- * @version 0.0.1
5
- * @since 2024-09-19
4
+ * @version 1.0.0
5
+ * @since 2024-08-31
6
+ * @description Handles Local/Remote term filtering logic (DOM is constructed in main module)
6
7
  */
7
- function createTermFilter() {
8
+
9
+ /**
10
+ * Attaches functionality to the term filter checkboxes
11
+ * @param {HTMLElement} checkboxesContainer - The container with the checkboxes
12
+ * @returns {void}
13
+ */
14
+ function attachTermFilterFunctionality(checkboxesContainer) {
8
15
  // Check if the terms and definitions list exists
9
- // If it doesn't exist, exit the function
10
- // This prevents errors when the script is run on pages without the terms and definitions list
11
- // and ensures that the script only runs when necessary
12
16
  const termsListElement = document.querySelector(".terms-and-definitions-list");
13
17
  const dtElements = termsListElement ? termsListElement.querySelectorAll("dt") : [];
14
18
 
@@ -16,36 +20,6 @@ function createTermFilter() {
16
20
  return;
17
21
  }
18
22
 
19
- const terminologySectionUtilityContainer = document.getElementById("terminology-section-utility-container");
20
-
21
- // Create checkboxes container
22
- const checkboxesContainer = document.createElement('div');
23
- checkboxesContainer.className = 'd-flex mt-0';
24
-
25
- // Create and configure checkbox for local terms
26
- const localTermsCheckboxDiv = document.createElement('div');
27
- localTermsCheckboxDiv.className = 'form-check me-3';
28
- localTermsCheckboxDiv.innerHTML = `
29
- <input class="form-check-input" type="checkbox" id="showLocalTermsCheckbox" checked>
30
- <label class="form-check-label" for="showLocalTermsCheckbox">
31
- Show local terms
32
- </label>
33
- `;
34
-
35
- // Create and configure checkbox for external terms
36
- const externalTermsCheckboxDiv = document.createElement('div');
37
- externalTermsCheckboxDiv.className = 'form-check ms-3';
38
- externalTermsCheckboxDiv.innerHTML = `
39
- <input class="form-check-input" type="checkbox" id="showExternalTermsCheckbox" checked>
40
- <label class="form-check-label" for="showExternalTermsCheckbox">
41
- Show external terms
42
- </label>
43
- `;
44
-
45
- // Append checkboxes to container
46
- checkboxesContainer.appendChild(localTermsCheckboxDiv);
47
- checkboxesContainer.appendChild(externalTermsCheckboxDiv);
48
-
49
23
  // Add event listeners to checkboxes (generic for any number of checkboxes)
50
24
  function enforceAtLeastOneChecked(event) {
51
25
  const checkboxes = checkboxesContainer.querySelectorAll('input[type="checkbox"]');
@@ -81,11 +55,4 @@ function createTermFilter() {
81
55
  enforceAtLeastOneChecked(event);
82
56
  }
83
57
  });
84
-
85
- // Add checkboxes to the terminology section utility container
86
- terminologySectionUtilityContainer.appendChild(checkboxesContainer);
87
58
  }
88
-
89
- document.addEventListener("DOMContentLoaded", function () {
90
- createTermFilter();
91
- });
@@ -0,0 +1,21 @@
1
+ /**
2
+ * @file Hides the utility container when no terms exist
3
+ * @author Kor Dwarshuis
4
+ * @version 1.0.0
5
+ * @since 2024-08-31
6
+ * @description Removes the utility container if no terms and definitions are present
7
+ */
8
+
9
+ /**
10
+ * Hides the utility container if no terms exist
11
+ * @returns {void}
12
+ */
13
+ function hideShowUtilityContainer() {
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
+ document.getElementById("terminology-section-utility-container")?.remove();
20
+ }
21
+ }