sourcey 3.5.10 → 3.6.1

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 (100) hide show
  1. package/README.md +24 -97
  2. package/dist/adapters/index.d.ts +12 -0
  3. package/dist/adapters/index.d.ts.map +1 -0
  4. package/dist/adapters/index.js +130 -0
  5. package/dist/adapters/mkdocs.d.ts +5 -0
  6. package/dist/adapters/mkdocs.d.ts.map +1 -0
  7. package/dist/adapters/mkdocs.js +367 -0
  8. package/dist/adapters/shared.d.ts +12 -0
  9. package/dist/adapters/shared.d.ts.map +1 -0
  10. package/dist/adapters/shared.js +134 -0
  11. package/dist/adapters/types.d.ts +74 -0
  12. package/dist/adapters/types.d.ts.map +1 -0
  13. package/dist/adapters/types.js +1 -0
  14. package/dist/cli.js +28 -34
  15. package/dist/client/search.js +213 -82
  16. package/dist/client/tabs.js +23 -0
  17. package/dist/components/layout/Head.js +17 -4
  18. package/dist/components/layout/TableOfContents.d.ts.map +1 -1
  19. package/dist/components/layout/TableOfContents.js +4 -2
  20. package/dist/components/openapi/Introduction.d.ts.map +1 -1
  21. package/dist/components/openapi/Introduction.js +16 -1
  22. package/dist/components/openapi/Security.d.ts.map +1 -1
  23. package/dist/components/openapi/Security.js +18 -2
  24. package/dist/config.d.ts +101 -0
  25. package/dist/config.d.ts.map +1 -1
  26. package/dist/config.js +95 -143
  27. package/dist/core/api-rendering.d.ts +47 -0
  28. package/dist/core/api-rendering.d.ts.map +1 -0
  29. package/dist/core/api-rendering.js +76 -0
  30. package/dist/core/doxygen-loader.d.ts.map +1 -1
  31. package/dist/core/doxygen-loader.js +36 -17
  32. package/dist/core/godoc-loader.d.ts.map +1 -1
  33. package/dist/core/godoc-loader.js +17 -6
  34. package/dist/core/markdown-loader.d.ts +14 -0
  35. package/dist/core/markdown-loader.d.ts.map +1 -1
  36. package/dist/core/markdown-loader.js +70 -26
  37. package/dist/core/mcp-normalizer.d.ts.map +1 -1
  38. package/dist/core/mcp-normalizer.js +48 -34
  39. package/dist/core/navigation.d.ts.map +1 -1
  40. package/dist/core/navigation.js +8 -7
  41. package/dist/core/parser.d.ts.map +1 -1
  42. package/dist/core/parser.js +75 -4
  43. package/dist/core/rustdoc-introspector.d.ts +29 -0
  44. package/dist/core/rustdoc-introspector.d.ts.map +1 -0
  45. package/dist/core/rustdoc-introspector.js +236 -0
  46. package/dist/core/rustdoc-loader.d.ts +25 -0
  47. package/dist/core/rustdoc-loader.d.ts.map +1 -0
  48. package/dist/core/rustdoc-loader.js +469 -0
  49. package/dist/core/rustdoc-render.d.ts +76 -0
  50. package/dist/core/rustdoc-render.d.ts.map +1 -0
  51. package/dist/core/rustdoc-render.js +785 -0
  52. package/dist/core/rustdoc-types.d.ts +314 -0
  53. package/dist/core/rustdoc-types.d.ts.map +1 -0
  54. package/dist/core/rustdoc-types.js +18 -0
  55. package/dist/core/search-indexer.d.ts +10 -0
  56. package/dist/core/search-indexer.d.ts.map +1 -1
  57. package/dist/core/search-indexer.js +6 -1
  58. package/dist/core/sourcey-rustdoc/Cargo.lock +705 -0
  59. package/dist/core/sourcey-rustdoc/Cargo.toml +38 -0
  60. package/dist/core/sourcey-rustdoc/README.md +59 -0
  61. package/dist/core/sourcey-rustdoc/src/diagnostics.rs +42 -0
  62. package/dist/core/sourcey-rustdoc/src/doctest.rs +268 -0
  63. package/dist/core/sourcey-rustdoc/src/extract.rs +492 -0
  64. package/dist/core/sourcey-rustdoc/src/lib.rs +58 -0
  65. package/dist/core/sourcey-rustdoc/src/links.rs +126 -0
  66. package/dist/core/sourcey-rustdoc/src/main.rs +256 -0
  67. package/dist/core/sourcey-rustdoc/src/signature.rs +415 -0
  68. package/dist/core/sourcey-rustdoc/src/spec.rs +390 -0
  69. package/dist/core/sourcey-rustdoc/tests/format_version.rs +45 -0
  70. package/dist/dev-server.d.ts.map +1 -1
  71. package/dist/dev-server.js +109 -33
  72. package/dist/index.d.ts +4 -0
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +26 -14
  75. package/dist/init.d.ts.map +1 -1
  76. package/dist/init.js +21 -16
  77. package/dist/renderer/changelog-feed.d.ts.map +1 -1
  78. package/dist/renderer/changelog-feed.js +27 -19
  79. package/dist/renderer/html-builder.d.ts.map +1 -1
  80. package/dist/renderer/html-builder.js +4 -3
  81. package/dist/renderer/llms.js +57 -2
  82. package/dist/site-assembly.d.ts +5 -0
  83. package/dist/site-assembly.d.ts.map +1 -1
  84. package/dist/site-assembly.js +161 -25
  85. package/dist/themes/default/sourcey.css +300 -0
  86. package/dist/utils/code-samples.d.ts +1 -0
  87. package/dist/utils/code-samples.d.ts.map +1 -1
  88. package/dist/utils/code-samples.js +30 -22
  89. package/dist/utils/html.d.ts +10 -0
  90. package/dist/utils/html.d.ts.map +1 -0
  91. package/dist/utils/html.js +152 -0
  92. package/dist/utils/http.d.ts +0 -2
  93. package/dist/utils/http.d.ts.map +1 -1
  94. package/dist/utils/http.js +19 -34
  95. package/dist/utils/markdown.d.ts.map +1 -1
  96. package/dist/utils/markdown.js +115 -39
  97. package/dist/vite-plugin.d.ts +2 -0
  98. package/dist/vite-plugin.d.ts.map +1 -1
  99. package/dist/vite-plugin.js +71 -1
  100. package/package.json +11 -5
