starlight-cannoli-plugins 2.14.2 → 2.16.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.
package/README.md CHANGED
@@ -361,6 +361,8 @@ An Astro integration that injects a client-side script to apply opt-in DOM patch
361
361
  - `limitDetailsElementHeight` (optional, default: `false`): Limits the height of expanded `<details>` elements and makes their content scrollable.
362
362
  - `offerToggleAllDetails` (optional, default: `false`): Injects an "Expand All Dropdowns" toggle checkbox into the right sidebar (before `nav[aria-labelledby="starlight__on-this-page"]`). Clicking it opens or closes every `<details>` element on the page at once. Only appears on pages that contain at least one visible `<details>` element.
363
363
  - `offerTabbedContent` (optional, default: `false`): Injects a "Tabbed view" toggle checkbox immediately after `#starlight__on-this-page` in the right sidebar. When enabled by the user, the page's markdown content is reorganised into tabs — one per `<h2>` heading, plus an optional "Main" tab for any content that appears before the first `<h2>`. The toggle state is persisted to `localStorage`; active tab selection is not persisted. Only activates when the page has at least two sections (i.e. two or more `<h2>` elements, or one `<h2>` with pre-heading content). Has no effect on pages that lack `#starlight__on-this-page` (e.g. pages with the TOC disabled). Clicking a TOC anchor while tabs are enabled automatically switches to the tab containing the target heading. The generated elements use the classes `tabbed-content`, `tabbed-content-nav`, `tabbed-content-tab`, and `tabbed-content-panel` for styling; toggle button uses the existing `.toggle-checkbox-btn` class.
364
+ - `decorateExternalLinks` (optional): Scans all links inside `.sl-markdown-content` and prepends a destination-specific icon to recognised link targets. Pass an object enabling each supported destination individually:
365
+ - `youtube` (default: `false`): prepends a YouTube play-button SVG (class `yt-icon`, `aria-hidden`, inherits `currentColor`) to links pointing to `youtube.com` or `youtu.be`.
364
366
 
365
367
  **Usage:**
366
368
 
@@ -378,6 +380,9 @@ export default defineConfig({
378
380
  limitDetailsElementHeight: true,
379
381
  offerToggleAllDetails: true,
380
382
  offerTabbedContent: true,
383
+ decorateExternalLinks: {
384
+ youtube: true,
385
+ },
381
386
  }),
382
387
  starlight({ title: "My Docs" }),
383
388
  ],
@@ -90,6 +90,7 @@ function starlightDomPatches(options = {}) {
90
90
  limitDetailsElementHeight: limitDetailsElementHeightOption = false,
91
91
  offerTabbedContent = false,
92
92
  offerToggleAllDetails = false,
93
+ decorateExternalLinks,
93
94
  wrapDetailsContent
94
95
  } = options;
