sourcey 3.4.0 → 3.4.3

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 (49) hide show
  1. package/dist/client/scroll-tracker.js +168 -156
  2. package/dist/client/search.js +186 -160
  3. package/dist/client/sidebar.js +56 -48
  4. package/dist/client/tabs.js +154 -146
  5. package/dist/components/layout/Head.d.ts.map +1 -1
  6. package/dist/components/layout/Head.js +4 -12
  7. package/dist/components/layout/Header.d.ts.map +1 -1
  8. package/dist/components/layout/Header.js +3 -3
  9. package/dist/components/layout/Page.d.ts.map +1 -1
  10. package/dist/components/layout/Page.js +9 -9
  11. package/dist/components/layout/Sidebar.d.ts.map +1 -1
  12. package/dist/components/layout/Sidebar.js +18 -14
  13. package/dist/components/layout/TableOfContents.d.ts.map +1 -1
  14. package/dist/components/layout/TableOfContents.js +2 -1
  15. package/dist/components/openapi/EndpointBar.js +5 -5
  16. package/dist/components/openapi/Introduction.js +1 -1
  17. package/dist/components/openapi/Responses.js +4 -4
  18. package/dist/components/openapi/Security.js +1 -1
  19. package/dist/components/ui/SectionLabel.js +1 -1
  20. package/dist/config.d.ts +8 -0
  21. package/dist/config.d.ts.map +1 -1
  22. package/dist/config.js +7 -3
  23. package/dist/core/doxygen-loader.d.ts +1 -0
  24. package/dist/core/doxygen-loader.d.ts.map +1 -1
  25. package/dist/core/doxygen-loader.js +12 -3
  26. package/dist/core/markdown-loader.d.ts.map +1 -1
  27. package/dist/core/markdown-loader.js +46 -2
  28. package/dist/core/search-indexer.d.ts +3 -1
  29. package/dist/core/search-indexer.d.ts.map +1 -1
  30. package/dist/core/search-indexer.js +7 -3
  31. package/dist/dev-server.js +2 -2
  32. package/dist/index.d.ts.map +1 -1
  33. package/dist/index.js +7 -2
  34. package/dist/init.js +1 -1
  35. package/dist/renderer/html-builder.d.ts +2 -0
  36. package/dist/renderer/html-builder.d.ts.map +1 -1
  37. package/dist/renderer/html-builder.js +6 -0
  38. package/dist/renderer/llms.d.ts +6 -0
  39. package/dist/renderer/llms.d.ts.map +1 -0
  40. package/dist/renderer/llms.js +247 -0
  41. package/dist/themes/default/main.css +6 -3
  42. package/dist/themes/default/sourcey.css +92 -58
  43. package/dist/utils/icons.d.ts +4 -0
  44. package/dist/utils/icons.d.ts.map +1 -1
  45. package/dist/utils/icons.js +6 -0
  46. package/dist/utils/markdown.d.ts +10 -0
  47. package/dist/utils/markdown.d.ts.map +1 -1
  48. package/dist/utils/markdown.js +82 -6
  49. package/package.json +1 -1