package/dist/cli.js CHANGED
@@ -7,7 +7,7 @@ import { loadConfig } from "./config.js";
7
7
  import { runIntrospector, GodocIntrospectorError } from "./core/godoc-introspector.js";
8
8
  import { GODOC_SCHEMA_VERSION } from "./core/godoc-types.js";
9
9
  import { init } from "./init.js";
10
- import { formatChangelogDiagnostic, formatGodocDiagnostic } from "./site-assembly.js";
10
+ import { formatChangelogDiagnostic, formatGodocDiagnostic, formatRustdocDiagnostic, } from "./site-assembly.js";
11
11
  import pkg from "../package.json" with { type: "json" };
12
12
  const build = defineCommand({
13
13
  meta: {
@@ -84,6 +84,7 @@ const build = defineCommand({
84
84
  if (!args.quiet) {
85
85
  logChangelogDiagnostics(result.changelogDiagnostics);
86
86
  logGodocDiagnostics(result.godocDiagnostics);
87
+ logRustdocDiagnostics(result.rustdocDiagnostics);
87
88
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
88
89
  console.log(` Pages: ${result.pageCount}`);
89
90
  console.log(` Output: ${result.outputDir}`);
@@ -129,8 +130,9 @@ const dev = defineCommand({
129
130
  },
130
131
  async run({ args }) {
131
132
  const { startDevServer } = await import("./dev-server.js");
133
+ const port = parsePort(args.port);
132
134
  await startDevServer({
133
- port: parseInt(args.port, 10),
135
+ port,
134
136
  host: args.host ?? process.env.HOST,
135
137
  config: args.config,
136
138
  strictChangelog: args.strictChangelog,
@@ -214,9 +216,15 @@ const godoc = defineCommand({
214
216
  },
215
217
  async run({ args }) {
216
218
  const moduleDir = resolve(process.cwd(), args.module);
217
- const patterns = args.packages.split(",").map((p) => p.trim()).filter(Boolean);
219
+ const patterns = args.packages
220
+ .split(",")
221
+ .map((p) => p.trim())
222
+ .filter(Boolean);
218
223
  const exclude = args.exclude
219
- ? args.exclude.split(",").map((p) => p.trim()).filter(Boolean)
224
+ ? args.exclude
225
+ .split(",")
226
+ .map((p) => p.trim())
227
+ .filter(Boolean)
220
228
  : [];
221
229
  const outputPath = resolve(process.cwd(), args.out);
222
230
  try {
@@ -282,36 +290,6 @@ const main = defineCommand({
282
290
  validate,
283
291
  godoc,
284
292
  },
285
- args: {
286
- spec: {
287
- type: "positional",
288
- description: "Path to an OpenAPI spec (shorthand for 'sourcey build <spec>')",
289
- required: false,
290
- },
291
- },
292
- async run({ args, rawArgs }) {
293
- if (rawArgs.includes("init") ||
294
- rawArgs.includes("build") ||
295
- rawArgs.includes("dev") ||
296
- rawArgs.includes("validate") ||
297
- rawArgs.includes("godoc"))
298
- return;
299
- if (args.spec) {
300
- await build.run({
301
- args: {
302
- _: [],
303
- spec: args.spec,
304
- output: "dist",
305
- embed: false,
306
- config: undefined,
307
- quiet: false,
308
- strictChangelog: false,
309
- },
310
- rawArgs: [],
311
- cmd: build,
312
- });
313
- }
314
- },
315
293
  });
316
294
  function logChangelogDiagnostics(diagnostics) {
317
295
  for (const diagnostic of diagnostics) {
@@ -324,4 +302,20 @@ function logGodocDiagnostics(diagnostics) {
324
302
  writer(` ${formatGodocDiagnostic(diagnostic)}`);
325
303
  }
326
304
  }
305
+ function logRustdocDiagnostics(diagnostics) {
306
+ for (const diagnostic of diagnostics) {
307
+ const writer = diagnostic.severity === "error" ? console.error : console.log;
308
+ writer(` ${formatRustdocDiagnostic(diagnostic)}`);
309
+ }
310
+ }
311
+ function parsePort(value) {
312
+ if (!/^\d+$/.test(value)) {
313
+ throw new Error(`Invalid port "${value}". Expected an integer from 1 to 65535.`);
314
+ }
315
+ const port = Number(value);
316
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
317
+ throw new Error(`Invalid port "${value}". Expected an integer from 1 to 65535.`);
318
+ }
319
+ return port;
320
+ }
327
321
  runMain(main);
@@ -1,10 +1,10 @@
1
1
  // Search — client-side search dialog with keyboard navigation
2
2
  (function () {
3
3
  function init() {
4
- var dialog = document.getElementById('search-dialog');
5
- var input = document.getElementById('search-input');
6
- var results = document.getElementById('search-results');
7
- var openBtn = document.getElementById('search-open');
4
+ var dialog = document.getElementById("search-dialog");
5
+ var input = document.getElementById("search-input");
6
+ var results = document.getElementById("search-results");
7
+ var openBtn = document.getElementById("search-open");
8
8
  if (!dialog || !input || !results) return;
9
9
 
10
10
  var entries = [];
@@ -16,127 +16,254 @@
16
16
  var searchMeta = document.querySelector('meta[name="sourcey-search"]');
17
17
 
18
18
  function loadJsonIndex(callback) {
19
- if (indexLoaded) { callback(); return; }
20
- if (!searchMeta) { indexLoaded = true; callback(); return; }
21
- var url = searchMeta.getAttribute('content');
22
- fetch(url).then(function (r) { return r.json(); }).then(function (data) {
23
- entries = data.map(function (e) {
24
- return {
25
- url: e.url,
26
- method: e.method || '',
27
- path: e.path || '',
28
- summary: e.title || '',
29
- tag: e.tab || '',
30
- content: e.content || '',
31
- category: e.category || '',
32
- featured: !!e.featured,
33
- searchText: [e.method || '', e.path || '', e.title || '', e.content || '', e.tab || ''].join(' ').toLowerCase()
34
- };
35
- });
36
- indexLoaded = true;
19
+ if (indexLoaded) {
37
20
  callback();
38
- }).catch(function () {
21
+ return;
22
+ }
23
+ if (!searchMeta) {
39
24
  indexLoaded = true;
40
25
  callback();
41
- });
26
+ return;
27
+ }
28
+ var url = searchMeta.getAttribute("content");
29
+ fetch(url)
30
+ .then(function (r) {
31
+ return r.json();
32
+ })
33
+ .then(function (data) {
34
+ entries = data.map(function (e) {
35
+ var title = e.title || "";
36
+ var qualifiedName = e.qualifiedName || "";
37
+ var owner = e.owner || "";
38
+ var tab = e.tab || "";
39
+ return {
40
+ url: e.url,
41
+ method: e.method || "",
42
+ path: e.path || "",
43
+ title: title,
44
+ summary: qualifiedName || title,
45
+ tag: owner ? owner : tab,
46
+ content: e.content || "",
47
+ category: e.category || "",
48
+ featured: !!e.featured,
49
+ symbolKind: e.symbolKind || "",
50
+ owner: owner,
51
+ ownerKind: e.ownerKind || "",
52
+ namespace: e.namespace || "",
53
+ qualifiedName: qualifiedName,
54
+ titleLower: title.toLowerCase(),
55
+ pathLower: (e.path || "").toLowerCase(),
56
+ qualifiedLower: qualifiedName.toLowerCase(),
57
+ ownerLower: owner.toLowerCase(),
58
+ searchText: [
59
+ e.method || "",
60
+ e.path || "",
61
+ title,
62
+ qualifiedName,
63
+ owner,
64
+ e.ownerKind || "",
65
+ e.namespace || "",
66
+ e.symbolKind || "",
67
+ e.content || "",
68
+ tab,
69
+ ]
70
+ .join(" ")
71
+ .toLowerCase(),
72
+ };
73
+ });
74
+ indexLoaded = true;
75
+ callback();
76
+ })
77
+ .catch(function () {
78
+ indexLoaded = true;
79
+ callback();
80
+ });
42
81
  }
43
82
 
44
- var dialogInner = dialog.querySelector('.search-dialog-inner');
83
+ var dialogInner = dialog.querySelector(".search-dialog-inner");
45
84
 
46
85
  function positionDialog() {
47
86
  if (!openBtn || !dialogInner) return;
48
87
  var rect = openBtn.getBoundingClientRect();
49
- dialogInner.style.position = 'absolute';
50
- dialogInner.style.top = (rect.top - 4) + 'px';
88
+ dialogInner.style.position = "absolute";
89
+ dialogInner.style.top = rect.top - 4 + "px";
51
90
  var extraWidth = Math.min(rect.width * 0.5, 200);
52
- dialogInner.style.left = (rect.left - extraWidth / 2) + 'px';
53
- dialogInner.style.width = (rect.width + extraWidth) + 'px';
54
- dialogInner.style.maxWidth = 'none';
55
- dialogInner.style.transform = 'none';
56
- dialogInner.style.margin = '0';
91
+ dialogInner.style.left = rect.left - extraWidth / 2 + "px";
92
+ dialogInner.style.width = rect.width + extraWidth + "px";
93
+ dialogInner.style.maxWidth = "none";
94
+ dialogInner.style.transform = "none";
95
+ dialogInner.style.margin = "0";
57
96
  }
58
97
 
59
98
  function open() {
60
99
  positionDialog();
61
- dialog.classList.add('open');
62
- input.value = '';
100
+ dialog.classList.add("open");
101
+ input.value = "";
63
102
  input.focus();
64
103
  if (!indexLoaded) {
65
104
  results.innerHTML = '<div class="search-loading">Loading…</div>';
66
- loadJsonIndex(function () { showResults(''); });
105
+ loadJsonIndex(function () {
106
+ showResults("");
107
+ });
67
108
  } else {
68
- showResults('');
109
+ showResults("");
69
110
  }
70
- document.addEventListener('keydown', onDialogKey);
111
+ document.addEventListener("keydown", onDialogKey);
71
112
  }
72
113
 
73
114
  function close() {
74
- dialog.classList.remove('open');
75
- document.removeEventListener('keydown', onDialogKey);
115
+ dialog.classList.remove("open");
116
+ document.removeEventListener("keydown", onDialogKey);
76
117
  }
77
118
 
78
119
  function showResults(query) {
79
120
  var q = query.toLowerCase().trim();
80
121
  if (!q) {
81
122
  // Show featured pages first, then endpoints
82
- var featured = entries.filter(function (e) { return e.featured; });
83
- var rest = entries.filter(function (e) { return !e.featured && e.category !== 'Sections'; });
123
+ var featured = entries.filter(function (e) {
124
+ return e.featured;
125
+ });
126
+ var rest = entries.filter(function (e) {
127
+ return !e.featured && e.category !== "Sections";
128
+ });
84
129
  filtered = featured.concat(rest).slice(0, 30);
85
130
  } else {
86
131
  var terms = q.split(/\s+/);
87
- filtered = entries.filter(function (e) {
88
- return terms.every(function (t) { return e.searchText.indexOf(t) !== -1; });
89
- });
90
- // Sort by category so groups stay together (only for search results)
91
- var categoryOrder = { Pages: 0, Sections: 1, Endpoints: 2, Models: 3 };
132
+ filtered = entries
133
+ .map(function (e) {
134
+ return { entry: e, score: scoreEntry(e, q, terms) };
135
+ })
136
+ .filter(function (result) {
137
+ return result.score > 0;
138
+ });
139
+ var categoryOrder = {
140
+ Pages: 0,
141
+ Endpoints: 1,
142
+ Models: 2,
143
+ Functions: 3,
144
+ Types: 4,
145
+ Enums: 5,
146
+ "Enum Values": 6,
147
+ Variables: 7,
148
+ Friends: 8,
149
+ Properties: 9,
150
+ Members: 10,
151
+ Sections: 20,
152
+ };
92
153
  filtered.sort(function (a, b) {
93
- return (categoryOrder[a.category] || 9) - (categoryOrder[b.category] || 9);
154
+ if (b.score !== a.score) return b.score - a.score;
155
+ return (categoryOrder[a.entry.category] || 19) - (categoryOrder[b.entry.category] || 19);
94
156
  });
157
+ filtered = filtered
158
+ .map(function (result) {
159
+ return result.entry;
160
+ })
161
+ .slice(0, 50);
95
162
  }
96
163
 
97
164
  activeIndex = filtered.length ? 0 : -1;
98
165
  render();
99
166
  }
100
167
 
168
+ function scoreEntry(e, q, terms) {
169
+ if (
170
+ !terms.every(function (t) {
171
+ return e.searchText.indexOf(t) !== -1;
172
+ })
173
+ )
174
+ return 0;
175
+
176
+ var compactQuery = q.replace(/\s+/g, "");
177
+ var qualified = e.qualifiedLower || "";
178
+ var title = e.titleLower || "";
179
+ var path = e.pathLower || "";
180
+ var owner = e.ownerLower || "";
181
+ var score = 1;
182
+
183
+ if (qualified && qualified === compactQuery) score += 1200;
184
+ if (title && title === q) score += 900;
185
+ if (path && path === q) score += 850;
186
+ if (qualified && qualified.endsWith("::" + compactQuery)) score += 780;
187
+ if (owner && compactQuery === owner + "::" + title) score += 760;
188
+ if (qualified && compactQuery.indexOf("::") !== -1 && qualified.indexOf(compactQuery) !== -1)
189
+ score += 700;
190
+ if (title && title.indexOf(q) === 0) score += 500;
191
+ if (qualified && qualified.indexOf(compactQuery) !== -1) score += 420;
192
+ if (owner && owner.indexOf(compactQuery) !== -1) score += 240;
193
+ if (path && path.indexOf(q) !== -1) score += 180;
194
+ if (e.content.toLowerCase().indexOf(q) !== -1) score += 80;
195
+
196
+ if (e.category === "Sections") score -= 70;
197
+ if (e.symbolKind) score += 35;
198
+ if (e.featured) score += 20;
199
+ return score;
200
+ }
201
+
101
202
  function render() {
102
- var html = '';
103
- var lastCategory = '';
203
+ var html = "";
204
+ var lastCategory = "";
104
205
 
105
206
  for (var i = 0; i < filtered.length; i++) {
106
207
  var e = filtered[i];
107
- var cat = e.category || 'Results';
208
+ var cat = e.category || "Results";
108
209
 
109
210
  if (cat !== lastCategory) {
110
- html += '<div class="search-category">' + escapeHtml(cat) + '</div>';
211
+ html += '<div class="search-category">' + escapeHtml(cat) + "</div>";
111
212
  lastCategory = cat;
112
213
  }
113
214
 
114
- var cls = 'search-result' + (i === activeIndex ? ' active' : '');
215
+ var cls = "search-result" + (i === activeIndex ? " active" : "");
115
216
  var label = e.method
116
- ? '<span class="search-result-method method-' + e.method.toLowerCase() + '">' + e.method + '</span> ' +
117
- '<span class="search-result-path">' + escapeHtml(e.path) + '</span>'
118
- : '<span class="search-result-path">' + escapeHtml(e.summary) + '</span>';
119
- var tagLine = e.tag ? '<span class="search-result-tag">' + escapeHtml(e.tag) + '</span>' : '';
120
- var summaryLine = e.method && e.summary ? '<span class="search-result-summary">' + escapeHtml(e.summary) + '</span>' : '';
121
-
122
- html += '<a href="' + e.url + '" class="' + cls + '" data-index="' + i + '">' +
123
- '<div class="search-result-main">' + label + summaryLine + '</div>' +
124
- tagLine + '</a>';
217
+ ? '<span class="search-result-method method-' +
218
+ e.method.toLowerCase() +
219
+ '">' +
220
+ e.method +
221
+ "</span> " +
222
+ '<span class="search-result-path">' +
223
+ escapeHtml(e.path) +
224
+ "</span>"
225
+ : '<span class="search-result-path">' + escapeHtml(e.summary) + "</span>";
226
+ var tagLine = e.tag
227
+ ? '<span class="search-result-tag">' + escapeHtml(e.tag) + "</span>"
228
+ : "";
229
+ var summaryLine =
230
+ e.method && e.summary
231
+ ? '<span class="search-result-summary">' + escapeHtml(e.summary) + "</span>"
232
+ : "";
233
+
234
+ html +=
235
+ '<a href="' +
236
+ escapeAttr(e.url) +
237
+ '" class="' +
238
+ cls +
239
+ '" data-index="' +
240
+ i +
241
+ '">' +
242
+ '<div class="search-result-main">' +
243
+ label +
244
+ summaryLine +
245
+ "</div>" +
246
+ tagLine +
247
+ "</a>";
125
248
  }
126
249
 
127
250
  results.innerHTML = html;
128
251
 
129
252
  // Scroll active result into view
130
- var activeEl = results.querySelector('.search-result.active');
131
- if (activeEl) activeEl.scrollIntoView({ block: 'nearest' });
253
+ var activeEl = results.querySelector(".search-result.active");
254
+ if (activeEl) activeEl.scrollIntoView({ block: "nearest" });
132
255
  }
133
256
 
134
257
  function escapeHtml(s) {
135
- var el = document.createElement('span');
258
+ var el = document.createElement("span");
136
259
  el.textContent = s;
137
260
  return el.innerHTML;
138
261
  }
139
262
 
263
+ function escapeAttr(s) {
264
+ return escapeHtml(s).replace(/"/g, "&quot;").replace(/'/g, "&#39;");
265
+ }
266
+
140
267
  function navigate(index) {
141
268
  if (index < 0 || index >= filtered.length) return;
142
269
  var entry = filtered[index];
@@ -145,71 +272,75 @@
145
272
  }
146
273
 
147
274
  function onDialogKey(e) {
148
- if (e.key === 'Escape') { close(); e.preventDefault(); return; }
149
- if (e.key === 'ArrowDown') {
275
+ if (e.key === "Escape") {
276
+ close();
277
+ e.preventDefault();
278
+ return;
279
+ }
280
+ if (e.key === "ArrowDown") {
150
281
  e.preventDefault();
151
282
  activeIndex = Math.min(activeIndex + 1, filtered.length - 1);
152
283
  render();
153
284
  return;
154
285
  }
155
- if (e.key === 'ArrowUp') {
286
+ if (e.key === "ArrowUp") {
156
287
  e.preventDefault();
157
288
  activeIndex = Math.max(activeIndex - 1, 0);
158
289
  render();
159
290
  return;
160
291
  }
161
- if (e.key === 'Enter') {
292
+ if (e.key === "Enter") {
162
293
  e.preventDefault();
163
294
  if (activeIndex >= 0) navigate(activeIndex);
164
295
  return;
165
296
  }
166
297
  }
167
298
 
168
- input.addEventListener('input', function () {
299
+ input.addEventListener("input", function () {
169
300
  showResults(input.value);
170
301
  });
171
302
 
172
- results.addEventListener('click', function (e) {
173
- var result = e.target.closest('.search-result');
303
+ results.addEventListener("click", function (e) {
304
+ var result = e.target.closest(".search-result");
174
305
  if (result) {
175
306
  e.preventDefault();
176
- navigate(parseInt(result.getAttribute('data-index'), 10));
307
+ navigate(parseInt(result.getAttribute("data-index"), 10));
177
308
  }
178
309
  });
179
310
 
180
311
  // Open search
181
- if (openBtn) openBtn.addEventListener('click', open);
312
+ if (openBtn) openBtn.addEventListener("click", open);
182
313
 
183
314
  // Also bind mobile search button
184
- var mobileBtn = document.getElementById('search-open-mobile');
185
- if (mobileBtn) mobileBtn.addEventListener('click', open);
315
+ var mobileBtn = document.getElementById("search-open-mobile");
316
+ if (mobileBtn) mobileBtn.addEventListener("click", open);
186
317
 
187
318
  // Keyboard shortcut: Ctrl+K or /
188
- document.addEventListener('keydown', function (e) {
189
- if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
319
+ document.addEventListener("keydown", function (e) {
320
+ if ((e.ctrlKey || e.metaKey) && e.key === "k") {
190
321
  e.preventDefault();
191
322
  open();
192
323
  }
193
- if (e.key === '/' && !isEditable(e.target)) {
324
+ if (e.key === "/" && !isEditable(e.target)) {
194
325
  e.preventDefault();
195
326
  open();
196
327
  }
197
328
  });
198
329
 
199
330
  // Close on backdrop click
200
- dialog.addEventListener('click', function (e) {
331
+ dialog.addEventListener("click", function (e) {
201
332
  if (e.target === dialog) close();
202
333
  });
203
334
 
204
335
  function isEditable(el) {
205
336
  var tag = el.tagName;
206
- return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
337
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || el.isContentEditable;
207
338
  }
208
339
  }
209
340
 
210
- if ('requestIdleCallback' in window) {
341
+ if ("requestIdleCallback" in window) {
211
342
  window.requestIdleCallback(init, { timeout: 250 });
212
343
  } else {
213
- window.addEventListener('load', init, { once: true });
344
+ window.addEventListener("load", init, { once: true });
214
345
  }
215
346
  })();
@@ -156,6 +156,29 @@
156
156
  });
157
157
  });
158
158
 
159
+ // ── Rust Doctest Hidden-Line Toggle ──────────────────────────────
160
+ // (Copy is handled by the shared .copy-btn inside each code block.)
161
+
162
+ document.addEventListener('click', function (e) {
163
+ var toggleBtn = e.target.closest('.rust-doctest-toggle-hidden');
164
+ if (!toggleBtn) return;
165
+ var fullSel = toggleBtn.getAttribute('data-target');
166
+ var displaySel = toggleBtn.getAttribute('data-display');
167
+ var fullEl = fullSel ? document.getElementById(fullSel.replace(/^#/, '')) : null;
168
+ var displayEl = displaySel ? document.getElementById(displaySel.replace(/^#/, '')) : null;
169
+ if (!fullEl || !displayEl) return;
170
+ var fullHidden = fullEl.hasAttribute('hidden');
171
+ if (fullHidden) {
172
+ fullEl.removeAttribute('hidden');
173
+ displayEl.setAttribute('hidden', '');
174
+ toggleBtn.textContent = 'Hide hidden lines';
175
+ } else {
176
+ fullEl.setAttribute('hidden', '');
177
+ displayEl.removeAttribute('hidden');
178
+ toggleBtn.textContent = 'Show hidden lines';
179
+ }
180
+ });
181
+
159
182
  // Close dropdowns on Escape
160
183
  document.addEventListener('keydown', function (e) {
161
184
  if (e.key === 'Escape') {
@@ -13,15 +13,15 @@ export function Head() {
13
13
  : undefined;
14
14
  const siteName = site.name || spec.info.title || "";
15
15
  const pageTitle = page.kind === "markdown"
16
- ? (siteName ? `${page.markdown.title} — ${siteName}` : page.markdown.title)
16
+ ? composePageTitle(page.markdown.title, siteName)
17
17
  : page.kind === "changelog"
18
18
  ? (() => {
19
19
  const baseTitle = changelogVersion
20
- ? `${changelogVersion.version ?? "Unreleased"} ${page.changelog.title}`
20
+ ? `${changelogVersion.version ?? "Unreleased"} - ${page.changelog.title}`
21
21
  : page.changelog.title;
22
- return siteName ? `${baseTitle} — ${siteName}` : baseTitle;
22
+ return composePageTitle(baseTitle, siteName);
23
23
  })()
24
- : `${siteName} API Reference`;
24
+ : composePageTitle(siteName, "API Reference");
25
25
  const pageDescription = page.kind === "markdown"
26
26
  ? page.markdown.description || pageTitle
27
27
  : page.kind === "changelog"
@@ -58,3 +58,16 @@ export function Head() {
58
58
  `;
59
59
  return (_jsxs("head", { children: [_jsx("meta", { charset: "utf-8" }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("title", { children: pageTitle }), _jsx("meta", { name: "description", content: pageDescription }), _jsx("meta", { name: "generator", content: `Sourcey ${pkg.version}` }), options.pageUrl && _jsx("link", { rel: "canonical", href: options.pageUrl }), _jsx("meta", { property: "og:title", content: pageTitle }), _jsx("meta", { property: "og:description", content: pageDescription }), _jsx("meta", { property: "og:type", content: "website" }), siteName && _jsx("meta", { property: "og:site_name", content: siteName }), options.pageUrl && _jsx("meta", { property: "og:url", content: options.pageUrl }), options.ogImageUrl && _jsx("meta", { property: "og:image", content: options.ogImageUrl }), options.ogImageUrl && _jsx("meta", { property: "og:image:width", content: "1200" }), options.ogImageUrl && _jsx("meta", { property: "og:image:height", content: "630" }), options.ogImageUrl && _jsx("meta", { property: "og:image:type", content: "image/png" }), _jsx("meta", { name: "twitter:card", content: options.ogImageUrl ? "summary_large_image" : "summary" }), _jsx("meta", { name: "twitter:title", content: pageTitle }), _jsx("meta", { name: "twitter:description", content: pageDescription }), options.ogImageUrl && _jsx("meta", { name: "twitter:image", content: options.ogImageUrl }), _jsx("meta", { name: "sourcey-search", content: `${options.assetBase}search-index.json` }), options.alternateLinks?.map((link) => (_jsx("link", { rel: "alternate", type: link.type, href: link.href, title: link.title }, `${link.type}-${link.href}`))), _jsx("link", { rel: "preconnect", href: "https://fonts.googleapis.com" }), _jsx("link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossorigin: "anonymous" }), _jsx("link", { rel: "stylesheet", href: `https://fonts.googleapis.com/css2?family=${encodeURIComponent(fonts.googleFont)}:wght@100..900&display=swap` }), _jsx("style", { dangerouslySetInnerHTML: { __html: themeCSS } }), showLangIconCSS && _jsx("style", { dangerouslySetInnerHTML: { __html: langIconCSS() } }), site.customCSS && _jsx("style", { dangerouslySetInnerHTML: { __html: site.customCSS } }), _jsx("script", { dangerouslySetInnerHTML: { __html: `(function(){var t=localStorage.getItem('sourcey-theme');if(t==='dark')document.documentElement.classList.add('dark')})()` } }), _jsx("link", { rel: "stylesheet", href: `${options.assetBase}sourcey.css` }), site.favicon && _jsx("link", { rel: "icon", href: site.favicon })] }));
60
60
  }
61
+ function composePageTitle(title, siteName) {
62
+ if (!siteName)
63
+ return title;
64
+ if (!title)
65
+ return siteName;
66
+ return sameTitle(title, siteName) ? title : `${title} - ${siteName}`;
67
+ }
68
+ function sameTitle(left, right) {
69
+ return normalizeTitle(left) === normalizeTitle(right);
70
+ }
71
+ function normalizeTitle(value) {
72
+ return value.trim().replace(/\s+/g, " ").toLowerCase();
73
+ }
@@ -1 +1 @@
1
- {"version":3,"file":"TableOfContents.d.ts","sourceRoot":"","sources":["../../../src/components/layout/TableOfContents.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AAyCjE,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,WAAW,EAAE,CAAA;CAAE,uCAuBxE"}
1
+ {"version":3,"file":"TableOfContents.d.ts","sourceRoot":"","sources":["../../../src/components/layout/TableOfContents.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,+BAA+B,CAAC;AA2CjE,wBAAgB,eAAe,CAAC,EAAE,QAAQ,EAAE,EAAE;IAAE,QAAQ,EAAE,WAAW,EAAE,CAAA;CAAE,uCAuBxE"}
@@ -7,10 +7,12 @@ import { iconPath } from "../../utils/icons.js";
7
7
  */
8
8
  const tocLink = "toc-item break-words block hover:text-[rgb(var(--color-gray-900))] dark:hover:text-[rgb(var(--color-gray-300))] dark:text-[rgb(var(--color-gray-400))] transition-colors";
9
9
  function TocList({ headings }) {
10
- // Group: each root heading (level 2) followed by its sub-headings (level 3+)
10
+ // Group by the shallowest heading present. Moxygen C++ pages often use
11
+ // h1 for the page title and h3 for sections, with no h2 in between.
12
+ const rootLevel = Math.min(...headings.map((h) => h.level));
11
13
  const groups = [];
12
14
  for (const h of headings) {
13
- if (h.level < 3) {
15
+ if (h.level <= rootLevel) {
14
16
  groups.push({ root: h, children: [] });
15
17
  }
16
18
  else if (groups.length) {
@@ -1 +1 @@
1
- {"version":3,"file":"Introduction.d.ts","sourceRoot":"","sources":["../../../src/components/openapi/Introduction.tsx"],"names":[],"mappings":"AAIA;;;GAGG;AACH,wBAAgB,YAAY,iCAqC3B"}
1
+ {"version":3,"file":"Introduction.d.ts","sourceRoot":"","sources":["../../../src/components/openapi/Introduction.tsx"],"names":[],"mappings":"AAKA;;;GAGG;AACH,wBAAgB,YAAY,iCAmE3B"}
@@ -1,6 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { useContext } from "preact/hooks";
3
3
  import { SpecContext } from "../../renderer/context.js";
4
+ import { safeUrl } from "../../utils/html.js";
4
5
  import { Markdown } from "../ui/Markdown.js";
5
6
  /**
6
7
  * API introduction: description, contact info, server URLs.
@@ -9,5 +10,19 @@ import { Markdown } from "../ui/Markdown.js";
9
10
  export function Introduction() {
10
11
  const spec = useContext(SpecContext);
11
12
  const { info, servers } = spec;
12
- return (_jsxs("div", { id: "introduction", "data-traverse-target": "introduction", class: "mb-8", children: [info.termsOfService && (_jsx("p", { class: "mt-4 text-sm text-[rgb(var(--color-gray-500))]", children: _jsx("a", { href: info.termsOfService, class: "text-[rgb(var(--color-primary-ink))] dark:text-[rgb(var(--color-primary-light))]", children: "Terms of Service" }) })), info.contact?.email && (_jsxs("p", { class: "mt-2 text-sm text-[rgb(var(--color-gray-500))]", children: ["Contact: ", _jsx("a", { href: `mailto:${info.contact.email}`, class: "text-[rgb(var(--color-primary-ink))] dark:text-[rgb(var(--color-primary-light))]", children: info.contact.email })] })), servers.length > 0 && (_jsxs("div", { class: "mt-6 rounded-[var(--radius)] border border-[rgb(var(--color-gray-200)/0.7)] dark:border-[rgb(var(--color-border-dark-subtle)/0.1)] overflow-x-auto", children: [_jsxs("div", { class: "px-4 py-2.5 text-xs font-semibold text-[rgb(var(--color-gray-600))] dark:text-[rgb(var(--color-gray-300))] border-b border-[rgb(var(--color-gray-200)/0.7)] dark:border-[rgb(var(--color-border-dark-subtle)/0.1)]", children: ["Base URL", servers.length > 1 ? "s" : ""] }), servers.map((s, i) => (_jsxs("div", { class: `flex items-baseline gap-3 px-4 py-2 ${i > 0 ? "border-t border-[rgb(var(--color-gray-100))] dark:border-[rgb(var(--color-gray-800))]" : ""}`, children: [_jsx("code", { class: "font-mono text-sm text-[rgb(var(--color-gray-800))] dark:text-[rgb(var(--color-gray-200))]", children: s.url }), s.description && (_jsx("span", { class: "text-xs text-[rgb(var(--color-gray-500))]", children: s.description }))] }, i)))] })), info.description && _jsx(Markdown, { content: info.description, class: "max-w-none mt-8" })] }));
13
+ const termsUrl = info.termsOfService
14
+ ? safeUrl(info.termsOfService, {
15
+ allowedProtocols: ["http:", "https:"],
16
+ allowRelative: false,
17
+ allowAnchor: false,
18
+ })
19
+ : null;
20
+ const contactEmailUrl = info.contact?.email
21
+ ? safeUrl(`mailto:${info.contact.email}`, {
22
+ allowedProtocols: ["mailto:"],
23
+ allowRelative: false,
24
+ allowAnchor: false,
25
+ })
26
+ : null;
27
+ return (_jsxs("div", { id: "introduction", "data-traverse-target": "introduction", class: "mb-8", children: [termsUrl && (_jsx("p", { class: "mt-4 text-sm text-[rgb(var(--color-gray-500))]", children: _jsx("a", { href: termsUrl, class: "text-[rgb(var(--color-primary-ink))] dark:text-[rgb(var(--color-primary-light))]", children: "Terms of Service" }) })), info.contact?.email && contactEmailUrl && (_jsxs("p", { class: "mt-2 text-sm text-[rgb(var(--color-gray-500))]", children: ["Contact:", " ", _jsx("a", { href: contactEmailUrl, class: "text-[rgb(var(--color-primary-ink))] dark:text-[rgb(var(--color-primary-light))]", children: info.contact.email })] })), servers.length > 0 && (_jsxs("div", { class: "mt-6 rounded-[var(--radius)] border border-[rgb(var(--color-gray-200)/0.7)] dark:border-[rgb(var(--color-border-dark-subtle)/0.1)] overflow-x-auto", children: [_jsxs("div", { class: "px-4 py-2.5 text-xs font-semibold text-[rgb(var(--color-gray-600))] dark:text-[rgb(var(--color-gray-300))] border-b border-[rgb(var(--color-gray-200)/0.7)] dark:border-[rgb(var(--color-border-dark-subtle)/0.1)]", children: ["Base URL", servers.length > 1 ? "s" : ""] }), servers.map((s, i) => (_jsxs("div", { class: `flex items-baseline gap-3 px-4 py-2 ${i > 0 ? "border-t border-[rgb(var(--color-gray-100))] dark:border-[rgb(var(--color-gray-800))]" : ""}`, children: [_jsx("code", { class: "font-mono text-sm text-[rgb(var(--color-gray-800))] dark:text-[rgb(var(--color-gray-200))]", children: s.url }), s.description && (_jsx("span", { class: "text-xs text-[rgb(var(--color-gray-500))]", children: s.description }))] }, i)))] })), info.description && _jsx(Markdown, { content: info.description, class: "max-w-none mt-8" })] }));
13
28
  }