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 +5 -0
- package/dist/{chunk-PPNIMJPX.js → chunk-SKQV57MG.js} +9 -2
- package/dist/index.js +1 -1
- package/dist/plugins/astro-sync-docs-to-public/page-script.js +7 -0
- package/dist/plugins/starlight-dom-patches/page-script.d.ts +5 -1
- package/dist/plugins/starlight-dom-patches/page-script.js +48 -0
- package/dist/plugins/starlight-dom-patches.d.ts +8 -0
- package/dist/plugins/starlight-dom-patches.js +1 -1
- package/package.json +1 -1
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) =>
|
|
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
|
@@ -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
|
*/
|
package/package.json
CHANGED