spec-up-t 1.1.54 → 1.2.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 (52) hide show
  1. package/assets/compiled/body.js +59 -8
  2. package/assets/compiled/head.css +13 -12
  3. package/assets/css/adjust-font-size.css +11 -0
  4. package/assets/css/collapse-meta-info.css +27 -8
  5. package/assets/css/create-term-filter.css +11 -0
  6. package/assets/css/index.css +15 -0
  7. package/assets/css/prism.css +176 -153
  8. package/assets/css/prism.dark.css +157 -0
  9. package/assets/css/prism.default.css +180 -0
  10. package/assets/css/terms-and-definitions.1.css +223 -0
  11. package/assets/css/terms-and-definitions.css +214 -100
  12. package/assets/js/addAnchorsToTerms.js +1 -1
  13. package/assets/js/collapse-definitions.js +2 -1
  14. package/assets/js/collapse-meta-info.js +25 -11
  15. package/assets/js/create-term-filter.js +61 -0
  16. package/assets/js/horizontal-scroll-hint.js +159 -0
  17. package/assets/js/index.1.js +137 -0
  18. package/assets/js/index.js +2 -1
  19. package/assets/js/insert-trefs.js +122 -116
  20. package/assets/js/insert-xrefs.1.js +372 -0
  21. package/assets/js/{show-commit-hashes.js → insert-xrefs.js} +67 -7
  22. package/assets/js/prism.dark.js +24 -0
  23. package/assets/js/prism.default.js +23 -0
  24. package/assets/js/prism.js +4 -5
  25. package/assets/js/search.js +1 -1
  26. package/assets/js/toggle-dense-info.js +40 -0
  27. package/branches.md +4 -29
  28. package/index.js +397 -189
  29. package/index.new.js +662 -0
  30. package/package.json +1 -1
  31. package/src/asset-map.json +9 -5
  32. package/src/collect-external-references.js +16 -9
  33. package/src/collectExternalReferences/fetchTermsFromIndex.js +328 -0
  34. package/src/collectExternalReferences/processXTrefsData.js +73 -18
  35. package/src/create-pdf.js +385 -89
  36. package/src/health-check/external-specs-checker.js +207 -0
  37. package/src/health-check/output-gitignore-checker.js +261 -0
  38. package/src/health-check/specs-configuration-checker.js +274 -0
  39. package/src/health-check/term-references-checker.js +191 -0
  40. package/src/health-check/terms-intro-checker.js +81 -0
  41. package/src/health-check/tref-term-checker.js +463 -0
  42. package/src/health-check.js +445 -0
  43. package/src/install-from-boilerplate/config-scripts-keys.js +2 -0
  44. package/src/install-from-boilerplate/menu.sh +6 -3
  45. package/src/markdown-it-extensions.js +134 -103
  46. package/src/prepare-tref.js +61 -24
  47. package/src/utils/fetch.js +100 -0
  48. package/templates/template.html +12 -7
  49. package/assets/js/css-helper.js +0 -30
  50. package/src/collectExternalReferences/fetchTermsFromGitHubRepository.js +0 -232
  51. package/src/collectExternalReferences/fetchTermsFromGitHubRepository.test.js +0 -385
  52. package/src/collectExternalReferences/octokitClient.js +0 -96
@@ -7,6 +7,49 @@ const contentRegex = /\s*([^\s\[\]:]+):?\s*([^\]\n]+)?/i;
7
7
 
