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,207 @@
1
+ # Markdown-it Extensions
2
+
3
+ This directory contains the refactored markdown-it extensions used by the spec-up-t system. The extensions have been broken down into focused, well-documented modules to improve maintainability and understanding.
4
+
5
+ ## Module Overview
6
+
7
+ ### Core Philosophy
8
+
9
+ The markdown-it library uses a token-based rendering system where:
10
+
11
+ - **Tokens** represent different parts of the markdown (paragraphs, headers, tables, etc.)
12
+ - **Renderer rules** are functions that convert tokens to HTML
13
+ - **Inline rules** process inline elements during parsing
14
+ - **Plugins** extend the parser with custom functionality
15
+
16
+ Our extensions override default renderer rules and add custom inline parsing rules to implement spec-up-specific functionality.
17
+
18
+ ## Modules
19
+
20
+ ### 1. `table-enhancement.js`
21
+
22
+ **Purpose**: Enhances table rendering with Bootstrap styling and responsive wrappers.
23
+
24
+ **Features**:
25
+
26
+ - Adds Bootstrap CSS classes (`table`, `table-striped`, `table-bordered`, `table-hover`)
27
+ - Wraps tables in responsive containers (`table-responsive-md`)
28
+ - Preserves existing classes while adding new ones
29
+
30
+ **How it works**: Overrides `table_open` and `table_close` renderer rules.
31
+
32
+ ### 2. `template-tag-syntax.js`
33
+
34
+ **Purpose**: Processes custom template-tag syntax like `[[def:term]]`, `[[tref:spec,term]]`, etc.
35
+
36
+ **Features**:
37
+
38
+ - Parses `[[type:arg1,arg2]]` syntax during markdown processing
39
+ - Creates template tokens for later rendering
40
+ - Supports extensible template-tag handlers
41
+ - Integrates with the escape mechanism
42
+
43
+ **How it works**:
44
+
45
+ - Adds an inline ruler (`templates_ruler`) to detect and parse template-tag syntax
46
+ - Creates template tokens with parsed information
47
+ - Provides a renderer rule to convert template tokens to HTML
48
+
49
+ ### 3. `link-enhancement.js`
50
+
51
+ **Purpose**: Adds path-based attributes to links for CSS styling and JavaScript targeting.
52
+
53
+ **Features**:
54
+
55
+ - Extracts domain and path segments from URLs
56
+ - Adds `path-0`, `path-1`, etc. attributes to anchor tags
57
+ - Special handling for auto-detected links (linkify)
58
+
59
+ **How it works**: Overrides `link_open` and `link_close` renderer rules.
60
+
61
+ ### 4. `definition-lists.js`
62
+
63
+ **Purpose**: Advanced processing of definition lists for terminology and reference management.
64
+
65
+ **Features**:
66
+
67
+ - **Smart Classification**: Distinguishes between terminology lists and reference lists
68
+ - **Term Type Detection**: Identifies local terms (`[[def:term]]`) vs external terms (`[[tref:spec,term]]`)
69
+ - **Quality Control**: Removes empty `<dt>` elements that cause rendering issues
70
+ - **Section-Aware**: Only applies terminology styling after the terminology section marker
71
+
72
+ **How it works**:
73
+
74
+ - Overrides `dl_open`, `dt_open`, and `dt_close` renderer rules
75
+ - Uses helper functions to analyze token structure and content
76
+ - Applies CSS classes based on term types and context
77
+
78
+ ### 5. `index.js`
79
+
80
+ **Purpose**: Main orchestrator that applies all enhancements in the correct order.
81
+
82
+ **Features**:
83
+
84
+ - Single entry point for all markdown-it extensions
85
+ - Maintains dependency order between modules
86
+ - Provides both unified and individual module access
87
+
88
+ ## Usage
89
+
90
+ ### Basic Usage
91
+
92
+ ```javascript
93
+ const MarkdownIt = require('markdown-it');
94
+ const applyMarkdownItExtensions = require('./markdown-it');
95
+
96
+ const md = new MarkdownIt();
97
+ const templates = [
98
+ {
99
+ filter: type => type === 'def',
100
+ render: (token, type, term) => `<span id="term:${term}">${term}</span>`
101
+ }
102
+ ];
103
+
104
+ applyMarkdownItExtensions(md, templates);
105
+ const html = md.render('[[def:example-term]]');
106
+ ```
107
+
108
+ ### Individual Module Usage
109
+
110
+ ```javascript
111
+ const applyTableEnhancements = require('./markdown-it/table-enhancement');
112
+ const applyTemplateTagSyntax = require('./markdown-it/template-tag-syntax');
113
+
114
+ // Apply only table enhancements
115
+ applyTableEnhancements(md);
116
+
117
+ // Apply only template-tag syntax with custom handlers
118
+ applyTemplateTagSyntax(md, templates);
119
+ ```
120
+
121
+ ## Template System
122
+
123
+ The template-tag system processes custom syntax like `[[type:args]]` in markdown. Template-tags are defined as objects with:
124
+
125
+ - `filter(type)`: Function that returns true if this handler processes the given type
126
+ - `parse(token, type, ...args)`: Optional preprocessing function called during parsing
127
+ - `render(token, type, ...args)`: Function that returns HTML string for rendering
128
+
129
+ ### Example Template-Tag Handler
130
+
131
+ ```javascript
132
+ {
133
+ filter: type => type.match(/^def$/),
134
+ parse: (token, type, term, alias) => {
135
+ // Preprocessing during markdown parsing
136
+ definitions.push([term, alias]);
137
+ return `<span id="term:${term.replace(/\s+/g, '-').toLowerCase()}">${term}</span>`;
138
+ },
139
+ render: (token, type, ...args) => {
140
+ // Final rendering (if parse didn't handle it)
141
+ return token.content;
142
+ }
143
+ }
144
+ ```
145
+
146
+ ## Definition List Processing
147
+
148
+ The definition list module implements sophisticated logic for handling terminology:
149
+
150
+ ### List Classification
151
+
152
+ - **Terminology Lists**: Get `terms-and-definitions-list` class
153
+ - **Reference Lists**: Keep existing classes (e.g., `reference-list`)
154
+ - **Section-Aware**: Only processes lists after `terminology-section-start` marker
155
+
156
+ ### Term Classification
157
+
158
+ - **Local Terms** (`[[def:term]]`): Get `term-local` class
159
+ - **External Terms** (`[[tref:spec,term]]`): Get `term-external` class
160
+ - **Regular Terms**: No special class
161
+
162
+ ### Quality Control
163
+
164
+ - **Empty Elements**: Removes `<dt>` elements with no content
165
+ - **Spec References**: Avoids styling bibliographic references as terminology
166
+
167
+ ## Development Guidelines
168
+
169
+ ### Adding New Modules
170
+
171
+ 1. Create a focused module in this directory
172
+ 2. Follow the existing naming pattern (`feature-name.js`)
173
+ 3. Export a single function that takes a markdown-it instance
174
+ 4. Add comprehensive documentation with examples
175
+ 5. Update `index.js` to include the new module
176
+
177
+ ### Code Quality
178
+
179
+ - Keep cognitive complexity below 15
180
+ - Add extensive comments for markdown-it concepts
181
+ - Use descriptive function and variable names
182
+ - Extract helper functions for complex logic
183
+ - Write tests for new functionality
184
+
185
+ ### SonarQube Compliance
186
+
187
+ All modules must pass SonarQube analysis without issues. The refactoring specifically addresses:
188
+
189
+ - Reduced cognitive complexity through modularization
190
+ - Better code organization and separation of concerns
191
+ - Comprehensive documentation for maintainability
192
+
193
+ ## Backward Compatibility
194
+
195
+ The main `markdown-it-extensions.js` file maintains complete backward compatibility by delegating to the new modular system. Existing code can continue to use the original interface without changes.
196
+
197
+ ## Testing
198
+
199
+ Run the full test suite to ensure all functionality works correctly:
200
+
201
+ ```bash
202
+ npm test
203
+ ```
204
+
205
+ Individual modules can be tested by importing and using them directly in test files.
206
+
207
+ Please remember to run `gulp compile` after making changes to client-side assets, and test on a separate test machine before deployment.
@@ -0,0 +1,397 @@
1
+ 'use strict';
2
+
3
+ const { htmlComments } = require('../utils/regex-patterns');
4
+
5
+ /**
6
+ * Markdown-it Definition Lists Enhancement Module
7
+ *
8
+ * This module provides sophisticated enhancements for definition lists (<dl>) in markdown.
9
+ * It handles several key aspects of definition list processing in the spec-up system:
10
+ *
11
+ * 1. TERMINOLOGY CLASSIFICATION: Distinguishes between different types of definition lists:
12
+ * - Terms and definitions lists (terminology sections)
13
+ * - Specification reference lists (bibliographic references)
14
+ *
15
+ * 2. TERM TYPE DETECTION: Identifies and styles different types of terms:
16
+ * - Local terms (defined with [[def:term]]) - get 'term-local' class
17
+ * - External terms (referenced with [[tref:spec,term]]) - get 'term-external' class
18
+ * - Regular terms (no special class)
19
+ *
20
+ * 3. QUALITY CONTROL: Handles problematic definition list structures:
21
+ * - Empty <dt> elements (removes them entirely)
22
+ * - Proper class assignment to avoid conflicts
23
+ *
24
+ * 4. SECTION DETECTION: Uses markers to identify terminology sections and apply
25
+ * appropriate styling only where needed.
26
+ *
27
+ * This module is central to the spec-up system's terminology management capabilities.
28
+ */
29
+
30
+ /**
31
+ * Applies definition list enhancements to a markdown-it instance
32
+ *
33
+ * @param {Object} md - The markdown-it instance to enhance
34
+ *
35
+ * This function sets up custom renderers for definition list elements (dl_open, dt_open, dt_close)
36
+ * that provide intelligent classification and styling based on content analysis.
37
+ */
38
+ function applyDefinitionListEnhancements(md) {
39
+
40
+ // Store original renderers for fallback behavior
41
+ const originalDlRender = md.renderer.rules.dl_open || function (tokens, idx, options, env, self) {
42
+ return self.renderToken(tokens, idx, options);
43
+ };
44
+
45
+ const originalDtRender = md.renderer.rules.dt_open || function (tokens, idx, options, env, self) {
46
+ return self.renderToken(tokens, idx, options);
47
+ };
48
+
49
+ const originalDtCloseRender = md.renderer.rules.dt_close || function (tokens, idx, options, env, self) {
50
+ return self.renderToken(tokens, idx, options);
51
+ };
52
+
53
+ // State tracking: ensures we only add the terminology class once per document
54
+ let classAdded = false;
55
+
56
+ // ===================================================================================
57
+ // HELPER FUNCTIONS FOR TOKEN ANALYSIS
58
+ // ===================================================================================
59
+
60
+ /**
61
+ * Locates a specific HTML marker in the token stream
62
+ *
63
+ * This is used to find the "terminology-section-start" marker that indicates
64
+ * where terminology definitions begin in the document. Only definition lists
65
+ * that appear after this marker should be styled as terminology lists.
66
+ *
67
+ * @param {Array} tokens - The complete token array for the document
68
+ * @param {String} targetHtml - The HTML string to search for (e.g., 'terminology-section-start')
69
+ * @returns {Number} Index of the token containing the target HTML, or -1 if not found
70
+ */
71
+ function findTargetIndex(tokens, targetHtml) {
72
+ for (let i = 0; i < tokens.length; i++) {
73
+ if (tokens[i].content && tokens[i].content.includes(targetHtml)) {
74
+ return i;
75
+ }
76
+ }
77
+ return -1;
78
+ }
79
+
80
+ /**
81
+ * Identifies and marks empty definition term elements for removal
82
+ *
83
+ * Empty <dt> elements (where dt_open is immediately followed by dt_close with no content)
84
+ * cause rendering and styling problems. This function marks them with an 'isEmpty' flag
85
+ * so they can be skipped during the rendering phase.
86
+ *
87
+ * @param {Array} tokens - The token array to analyze
88
+ * @param {Number} startIdx - Index to start searching from (typically after dl_open)
89
+ */
90
+ function markEmptyDtElements(tokens, startIdx) {
91
+ for (let i = startIdx; i < tokens.length; i++) {
92
+ // Stop when we reach the end of this definition list
93
+ if (tokens[i].type === 'dl_close') {
94
+ break;
95
+ }
96
+
97
+ // Check for the empty dt pattern: dt_open immediately followed by dt_close
98
+ if (tokens[i].type === 'dt_open' &&
99
+ i + 1 < tokens.length &&
100
+ tokens[i + 1].type === 'dt_close') {
101
+
102
+ // Mark both tokens for removal during rendering
103
+ tokens[i].isEmpty = true;
104
+ tokens[i + 1].isEmpty = true;
105
+ }
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Placeholder for future last dd element processing
111
+ *
112
+ * This function was mentioned in the original code but not implemented.
113
+ * It could be used to identify the last <dd> in each dt/dd group for special styling.
114
+ *
115
+ * @param {Array} tokens - The token array to process
116
+ * @param {Number} startIdx - Index to start processing from
117
+ */
118
+ function processLastDdElements(tokens, startIdx) {
119
+ let lastDdIndex = -1; // Tracks the most recent dd_open token
120
+ // TODO: Implement if needed for future enhancements
121
+ }
122
+
123
+ // ===================================================================================
124
+ // SPEC REFERENCE DETECTION FUNCTIONS
125
+ // ===================================================================================
126
+
127
+ /**
128
+ * Determines if a definition list contains specification references
129
+ *
130
+ * Specification references are identified by dt elements with id attributes
131
+ * starting with "ref:" (e.g., id="ref:RFC2119"). These should NOT be styled
132
+ * as terminology lists since they serve a different purpose (bibliography).
133
+ *
134
+ * @param {Array} tokens - Token array to search through
135
+ * @param {Number} startIdx - Index to start searching from (after dl_open)
136
+ * @returns {Boolean} True if the dl contains spec references, false otherwise
137
+ */
138
+ function containsSpecReferences(tokens, startIdx) {
139
+ for (let i = startIdx; i < tokens.length; i++) {
140
+ if (tokens[i].type === 'dl_close') {
141
+ break; // End of this definition list
142
+ }
143
+
144
+ // Check all three ways spec references can appear
145
+ if (isDtRef(tokens[i]) || isHtmlRef(tokens[i]) || isInlineRef(tokens[i])) {
146
+ return true;
147
+ }
148
+ }
149
+ return false;
150
+ }
151
+
152
+ /**
153
+ * Checks if a dt_open token has a spec reference id attribute
154
+ *
155
+ * @param {Object} token - The token to check
156
+ * @returns {Boolean} True if token has id starting with "ref:"
157
+ */
158
+ function isDtRef(token) {
159
+ if (token.type !== 'dt_open' || !token.attrs) return false;
160
+ return token.attrs.some(attr => attr[0] === 'id' && attr[1].startsWith('ref:'));
161
+ }
162
+
163
+ /**
164
+ * Checks if an HTML token contains a spec reference id
165
+ *
166
+ * @param {Object} token - The token to check
167
+ * @returns {Boolean} True if HTML content contains id="ref:..."
168
+ */
169
+ function isHtmlRef(token) {
170
+ if (token.type !== 'html_block' && token.type !== 'html_inline') return false;
171
+ return token.content && token.content.includes('id="ref:');
172
+ }
173
+
174
+ /**
175
+ * Checks if an inline token contains a spec reference id
176
+ *
177
+ * @param {Object} token - The token to check
178
+ * @returns {Boolean} True if inline content contains id="ref:..."
179
+ */
180
+ function isInlineRef(token) {
181
+ if (token.type !== 'inline') return false;
182
+ return token.content && token.content.includes('id="ref:');
183
+ }
184
+
185
+ // ===================================================================================
186
+ // TERM TYPE DETECTION FUNCTIONS
187
+ // ===================================================================================
188
+
189
+ /**
190
+ * Determines if a definition term is transcluded from an external source
191
+ *
192
+ * Transcluded terms are created using [[tref:external-spec,term]] syntax.
193
+ * These terms are defined in other specifications and referenced here.
194
+ * They get the 'term-external' CSS class for distinctive styling.
195
+ *
196
+ * @param {Array} tokens - Token array to analyze
197
+ * @param {Number} dtOpenIndex - Index of the dt_open token to check
198
+ * @returns {Boolean} True if the term is transcluded (external), false otherwise
199
+ */
200
+ function isTermTranscluded(tokens, dtOpenIndex) {
201
+ // Search within this definition term only
202
+ for (let i = dtOpenIndex + 1; i < tokens.length; i++) {
203
+ if (tokens[i].type === 'dt_close') {
204
+ break; // End of this definition term
205
+ }
206
+
207
+ // Look for inline content with template tokens
208
+ if (tokens[i].type === 'inline' && tokens[i].children) {
209
+ for (let child of tokens[i].children) {
210
+ // Check if this is a tref (transcluded reference) template
211
+ if (child.type === 'template' &&
212
+ child.info &&
213
+ child.info.type === 'tref') {
214
+ return true;
215
+ }
216
+ }
217
+ }
218
+ }
219
+ return false;
220
+ }
221
+
222
+ /**
223
+ * Determines if a definition term is a local term definition
224
+ *
225
+ * Local terms are created using [[def:term,alias]] syntax.
226
+ * These are terms defined within the current specification.
227
+ * They get the 'term-local' CSS class for distinctive styling.
228
+ *
229
+ * @param {Array} tokens - Token array to analyze
230
+ * @param {Number} dtOpenIndex - Index of the dt_open token to check
231
+ * @returns {Boolean} True if the term is a local definition, false otherwise
232
+ */
233
+ function isLocalTerm(tokens, dtOpenIndex) {
234
+ // Search within this definition term only
235
+ for (let i = dtOpenIndex + 1; i < tokens.length; i++) {
236
+ if (tokens[i].type === 'dt_close') {
237
+ break; // End of this definition term
238
+ }
239
+
240
+ // Look for inline content with template tokens
241
+ if (tokens[i].type === 'inline' && tokens[i].children) {
242
+ for (let child of tokens[i].children) {
243
+ // Check if this is a def (definition) template
244
+ if (child.type === 'template' &&
245
+ child.info &&
246
+ child.info.type === 'def') {
247
+ return true;
248
+ }
249
+ }
250
+ }
251
+ }
252
+ return false;
253
+ }
254
+
255
+ // ===================================================================================
256
+ // CUSTOM RENDERERS
257
+ // ===================================================================================
258
+
259
+ /**
260
+ * Custom renderer for definition list opening tags (<dl>)
261
+ *
262
+ * This renderer implements intelligent classification of definition lists:
263
+ *
264
+ * 1. Finds the terminology section marker in the document
265
+ * 2. Checks if this dl already has styling (to avoid conflicts)
266
+ * 3. Determines if this dl contains spec references (bibliography vs terminology)
267
+ * 4. Adds 'terms-and-definitions-list' class only to appropriate terminology lists
268
+ * 5. Processes empty dt elements and last dd elements
269
+ *
270
+ * @param {Array} tokens - Complete token array
271
+ * @param {Number} idx - Index of current dl_open token
272
+ * @param {Object} options - Markdown-it options
273
+ * @param {Object} env - Environment/context object
274
+ * @param {Object} self - Renderer instance
275
+ * @returns {String} HTML for the opening <dl> tag
276
+ */
277
+ md.renderer.rules.dl_open = function (tokens, idx, options, env, self) {
278
+ const targetHtml = 'terminology-section-start';
279
+ let targetIndex = findTargetIndex(tokens, targetHtml);
280
+
281
+ // Check if this dl already has a class attribute (e.g., 'reference-list')
282
+ const existingClassIndex = tokens[idx].attrIndex('class');
283
+ const hasExistingClass = existingClassIndex >= 0;
284
+
285
+ // Check if this dl contains specification references
286
+ const hasSpecReferences = containsSpecReferences(tokens, idx + 1);
287
+
288
+ // Apply terminology list styling only if ALL conditions are met:
289
+ // 1. We found the terminology section marker
290
+ // 2. This dl appears after the marker
291
+ // 3. We haven't already added the class to a previous dl
292
+ // 4. This dl doesn't already have a class (preserves reference-list, etc.)
293
+ // 5. This dl doesn't contain spec references (avoids bibliography confusion)
294
+ if (targetIndex !== -1 &&
295
+ idx > targetIndex &&
296
+ !classAdded &&
297
+ !hasExistingClass &&
298
+ !hasSpecReferences) {
299
+ tokens[idx].attrPush(['class', 'terms-and-definitions-list']);
300
+ classAdded = true;
301
+ }
302
+
303
+ // Pre-process this definition list to handle problematic structures
304
+ markEmptyDtElements(tokens, idx + 1);
305
+ processLastDdElements(tokens, idx + 1);
306
+
307
+ return originalDlRender(tokens, idx, options, env, self);
308
+ };
309
+
310
+ /**
311
+ * Custom renderer for definition term opening tags (<dt>)
312
+ *
313
+ * This renderer handles:
314
+ * 1. Skipping empty dt elements (marked during dl_open processing)
315
+ * 2. Adding 'term-external' class to transcluded terms (from [[tref:...]])
316
+ * 3. Adding 'term-local' class to local definitions (from [[def:...]])
317
+ * 4. Leaving regular terms without special classes
318
+ *
319
+ * @param {Array} tokens - Complete token array
320
+ * @param {Number} idx - Index of current dt_open token
321
+ * @param {Object} options - Markdown-it options
322
+ * @param {Object} env - Environment/context object
323
+ * @param {Object} self - Renderer instance
324
+ * @returns {String} HTML for the opening <dt> tag, or empty string if skipped
325
+ */
326
+ md.renderer.rules.dt_open = function (tokens, idx, options, env, self) {
327
+ // Skip rendering empty dt elements that were marked during preprocessing
328
+ if (tokens[idx].isEmpty) {
329
+ return '';
330
+ }
331
+
332
+ // Look for the most recent file comment before this dt element
333
+ let sourceFile = null;
334
+
335
+ // Search backwards through all tokens to find the most recent HTML comment with file info
336
+ for (let i = idx - 1; i >= 0; i--) {
337
+ if (tokens[i].type === 'html_block' && tokens[i].content) {
338
+ const fileMatch = tokens[i].content.match(htmlComments.fileTracker);
339
+ if (fileMatch) {
340
+ sourceFile = fileMatch[1];
341
+ break; // Use the most recent file comment
342
+ }
343
+ }
344
+ }
345
+
346
+ // Add data-sourcefile attribute to the dt element if source file was found
347
+ if (sourceFile) {
348
+ tokens[idx].attrPush(['data-sourcefile', sourceFile]);
349
+ }
350
+
351
+ // Determine term type and add appropriate CSS class
352
+ if (isTermTranscluded(tokens, idx)) {
353
+ // External/transcluded term - add or append 'term-external' class
354
+ const classIndex = tokens[idx].attrIndex('class');
355
+ if (classIndex < 0) {
356
+ tokens[idx].attrPush(['class', 'term-external']);
357
+ } else {
358
+ tokens[idx].attrs[classIndex][1] += ' term-external';
359
+ }
360
+ } else if (isLocalTerm(tokens, idx)) {
361
+ // Local definition - add or append 'term-local' class
362
+ const classIndex = tokens[idx].attrIndex('class');
363
+ if (classIndex < 0) {
364
+ tokens[idx].attrPush(['class', 'term-local']);
365
+ } else {
366
+ tokens[idx].attrs[classIndex][1] += ' term-local';
367
+ }
368
+ }
369
+ // Regular terms get no special class
370
+
371
+ return originalDtRender(tokens, idx, options, env, self);
372
+ };
373
+
374
+ /**
375
+ * Custom renderer for definition term closing tags (</dt>)
376
+ *
377
+ * This renderer ensures that empty dt elements are completely omitted
378
+ * from the final HTML output by skipping their closing tags as well.
379
+ *
380
+ * @param {Array} tokens - Complete token array
381
+ * @param {Number} idx - Index of current dt_close token
382
+ * @param {Object} options - Markdown-it options
383
+ * @param {Object} env - Environment/context object
384
+ * @param {Object} self - Renderer instance
385
+ * @returns {String} HTML for the closing </dt> tag, or empty string if skipped
386
+ */
387
+ md.renderer.rules.dt_close = function (tokens, idx, options, env, self) {
388
+ // Skip rendering the closing tag for empty dt elements
389
+ // This completes the removal of problematic empty dt structures
390
+ if (tokens[idx].isEmpty) {
391
+ return '';
392
+ }
393
+ return originalDtCloseRender(tokens, idx, options, env, self);
394
+ };
395
+ }
396
+
397
+ module.exports = applyDefinitionListEnhancements;
@@ -0,0 +1,83 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Markdown-it Extensions Module Orchestrator
5
+ *
6
+ * This module serves as the main entry point for all markdown-it enhancements
7
+ * used in the spec-up system. It provides a clean interface to apply all
8
+ * custom rendering rules and functionality to a markdown-it instance.
9
+ *
10
+ * The module is organized into focused sub-modules, each handling a specific
11
+ * aspect of markdown processing:
12
+ *
13
+ * - TABLE ENHANCEMENT: Bootstrap styling and responsive wrappers
14
+ * - TEMPLATE-TAG SYNTAX: Custom [[template-tag:args]] syntax processing
15
+ * - LINK ENHANCEMENT: Path-based attributes for links
16
+ * - DEFINITION LISTS: Advanced terminology and reference list handling
17
+ *
18
+ * This modular approach makes the code more maintainable and easier to
19
+ * understand for developers unfamiliar with the markdown-it library.
20
+ */
21
+
22
+ // Import all the specialized enhancement modules
23
+ const applyTableEnhancements = require('./table-enhancement');
24
+ const applyTemplateTagSyntax = require('./template-tag-syntax');
25
+ const applyLinkEnhancements = require('./link-enhancement');
26
+ const applyDefinitionListEnhancements = require('./definition-lists');
27
+
28
+ /**
29
+ * Applies all markdown-it enhancements to a markdown-it instance
30
+ *
31
+ * This is the main function that consuming code should call. It orchestrates
32
+ * the application of all enhancement modules in the correct order.
33
+ *
34
+ * @param {Object} md - The markdown-it instance to enhance
35
+ * @param {Array} templates - Array of template-tag handler objects for custom syntax
36
+ * Each template-tag handler should have:
37
+ * - filter(type): function returning true if handler processes this type
38
+ * - parse(token, type, ...args): optional preprocessing function
39
+ * - render(token, type, ...args): function returning HTML string
40
+ *
41
+ * @example
42
+ * const MarkdownIt = require('markdown-it');
43
+ * const applyMarkdownItExtensions = require('./markdown-it');
44
+ *
45
+ * const md = new MarkdownIt();
46
+ * const templates = [
47
+ * {
48
+ * filter: type => type === 'def',
49
+ * render: (token, type, term) => `<span id="term:${term}">${term}</span>`
50
+ * }
51
+ * ];
52
+ *
53
+ * applyMarkdownItExtensions(md, templates);
54
+ * const html = md.render('[[def:example-term]]');
55
+ */
56
+ function applyMarkdownItExtensions(md, templates = []) {
57
+
58
+ // Apply enhancements in order of dependency
59
+ // Some modules may depend on others being applied first
60
+
61
+ // 1. Table enhancements - independent, can be applied first
62
+ applyTableEnhancements(md);
63
+
64
+ // 2. Template-tag syntax - should be applied early as other modules may depend on it
65
+ applyTemplateTagSyntax(md, templates);
66
+
67
+ // 3. Link enhancements - independent, can be applied anytime
68
+ applyLinkEnhancements(md);
69
+
70
+ // 4. Definition lists - depends on template-tag syntax for term type detection
71
+ applyDefinitionListEnhancements(md);
72
+
73
+ // The markdown-it instance is now fully enhanced and ready for use
74
+ }
75
+
76
+ // Export the main orchestrator function
77
+ module.exports = applyMarkdownItExtensions;
78
+
79
+ // Also export individual modules for fine-grained control if needed
80
+ module.exports.tableEnhancements = applyTableEnhancements;
81
+ module.exports.templateTagSyntax = applyTemplateTagSyntax;
82
+ module.exports.linkEnhancements = applyLinkEnhancements;
83
+ module.exports.definitionLists = applyDefinitionListEnhancements;