95
96
  if (wrapDetailsContent !== void 0) {
@@ -132,10 +133,16 @@ function starlightDomPatches(options = {}) {
132
133
  syncTocLabelsFromHeadings ? "syncTocLabelsFromHeadings" : null,
133
134
  limitDetailsElementHeight ? "limitDetailsElementHeight" : null,
134
135
  offerTabbedContent ? "tabbedH2Content" : null,
135
- offerToggleAllDetails ? "toggleAllDetails" : null
136
+ offerToggleAllDetails ? "toggleAllDetails" : null,
137
+ decorateExternalLinks ? "decorateExternalLinks" : null
136
138
  ].filter(Boolean);
137
139
  if (imports.length === 0) return;
138
- const calls = imports.map((fn) => `${fn}();`).join("\n");
140
+ const calls = imports.map((fn) => {
141
+ if (fn === "decorateExternalLinks" && decorateExternalLinks) {
142
+ return `${fn}(${JSON.stringify(decorateExternalLinks)});`;
143
+ }
144
+ return `${fn}();`;
145
+ }).join("\n");
139
146
  injectScript(
140
147
  "page",
141
148
  `import { ${imports.join(", ")} } from ${scriptPath};
package/dist/index.js CHANGED
@@ -26,7 +26,7 @@ import {
26
26
  } from "./chunk-C2VXRQOK.js";
27
27
  import {
28
28
  starlightDomPatches
29
- } from "./chunk-PPNIMJPX.js";
29
+ } from "./chunk-SKQV57MG.js";
30
30
  import "./chunk-4VNS5WPM.js";
31
31
 
32
32
  // src/plugins/starlight-index-sourced-sidebar/index.ts
@@ -47,6 +47,13 @@ function createActionBar() {
47
47
  getRawMdUrl().then((url) => fetch(url)).then((resp) => resp.text()).then((text) => navigator.clipboard.writeText(text)).then(() => showBanner("Page source copied to clipboard!", "success")).catch(() => showBanner("Failed to copy page source.", "error")).finally(() => setLoading(false));
48
48
  }
49
49
  },
50
+ {
51
+ label: "Copy raw URL",
52
+ action: () => {
53
+ setLoading(true);
54
+ getRawMdUrl().then((url) => navigator.clipboard.writeText(url)).then(() => showBanner("Raw URL copied to clipboard!", "success")).catch(() => showBanner("Failed to copy raw URL.", "error")).finally(() => setLoading(false));
55
+ }
56
+ },
50
57
  {
51
58
  label: "View raw",
52
59
  action: () => {
@@ -2,6 +2,10 @@ declare function hideSingleLineGutters(): void;
2
2
  declare function syncTocLabelsFromHeadings(): void;
3
3
  declare function tabbedH2Content(): void;
4
4
  declare function toggleAllDetails(): void;
5
+ type DecorateExternalLinksOptions = {
6
+ youtube?: boolean;
7
+ };
8
+ declare function decorateExternalLinks(options: DecorateExternalLinksOptions): void;
5
9
  declare function limitDetailsElementHeight(): void;
6
10
 
7
- export { hideSingleLineGutters, limitDetailsElementHeight, syncTocLabelsFromHeadings, tabbedH2Content, toggleAllDetails };
11
+ export { decorateExternalLinks, hideSingleLineGutters, limitDetailsElementHeight, syncTocLabelsFromHeadings, tabbedH2Content, toggleAllDetails };
@@ -285,6 +285,53 @@ function toggleAllDetails() {
285
285
  }, 10);
286
286
  });
287
287
  }
288
+ function decorateExternalLinks(options) {
289
+ const MATCHERS = [
290
+ {
291
+ key: "youtube",
292
+ domains: ["youtube.com", "youtu.be", "www.youtube.com"],
293
+ iconClass: "yt-icon",
294
+ viewBox: "0 0 24 24",
295
+ d: "M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"
296
+ }
297
+ ];
298
+ const enabledMatchers = MATCHERS.filter((m) => options[m.key]);
299
+ function matchIcon(href) {
300
+ try {
301
+ const { hostname } = new URL(href);
302
+ return enabledMatchers.find((m) => m.domains.includes(hostname)) ?? null;
303
+ } catch {
304
+ return null;
305
+ }
306
+ }
307
+ document.querySelectorAll(".sl-markdown-content a[href]").forEach((anchor) => {
308
+ const href = anchor.getAttribute("href") ?? "";
309
+ const matcher = matchIcon(href);
310
+ if (!matcher) return;
311
+ if (anchor.querySelector(`span.not-content > .${matcher.iconClass}`))
312
+ return;
313
+ const wrapper = document.createElement("span");
314
+ wrapper.className = "not-content pe-1 align-middle";
315
+ const icon = document.createElementNS(
316
+ "http://www.w3.org/2000/svg",
317
+ "svg"
318
+ );
319
+ icon.setAttribute("class", matcher.iconClass);
320
+ icon.setAttribute("viewBox", matcher.viewBox);
321
+ icon.setAttribute("aria-hidden", "true");
322
+ icon.setAttribute("width", "1em");
323
+ icon.setAttribute("height", "1em");
324
+ const path = document.createElementNS(
325
+ "http://www.w3.org/2000/svg",
326
+ "path"
327
+ );
328
+ path.setAttribute("d", matcher.d);
329
+ path.setAttribute("fill", "currentColor");
330
+ icon.appendChild(path);
331
+ wrapper.appendChild(icon);
332
+ anchor.prepend(wrapper);
333
+ });
334
+ }
288
335
  function limitDetailsElementHeight() {
289
336
  const detailsElements = document.querySelectorAll(".main-pane details");
290
337
  detailsElements.forEach((details) => {
@@ -313,6 +360,7 @@ function limitDetailsElementHeight() {
313
360
  });
314
361
  }
315
362
  export {
363
+ decorateExternalLinks,
316
364
  hideSingleLineGutters,
317
365
  limitDetailsElementHeight,
318
366
  syncTocLabelsFromHeadings,
@@ -20,6 +20,14 @@ interface DomPatchesOptions {
20
20
  * @default false
21
21
  */
22
22
  offerToggleAllDetails?: boolean;
23
+ /**
24
+ * Prepend icons to links based on their destination.
25
+ * Each key enables a specific icon; omitting a key or setting it to `false` disables it.
26
+ */
27
+ decorateExternalLinks?: {
28
+ /** Prepend a YouTube play-button icon to links pointing to youtube.com or youtu.be. */
29
+ youtube?: boolean;
30
+ };
23
31
  /**
24
32
  * @deprecated Use `limitDetailsElementHeight` instead.
25
33
  */
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  starlightDomPatches
3
- } from "../chunk-PPNIMJPX.js";
3
+ } from "../chunk-SKQV57MG.js";
4
4
  import "../chunk-4VNS5WPM.js";
5
5
  export {
6
6
  starlightDomPatches
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "starlight-cannoli-plugins",
3
3
  "type": "module",
4
- "version": "2.14.2",
4
+ "version": "2.16.0",
5
5
  "description": "Starlight plugins for automatic sidebar generation and link validation",
6
6
  "license": "ISC",
7
7
  "main": "./dist/index.js",