8
8
  module.exports = function (md, templates = {}) {
9
9
 
10
+ // Add table renderer to apply Bootstrap classes to all tables by default
11
+ const originalTableRender = md.renderer.rules.table_open || function (tokens, idx, options, env, self) {
12
+ return self.renderToken(tokens, idx, options);
13
+ };
14
+
15
+ // Save the original table_close renderer
16
+ const originalTableCloseRender = md.renderer.rules.table_close || function (tokens, idx, options, env, self) {
17
+ return self.renderToken(tokens, idx, options);
18
+ };
19
+
20
+ // Override table_open to add both the classes and open a wrapper div
21
+ md.renderer.rules.table_open = function (tokens, idx, options, env, self) {
22
+ // Add Bootstrap classes to the table element
23
+ const token = tokens[idx];
24
+ const classIndex = token.attrIndex('class');
25
+ const tableClasses = 'table table-striped table-bordered table-hover';
26
+
27
+ if (classIndex < 0) {
28
+ token.attrPush(['class', tableClasses]);
29
+ } else {
30
+ // If a class attribute already exists, append our classes
31
+ const existingClasses = token.attrs[classIndex][1];
32
+ // Only add classes that aren't already present
33
+ const classesToAdd = tableClasses
34
+ .split(' ')
35
+ .filter(cls => !existingClasses.includes(cls))
36
+ .join(' ');
37
+
38
+ if (classesToAdd) {
39
+ token.attrs[classIndex][1] = existingClasses + ' ' + classesToAdd;
40
+ }
41
+ }
42
+
43
+ // Add the responsive wrapper div before the table
44
+ return '<div class="table-responsive">' + originalTableRender(tokens, idx, options, env, self);
45
+ };
46
+
47
+ // Override table_close to close the wrapper div
48
+ md.renderer.rules.table_close = function (tokens, idx, options, env, self) {
49
+ // Close the table and add the closing div
50
+ return originalTableCloseRender(tokens, idx, options, env, self) + '</div>';
51
+ };
52
+
10
53
  md.inline.ruler.after('emphasis', 'templates', function templates_ruler(state, silent) {
11
54
 
12
55
  var start = state.pos;
@@ -78,7 +121,6 @@ module.exports = function (md, templates = {}) {
78
121
  let classAdded = false;
79
122
 
80
123
  md.renderer.rules.dl_open = function (tokens, idx, options, env, self) {
81
-
82
124
  const targetHtml = 'terminology-section-start-h7vc6omi2hr2880';
83
125
  let targetIndex = -1;
84
126
 
@@ -94,115 +136,35 @@ module.exports = function (md, templates = {}) {
94
136
  if (targetIndex !== -1 && idx > targetIndex && !classAdded) {
95
137
  tokens[idx].attrPush(['class', 'terms-and-definitions-list']);
96
138
  classAdded = true;
139
+ }
97
140
 
98
- /* Sort terms and definitions alphabetically
99
- Sort dt/dd pairs case-insensitively based on dt content
100
-
101
- 1: Token-based Markdown Processing: Spec-Up-T uses a token-based approach to parse and render Markdown. When Markdown is processed, it's converted into a series of tokens that represent different elements (like dt_open, dt_content, dt_close, dd_open, dd_content, dd_close). We're not dealing with simple strings but with structured tokens.
102
-
103
- 2: Preserving Relationships: When sorting terms, we need to ensure that each definition term (<dt>) stays connected to its corresponding definition description (<dd>). It's not as simple as sorting an array of strings - we're sorting complex structures.
104
-
105
- 3: Implementation Details: The implementation includes:
106
-
107
- - Finding the terminology section in the document
108
- - Collecting term starts, ends, and their contents
109
- - Creating a sorted index based on case-insensitive comparisons
110
- - Rebuilding the token array in the correct order
111
- - Ensuring all relationships between terms and definitions are preserved
112
- - Handling special cases and edge conditions
113
-
114
- The complexity is unavoidable because:
115
-
116
- - We're working with the markdown-it rendering pipeline, not just manipulating DOM
117
- - The terms and definitions exist as tokens before they become HTML
118
- - We need to preserve all the token relationships while reordering
119
- - We're intercepting the rendering process to modify the token structure
120
-
121
- If we were just sorting DOM elements after the page rendered, it would be simpler. But by doing the sorting during the Markdown processing, we ensure the HTML output is correct from the beginning, which is more efficient and leads to better performance.
122
- */
123
- let dtStartIndices = [];
124
- let dtEndIndices = [];
125
- let dtContents = [];
141
+ let lastDdIndex = -1;
142
+ let currentDtIndex = -1; // Track current dt to detect empty dt elements
126
143
 
127
- // First pass: collect all dt blocks and their contents
128
- for (let i = idx + 1; i < tokens.length; i++) {
129
- if (tokens[i].type === 'dl_close') {
130
- break;
131
- }
132
- if (tokens[i].type === 'dt_open') {
133
- const startIdx = i;
134
- let content = '';
135
-
136
- // Find the end of this dt block and capture its content
137
- for (let j = i + 1; j < tokens.length; j++) {
138
- if (tokens[j].type === 'dt_close') {
139
- dtStartIndices.push(startIdx);
140
- dtEndIndices.push(j);
141
- dtContents.push(content.toLowerCase()); // Store lowercase for case-insensitive sorting
142
- break;
143
- }
144
- // Collect the content inside the dt (including spans with term IDs)
145
- if (tokens[j].content) {
146
- content += tokens[j].content;
147
- }
148
- }
149
- }
144
+ // First pass - check for and mark empty dt elements
145
+ // This scan identifies definition terms that have no content (empty dt elements)
146
+ // which is one of the root causes of the issues we're fixing
147
+ for (let i = idx + 1; i < tokens.length; i++) {
148
+ if (tokens[i].type === 'dl_close') {
149
+ break;
150
150
  }
151
-
152
- // Create indices sorted by case-insensitive term content
153
- const sortedIndices = dtContents.map((_, idx) => idx)
154
- .sort((a, b) => dtContents[a].localeCompare(dtContents[b]));
155
-
156
- // Reorder the tokens based on the sorted indices
157
- if (sortedIndices.length > 0) {
158
- // Create a new array of tokens
159
- const newTokens = tokens.slice(0, idx + 1); // Include dl_open
160
-
161
- // For each dt/dd pair in sorted order
162
- for (let i = 0; i < sortedIndices.length; i++) {
163
- const originalIndex = sortedIndices[i];
164
- const dtStart = dtStartIndices[originalIndex];
165
- const dtEnd = dtEndIndices[originalIndex];
166
-
167
- // Add dt tokens
168
- for (let j = dtStart; j <= dtEnd; j++) {
169
- newTokens.push(tokens[j]);
170
- }
171
-
172
- // Find and add dd tokens
173
- let ddFound = false;
174
- for (let j = dtEnd + 1; j < tokens.length; j++) {
175
- if (tokens[j].type === 'dt_open' || tokens[j].type === 'dl_close') {
176
- break;
177
- }
178
- if (tokens[j].type === 'dd_open') {
179
- ddFound = true;
180
- }
181
- if (ddFound) {
182
- newTokens.push(tokens[j]);
183
- if (tokens[j].type === 'dd_close') {
184
- break;
185
- }
186
- }
187
- }
188
- }
189
-
190
- // Add the closing dl token
191
- for (let i = idx + 1; i < tokens.length; i++) {
192
- if (tokens[i].type === 'dl_close') {
193
- newTokens.push(tokens[i]);
194
- break;
195
- }
151
+
152
+ if (tokens[i].type === 'dt_open') {
153
+ currentDtIndex = i;
154
+ // Check if this is an empty dt (no content between dt_open and dt_close)
155
+ // An empty dt is when a dt_close token immediately follows a dt_open token
156
+ if (i + 1 < tokens.length && tokens[i + 1].type === 'dt_close') {
157
+ // Mark this dt pair for handling by adding an isEmpty property
158
+ // This property will be used later to skip rendering these empty elements
159
+ tokens[i].isEmpty = true;
160
+ tokens[i + 1].isEmpty = true;
196
161
  }
197
-
198
- // Replace the old tokens with the new sorted ones
199
- tokens.splice(idx, newTokens.length, ...newTokens);
200
162
  }
201
- // END Sort terms and definitions alphabetically
202
163
  }
203
164
 
204
- let lastDdIndex = -1;
205
-
165
+ // Second pass - add classes and handle last-dd
166
+ // Now that we've identified empty dt elements, we can process the tokens
167
+ // while skipping the empty ones
206
168
  for (let i = idx + 1; i < tokens.length; i++) {
207
169
  if (tokens[i].type === 'dl_close') {
208
170
  // Add class to the last <dd> before closing <dl>
@@ -219,6 +181,12 @@ module.exports = function (md, templates = {}) {
219
181
  }
220
182
 
221
183
  if (tokens[i].type === 'dt_open') {
184
+ // Skip empty dt elements - this is where we use the isEmpty flag
185
+ // to avoid processing empty definition terms
186
+ if (tokens[i].isEmpty) {
187
+ continue; // Skip to the next iteration without processing this empty dt
188
+ }
189
+
222
190
  // Add class to the last <dd> before a new <dt>
223
191
  if (lastDdIndex !== -1) {
224
192
  const ddToken = tokens[lastDdIndex];
@@ -239,4 +207,67 @@ module.exports = function (md, templates = {}) {
239
207
 
240
208
  return originalRender(tokens, idx, options, env, self);
241
209
  };
210
+
211
+ // Override the rendering of dt elements to properly handle transcluded terms
212
+ const originalDtRender = md.renderer.rules.dt_open || function (tokens, idx, options, env, self) {
213
+ return self.renderToken(tokens, idx, options);
214
+ };
215
+
216
+ md.renderer.rules.dt_open = function (tokens, idx, options, env, self) {
217
+ // Skip rendering empty dt elements - this is the first critical fix
218
+ // When a dt has been marked as empty, we return an empty string
219
+ // instead of rendering the <dt> tag. This effectively removes empty dt tags
220
+ // from the output HTML.
221
+ if (tokens[idx].isEmpty) {
222
+ return '';
223
+ }
224
+
225
+ // Check if this dt is part of a transcluded term by looking at the next inline token
226
+ // This is part of the second fix, to properly handle transcluded terms
227
+ let isTranscluded = false;
228
+ for (let i = idx + 1; i < tokens.length; i++) {
229
+ if (tokens[i].type === 'dt_close') {
230
+ break;
231
+ }
232
+ // Look for child tokens that are template tokens with type 'tref'
233
+ // These represent transcluded terms from external sources
234
+ if (tokens[i].type === 'inline' &&
235
+ tokens[i].children &&
236
+ tokens[i].children.some(child =>
237
+ child.type === 'template' &&
238
+ child.info &&
239
+ child.info.type === 'tref')) {
240
+ isTranscluded = true;
241
+ break;
242
+ }
243
+ }
244
+
245
+ // Add a class for transcluded terms to ensure proper styling
246
+ // This helps maintain consistent styling for transcluded terms
247
+ if (isTranscluded) {
248
+ const classIndex = tokens[idx].attrIndex('class');
249
+ if (classIndex < 0) {
250
+ tokens[idx].attrPush(['class', 'transcluded-xref-term']);
251
+ } else {
252
+ tokens[idx].attrs[classIndex][1] += ' transcluded-xref-term';
253
+ }
254
+ }
255
+
256
+ return originalDtRender(tokens, idx, options, env, self);
257
+ };
258
+
259
+ // Similarly override dt_close to skip empty dts
260
+ const originalDtCloseRender = md.renderer.rules.dt_close || function (tokens, idx, options, env, self) {
261
+ return self.renderToken(tokens, idx, options);
262
+ };
263
+
264
+ md.renderer.rules.dt_close = function (tokens, idx, options, env, self) {
265
+ // Skip rendering the closing </dt> tag for empty dt elements
266
+ // This completes the fix for empty dt elements by ensuring neither
267
+ // the opening nor closing tags are rendered
268
+ if (tokens[idx].isEmpty) {
269
+ return '';
270
+ }
271
+ return originalDtCloseRender(tokens, idx, options, env, self);
272
+ };
242
273
  };
@@ -26,23 +26,40 @@ const { shouldProcessFile } = require('./utils/file-filter');
26
26
 
27
27
  function getLocalXTrefContent(externalSpec, term) {
28
28
  const filePath = path.join('output', 'xtrefs-data.json');
29
- const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
30
- const xtrefs = data.xtrefs;
31
-
32
- for (const xtref of xtrefs) {
33
- if (xtref.externalSpec === externalSpec && xtref.term === term) {
34
- return {
35
- content: xtref.content,
36
- commitHash: xtref.commitHash,
37
- owner: xtref.owner,
38
- repo: xtref.repo,
39
- repoUrl: xtref.repoUrl,
40
- avatarUrl: xtref.avatarUrl
41
- };
29
+
30
+ try {
31
+ const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
32
+ const xtrefs = data.xtrefs;
33
+
34
+ for (const xtref of xtrefs) {
35
+ if (xtref.externalSpec === externalSpec && xtref.term === term) {
36
+ // Validate that required properties exist
37
+ if (!xtref.content || !xtref.owner || !xtref.repo || !xtref.repoUrl) {
38
+ console.warn(`Warning: Incomplete data for ${externalSpec}, ${term}`);
39
+ }
40
+
41
+ return {
42
+ content: xtref.content || "No content available",
43
+ commitHash: xtref.commitHash || "Not available",
44
+ owner: xtref.owner || "Unknown",
45
+ repo: xtref.repo || "Unknown",
46
+ repoUrl: xtref.repoUrl || "#",
47
+ avatarUrl: xtref.avatarUrl || ""
48
+ };
49
+ }
42
50
  }
51
+ } catch (err) {
52
+ console.error(`Error reading xtrefs-data.json: ${err}`);
43
53
  }
44
54
 
45
- return null;
55
+ return {
56
+ content: `Term '${term}' not found in external specification '${externalSpec}'`,
57
+ commitHash: "Not available",
58
+ owner: "Unknown",
59
+ repo: "Unknown",
60
+ repoUrl: "#",
61
+ avatarUrl: ""
62
+ };
46
63
  }
47
64
 
48
65
  // Function to process markdown files in a directory recursively
@@ -93,18 +110,34 @@ function prepareTref(directory) {
93
110
  if (lines[i].startsWith('[[tref:')) {
94
111
  const tref = /\[\[tref:(.*?)\]\]/;
95
112
  const match = lines[i].match(tref);
113
+ let currentTref = lines[i]; // Store the current tref line for error handling
114
+
96
115
  if (match) {
97
- const result = match[1].split(',').map(term => term.trim());
98
- const localXTrefContent = getLocalXTrefContent(result[0], result[1]);
99
-
100
- const defPart = /\[\[def: ([^,]+),.*?\]\]/g;
101
- localXTrefContent.content = localXTrefContent.content.replace(defPart, '');
102
-
103
- const readyForWrite = dedent`
116
+ try {
117
+ const result = match[1].split(',').map(term => term.trim());
118
+
119
+ if (result.length < 2) {
120
+ throw new Error(`Invalid tref format. Expected: [[tref:spec,term]], got: ${match[0]}`);
121
+ }
122
+
123
+ const localXTrefContent = getLocalXTrefContent(result[0], result[1]);
124
+
125
+ // Skip processing if essential data is missing
126
+ if (!localXTrefContent) {
127
+ console.warn(`Warning: No content found for ${result[0]}, ${result[1]}`);
128
+ continue;
129
+ }
130
+
131
+ const defPart = /\[\[def: ([^,]+),.*?\]\]/g;
132
+ if (localXTrefContent.content) {
133
+ localXTrefContent.content = localXTrefContent.content.replace(defPart, '');
134
+ }
135
+
136
+ const readyForWrite = dedent`
104
137
  ${match[0]}
105
138
  | Property | Value |
106
139
  | -------- | ----- |
107
- | Owner | ![avatar](${localXTrefContent.avatarUrl}) ${localXTrefContent.owner} |
140
+ | Owner | ${localXTrefContent.avatarUrl ? `![avatar](${localXTrefContent.avatarUrl})` : ''} ${localXTrefContent.owner} |
108
141
  | Repo | [${localXTrefContent.repo}](${localXTrefContent.repoUrl}) |
109
142
  | Commit hash | ${localXTrefContent.commitHash} |
110
143
 
@@ -115,12 +148,16 @@ ${contentAfterSpan}
115
148
 
116
149
  `;
117
150
 
118
- fs.writeFileSync(itemPath, readyForWrite, 'utf8');
151
+ fs.writeFileSync(itemPath, readyForWrite, 'utf8');
152
+ } catch (err) {
153
+ console.error(`Error processing tref: ${err}`);
154
+ fs.writeFileSync(itemPath, currentTref + '\n\n' + '\n\nError processing reference: ' + err.message, 'utf8');
155
+ }
119
156
  }
120
157
  }
121
158
  }
122
159
  } catch (err) {
123
- fs.writeFileSync(itemPath, match[0] + '\n\n' + '\n\nNothing found, so nothing to show.', 'utf8');
160
+ console.error(`Error processing file ${itemPath}: ${err}`);
124
161
  }
125
162
  }
126
163
  });
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Utility functions to fetch and parse various JSON specification files
3
+ */
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ /**
8
+ * Fetches the output/specs-generated.json file and returns it as a JavaScript object
9
+ * @returns {Object} The parsed contents of specs-generated.json
10
+ */
11
+ function fetchSpecs() {
12
+ try {
13
+ // Resolve path to output/specs-generated.json from the project root
14
+ const specsPath = path.resolve(process.cwd(), 'output', 'specs-generated.json');
15
+
16
+ // Read the file synchronously
17
+ const specsContent = fs.readFileSync(specsPath, 'utf8');
18
+
19
+ // Parse the JSON content
20
+ const specs = JSON.parse(specsContent);
21
+
22
+ return specs;
23
+ } catch (error) {
24
+ console.error('Error fetching output/specs-generated.json:', error.message);
25
+ return null;
26
+ }
27
+ }
28
+
29
+ /**
30
+ * Fetches the output/xtrefs-data.json file and returns it as a JavaScript object
31
+ * @returns {Object} The parsed contents of xtrefs-data.json
32
+ */
33
+ function fetchExternalTerms() {
34
+ try {
35
+ // Resolve path to output/xtrefs-data.json from the project root
36
+ const xtrefsPath = path.resolve(process.cwd(), 'output', 'xtrefs-data.json');
37
+
38
+ // Read the file synchronously
39
+ const xtrefsContent = fs.readFileSync(xtrefsPath, 'utf8');
40
+
41
+ // Parse the JSON content
42
+ const xtrefs = JSON.parse(xtrefsContent);
43
+
44
+ return xtrefs;
45
+ } catch (error) {
46
+ console.error('Error fetching output/xtrefs-data.json:', error.message);
47
+ return null;
48
+ }
49
+ }
50
+
51
+ /**
52
+ * Asynchronous version of fetchGeneratedSpecs
53
+ * @returns {Promise<Object>} The parsed specs-generated object
54
+ */
55
+ async function fetchSpecsAsync() {
56
+ try {
57
+ // Resolve path to output/specs-generated.json from the project root
58
+ const specsPath = path.resolve(process.cwd(), 'output', 'specs-generated.json');
59
+
60
+ // Read the file asynchronously
61
+ const specsContent = await fs.promises.readFile(specsPath, 'utf8');
62
+
63
+ // Parse the JSON content
64
+ const specs = JSON.parse(specsContent);
65
+
66
+ return specs;
67
+ } catch (error) {
68
+ console.error('Error fetching output/specs-generated.json:', error.message);
69
+ return null;
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Asynchronous version of fetchXtrefsData
75
+ * @returns {Promise<Object>} The parsed xtrefs-data object
76
+ */
77
+ async function fetchExternalTermsAsync() {
78
+ try {
79
+ // Resolve path to output/xtrefs-data.json from the project root
80
+ const xtrefsPath = path.resolve(process.cwd(), 'output', 'xtrefs-data.json');
81
+
82
+ // Read the file asynchronously
83
+ const xtrefsContent = await fs.promises.readFile(xtrefsPath, 'utf8');
84
+
85
+ // Parse the JSON content
86
+ const xtrefs = JSON.parse(xtrefsContent);
87
+
88
+ return xtrefs;
89
+ } catch (error) {
90
+ console.error('Error fetching output/xtrefs-data.json:', error.message);
91
+ return null;
92
+ }
93
+ }
94
+
95
+ module.exports = {
96
+ fetchSpecs,
97
+ fetchSpecsAsync,
98
+ fetchExternalTerms,
99
+ fetchExternalTermsAsync
100
+ };
@@ -109,7 +109,7 @@
109
109
 
110
110
  <header id="header" class="navbar sticky-top p-0 shadow">
111
111
  <!-- Left-aligned elements -->
112
- <button class="nav-link px-3 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu"
112
+ <button class="nav-link d-print-none px-3 d-md-none" type="button" data-bs-toggle="offcanvas" data-bs-target="#sidebarMenu"
113
113
  aria-controls="sidebarMenu" aria-expanded="false" aria-label="Toggle navigation">
114
114
  <svg class="bi">
115
115
  <use xlink:href="#list" />
@@ -117,7 +117,7 @@
117
117
  </button>
118
118
 
119
119
  <a id="logo" href="${specLogoLink}">
120
- <img class="m-1" src="${specLogo}" alt="" />
120
+ <img class="d-print-none m-1" src="${specLogo}" alt="" />
121
121
  </a>
122
122
  <!-- <a class="navbar-brand col-md-3 col-lg-2 me-0 px-3 fs-6 text-white" href="#">Spec-Up-T</a> -->
123
123
 
@@ -125,7 +125,7 @@
125
125
  <div class="flex-grow-1"></div>
126
126
 
127
127
  <!-- Right-aligned elements -->
128
- <div class="d-flex align-items-center">
128
+ <div class="d-flex align-items-center service-menu d-print-none">
129
129
 
130
130
  <!-- Settings side menu -->
131
131
  <button id="repo_settings" animate class="btn btn-sm btn-outline-secondary ms-2" type="button"
@@ -137,7 +137,7 @@
137
137
  </button>
138
138
 
139
139
  <!-- Dark / Light theme selector -->
140
- <div class="dropdown bd-mode-toggle me-2">
140
+ <div class="dropdown bd-mode-toggle">
141
141
  <button
142
142
  class="btn btn-bd-primary btn-sm btn-outline-secondary dropdown-toggle d-flex align-items-center"
143
143
  id="bd-theme" type="button" aria-expanded="false" data-bs-toggle="dropdown"
@@ -175,13 +175,18 @@
175
175
  </ul>
176
176
  </div>
177
177
 
178
+ <!-- Container width toggle -->
179
+ <button id="container_toggle" title="Toggle wide/narrow layout" type="button" class="btn btn-sm btn-outline-secondary me-2">
180
+ <span class="visually-hidden">Toggle wide/narrow layout</span>
181
+ <i class="bi bi-arrows-angle-expand"></i>
182
+ </button>
183
+
178
184
  <!-- Font size -->
179
185
  <div class="adjust-font-size btn-group me-2" role="group">
180
186
  <button title="Decrease font size" type="button"
181
187
  class="btn btn-outline-secondary m-0 border-end-0" id="decreaseBtn"><span
182
188
  class="visually-hidden">Decrease font size</span>
183
189
  </button>
184
- <!-- <button type="button" class="btn btn-sm btn-outline-secondary m-0 px-2" id="resetBtn">0</button> -->
185
190
  <button title="Increase font size" type="button"
186
191
  class="btn btn-outline-secondary m-0 border-start-0" id="increaseBtn">
187
192
  <span class="visually-hidden">Increase font size</span>
@@ -192,7 +197,7 @@
192
197
 
193
198
  <div class="container">
194
199
  <div class="row">
195
- <div class="sidebar border border-right col-md-4 col-lg-3 bg-body-tertiary">
200
+ <div class="sidebar d-print-none border border-right col-md-4 col-lg-3 bg-body-tertiary">
196
201
  <div class="offcanvas-md offcanvas-start bg-body-tertiary p-2 pt-5" tabindex="-1" id="sidebarMenu"
197
202
  aria-labelledby="sidebarMenuLabel">
198
203
  <div class="offcanvas-header">
@@ -210,7 +215,7 @@
210
215
 
211
216
  <main class="col-md-8 ms-sm-auto col-lg-9 px-md-4 pt-5">
212
217
  <article id="content" class="">
213
- <div id="terminology-section-utility-container" class="alert alert-primary p-3"></div>
218
+ <div id="terminology-section-utility-container" class="d-print-none alert alert-primary p-3"></div>
214
219
  ${render}
215
220
  </article>
216
221
 
@@ -1,30 +0,0 @@
1
- /*
2
- JS to help with CSS.
3
- */
4
-
5
- function addClassToTranscludedTerms() {
6
- // Find all spans with class 'transcluded-xref-term'
7
- const spans = document.querySelectorAll('span.transcluded-xref-term');
8
-
9
- spans.forEach(span => {
10
- // Find the closest <dt> ancestor
11
- const dt = span.closest('dt');
12
- if (dt) {
13
- // Add class 'transcluded-xref-term' to the <dt>
14
- dt.classList.add('transcluded-xref-term');
15
-
16
- // Get the next sibling elements until the next <dt> or </dl>
17
- let sibling = dt.nextElementSibling;
18
- while (sibling && sibling.tagName !== 'DT' && sibling.tagName !== 'DL') {
19
- if (sibling.tagName === 'DD') {
20
- sibling.classList.add('transcluded-xref-term');
21
- }
22
- sibling = sibling.nextElementSibling;
23
- }
24
- }
25
- });
26
- }
27
-
28
- document.addEventListener("DOMContentLoaded", function () {
29
- addClassToTranscludedTerms();
30
- });