@@ -16,6 +16,27 @@ export function renderCodeBlock(text, lang) {
16
16
  <div class="prose-code-content">${shiki}</div>
17
17
  </div>`;
18
18
  }
19
+ /* ── Shared directive helpers ─────────────────────────────────────── */
20
+ function parseDirectiveAttrs(raw) {
21
+ const attrs = {};
22
+ for (const [, k, v] of raw.matchAll(/(\w+)="([^"]*)"/g))
23
+ attrs[k] = v;
24
+ return attrs;
25
+ }
26
+ function escAttr(s) {
27
+ return s.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
28
+ }
29
+ function requireHttps(url) {
30
+ try {
31
+ const u = new URL(url);
32
+ if (u.protocol !== "https:" && u.protocol !== "http:")
33
+ return null;
34
+ return u.href;
35
+ }
36
+ catch {
37
+ return null;
38
+ }
39
+ }
19
40
  function parseVideoUrl(url) {
20
41
  // YouTube
21
42
  let m = url.match(/(?:youtube\.com\/watch\?v=|youtu\.be\/)([\w-]+)/);
@@ -42,15 +63,13 @@ const videoExtension = {
42
63
  const match = src.match(/^::video\[([^\]]+)\](?:\{([^}]*)\})?/);
43
64
  if (!match)
44
65
  return undefined;
45
- const url = match[1];
46
- const attrs = match[2] ?? "";
47
- const titleMatch = attrs.match(/title="([^"]*)"/);
48
- return { type: "video", raw: match[0], url, title: titleMatch?.[1] ?? "" };
66
+ const attrs = parseDirectiveAttrs(match[2] ?? "");
67
+ return { type: "video", raw: match[0], url: match[1], title: attrs.title ?? "" };
49
68
  },
50
69
  renderer(token) {
51
70
  const { url, title } = token;
52
71
  const parsed = parseVideoUrl(url);
53
- const safeTitle = title.replace(/"/g, "&quot;").replace(/</g, "&lt;");
72
+ const safeTitle = escAttr(title);
54
73
  if (parsed.type === "iframe") {
55
74
  return `<div class="prose-video not-prose">
56
75
  <iframe src="${parsed.src}" title="${safeTitle}" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen loading="lazy"></iframe>
@@ -65,8 +84,36 @@ const videoExtension = {
65
84
  },
66
85
  ],
67
86
  };
87
+ const iframeExtension = {
88
+ extensions: [
89
+ {
90
+ name: "iframe",
91
+ level: "block",
92
+ start(src) {
93
+ return src.match(/::iframe\[/)?.index;
94
+ },
95
+ tokenizer(src) {
96
+ const match = src.match(/^::iframe\[([^\]]+)\](?:\{([^}]*)\})?/);
97
+ if (!match)
98
+ return undefined;
99
+ const attrs = parseDirectiveAttrs(match[2] ?? "");
100
+ const height = parseInt(attrs.height ?? "", 10);
101
+ return { type: "iframe", raw: match[0], url: match[1], title: attrs.title ?? "", height: Number.isFinite(height) ? height : 400 };
102
+ },
103
+ renderer(token) {
104
+ const { url, title, height } = token;
105
+ const safeUrl = requireHttps(url);
106
+ if (!safeUrl)
107
+ return `<p>[iframe: invalid URL]</p>\n`;
108
+ return `<div class="prose-iframe not-prose" style="height:${height}px">
109
+ <iframe src="${escAttr(safeUrl)}" title="${escAttr(title)}" frameborder="0" loading="lazy" allowfullscreen></iframe>
110
+ </div>\n`;
111
+ },
112
+ },
113
+ ],
114
+ };
68
115
  /** Singleton Marked instance — code blocks get Shiki + prose wrapper, headings get IDs. */
69
- const marked = new Marked(videoExtension, {
116
+ const marked = new Marked(videoExtension, iframeExtension, {
70
117
  renderer: {
71
118
  code({ text, lang }) {
72
119
  return renderCodeBlock(text, lang);
@@ -110,6 +157,35 @@ export function extractHeadings(input) {
110
157
  }
111
158
  return headings;
112
159
  }
160
+ /**
161
+ * Extract the first paragraph token from markdown.
162
+ * Useful for generated docs where a summary paragraph is embedded in the body.
163
+ */
164
+ export function extractFirstParagraph(input) {
165
+ if (!input)
166
+ return "";
167
+ const tokens = marked.lexer(input);
168
+ for (const token of tokens) {
169
+ if (token.type === "paragraph") {
170
+ const raw = token.raw.trim();
171
+ if (!raw)
172
+ continue;
173
+ if (!raw.replace(/<[^>]+>/g, "").trim())
174
+ continue;
175
+ return raw;
176
+ }
177
+ }
178
+ return "";
179
+ }
180
+ /**
181
+ * Replace markdown links with their visible labels.
182
+ * Useful when rendering inside an existing clickable container.
183
+ */
184
+ export function stripMarkdownLinks(input) {
185
+ if (!input)
186
+ return "";
187
+ return input.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "$1");
188
+ }
113
189
  /**
114
190
  * Render Markdown to HTML.
115
191
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sourcey",
3
- "version": "3.4.0",
3
+ "version": "3.4.3",
4
4
  "description": "Open source documentation platform. API references, guides, static output.",
5
5
  "type": "module",
6
6
  "engines": {