starlight-cannoli-plugins 2.16.0 → 2.17.2

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
@@ -231,6 +231,28 @@ The following attributes can be added to the opening fence:
231
231
  ```
232
232
  ````
233
233
 
234
+ ### Remark GFM Slug
235
+
236
+ A remark plugin that assigns heading IDs using the same GFM slug algorithm as VS Code and GitHub, before Astro's internal `rehypeHeadingIds` runs. Since `rehypeHeadingIds` skips headings that already have an `id`, this takes precedence and produces fragment anchors that match what VS Code's markdown link resolver expects.
237
+
238
+ This is especially useful when headings contain inline math or other syntax that Astro/Starlight would otherwise slugify from the rendered HTML output (producing long encoded IDs that differ from VS Code's raw-text slugification).
239
+
240
+ **Usage:**
241
+
242
+ ```ts
243
+ // astro.config.mjs
244
+ import { defineConfig } from "astro/config";
245
+ import { remarkGfmSlug } from "starlight-cannoli-plugins";
246
+
247
+ export default defineConfig({
248
+ markdown: {
249
+ remarkPlugins: [
250
+ remarkGfmSlug, // must come before remark-math or other transforming plugins
251
+ ],
252
+ },
253
+ });
254
+ ```
255
+
234
256
  ### Rehype Validate Links
235
257
 
236
258
  A rehype plugin that validates all internal links in your Markdown/MDX files at build time. Links without matching files will cause the build to fail.
@@ -342,11 +364,15 @@ export default defineConfig({
342
364
 
343
365
  - `preserveDirs` (required): Names of child directories inside `public/` to preserve during sync. These will not be deleted when re-syncing.
344
366
  - `ignorePatterns` (optional): Glob patterns for files to exclude from syncing. Patterns are matched against paths relative to `src/content/docs/`.
367
+ - `exposePageSrcButton` (optional, default: `false`): Injects a client-side script on every page that adds a `⋮` dropdown next to the page's `<h1>` with "Copy Page", "Copy raw URL", and "View raw" actions for the page's raw markdown source.
368
+ - `enableAskAiOption` (optional, default: `false`): Adds "Ask ChatGPT", "Ask Claude", and "Ask Gemini" entries to the same `⋮` dropdown, each opening the respective chat app in a new tab with a prompt pre-filled to read the page's raw markdown URL. Independent of `exposePageSrcButton` — enabling this on its own still injects the dropdown button, containing only the "Ask" entries.
345
369
 
346
370
  ```ts
347
371
  syncDocsToPublic({
348
372
  preserveDirs: ["static"],
349
373
  ignorePatterns: ["**/*.txt", "**/drafts/**"],
374
+ exposePageSrcButton: true,
375
+ enableAskAiOption: true,
350
376
  });
351
377
  ```
352
378
 
@@ -360,7 +386,7 @@ An Astro integration that injects a client-side script to apply opt-in DOM patch
360
386
  - `syncTocLabelsFromHeadings` (optional, default: `false`): Copies the rendered HTML of each heading into its matching Starlight TOC anchor label, so the TOC properly reflects any custom markup (e.g. math) present in the heading.
361
387
  - `limitDetailsElementHeight` (optional, default: `false`): Limits the height of expanded `<details>` elements and makes their content scrollable.
362
388
  - `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
- - `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.
389
+ - `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 defaults to off on every page load and 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
390
  - `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
391
  - `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`.
366
392
 
@@ -13502,8 +13502,58 @@ import { visit } from "unist-util-visit";
13502
13502
  import path from "path";
13503
13503
  import { pathToFileURL } from "url";
13504
13504
 
13505
+ // node_modules/github-slugger/regex.js
13506
+ var regex = /[\0-\x1F!-,\.\/:-@\[-\^`\{-\xA9\xAB-\xB4\xB6-\xB9\xBB-\xBF\xD7\xF7\u02C2-\u02C5\u02D2-\u02DF\u02E5-\u02EB\u02ED\u02EF-\u02FF\u0375\u0378\u0379\u037E\u0380-\u0385\u0387\u038B\u038D\u03A2\u03F6\u0482\u0530\u0557\u0558\u055A-\u055F\u0589-\u0590\u05BE\u05C0\u05C3\u05C6\u05C8-\u05CF\u05EB-\u05EE\u05F3-\u060F\u061B-\u061F\u066A-\u066D\u06D4\u06DD\u06DE\u06E9\u06FD\u06FE\u0700-\u070F\u074B\u074C\u07B2-\u07BF\u07F6-\u07F9\u07FB\u07FC\u07FE\u07FF\u082E-\u083F\u085C-\u085F\u086B-\u089F\u08B5\u08C8-\u08D2\u08E2\u0964\u0965\u0970\u0984\u098D\u098E\u0991\u0992\u09A9\u09B1\u09B3-\u09B5\u09BA\u09BB\u09C5\u09C6\u09C9\u09CA\u09CF-\u09D6\u09D8-\u09DB\u09DE\u09E4\u09E5\u09F2-\u09FB\u09FD\u09FF\u0A00\u0A04\u0A0B-\u0A0E\u0A11\u0A12\u0A29\u0A31\u0A34\u0A37\u0A3A\u0A3B\u0A3D\u0A43-\u0A46\u0A49\u0A4A\u0A4E-\u0A50\u0A52-\u0A58\u0A5D\u0A5F-\u0A65\u0A76-\u0A80\u0A84\u0A8E\u0A92\u0AA9\u0AB1\u0AB4\u0ABA\u0ABB\u0AC6\u0ACA\u0ACE\u0ACF\u0AD1-\u0ADF\u0AE4\u0AE5\u0AF0-\u0AF8\u0B00\u0B04\u0B0D\u0B0E\u0B11\u0B12\u0B29\u0B31\u0B34\u0B3A\u0B3B\u0B45\u0B46\u0B49\u0B4A\u0B4E-\u0B54\u0B58-\u0B5B\u0B5E\u0B64\u0B65\u0B70\u0B72-\u0B81\u0B84\u0B8B-\u0B8D\u0B91\u0B96-\u0B98\u0B9B\u0B9D\u0BA0-\u0BA2\u0BA5-\u0BA7\u0BAB-\u0BAD\u0BBA-\u0BBD\u0BC3-\u0BC5\u0BC9\u0BCE\u0BCF\u0BD1-\u0BD6\u0BD8-\u0BE5\u0BF0-\u0BFF\u0C0D\u0C11\u0C29\u0C3A-\u0C3C\u0C45\u0C49\u0C4E-\u0C54\u0C57\u0C5B-\u0C5F\u0C64\u0C65\u0C70-\u0C7F\u0C84\u0C8D\u0C91\u0CA9\u0CB4\u0CBA\u0CBB\u0CC5\u0CC9\u0CCE-\u0CD4\u0CD7-\u0CDD\u0CDF\u0CE4\u0CE5\u0CF0\u0CF3-\u0CFF\u0D0D\u0D11\u0D45\u0D49\u0D4F-\u0D53\u0D58-\u0D5E\u0D64\u0D65\u0D70-\u0D79\u0D80\u0D84\u0D97-\u0D99\u0DB2\u0DBC\u0DBE\u0DBF\u0DC7-\u0DC9\u0DCB-\u0DCE\u0DD5\u0DD7\u0DE0-\u0DE5\u0DF0\u0DF1\u0DF4-\u0E00\u0E3B-\u0E3F\u0E4F\u0E5A-\u0E80\u0E83\u0E85\u0E8B\u0EA4\u0EA6\u0EBE\u0EBF\u0EC5\u0EC7\u0ECE\u0ECF\u0EDA\u0EDB\u0EE0-\u0EFF\u0F01-\u0F17\u0F1A-\u0F1F\u0F2A-\u0F34\u0F36\u0F38\u0F3A-\u0F3D\u0F48\u0F6D-\u0F70\u0F85\u0F98\u0FBD-\u0FC5\u0FC7-\u0FFF\u104A-\u104F\u109E\u109F\u10C6\u10C8-\u10CC\u10CE\u10CF\u10FB\u1249\u124E\u124F\u1257\u1259\u125E\u125F\u1289\u128E\u128F\u12B1\u12B6\u12B7\u12BF\u12C1\u12C6\u12C7\u12D7\u1311\u1316\u1317\u135B\u135C\u1360-\u137F\u1390-\u139F\u13F6\u13F7\u13FE-\u1400\u166D\u166E\u1680\u169B-\u169F\u16EB-\u16ED\u16F9-\u16FF\u170D\u1715-\u171F\u1735-\u173F\u1754-\u175F\u176D\u1771\u1774-\u177F\u17D4-\u17D6\u17D8-\u17DB\u17DE\u17DF\u17EA-\u180A\u180E\u180F\u181A-\u181F\u1879-\u187F\u18AB-\u18AF\u18F6-\u18FF\u191F\u192C-\u192F\u193C-\u1945\u196E\u196F\u1975-\u197F\u19AC-\u19AF\u19CA-\u19CF\u19DA-\u19FF\u1A1C-\u1A1F\u1A5F\u1A7D\u1A7E\u1A8A-\u1A8F\u1A9A-\u1AA6\u1AA8-\u1AAF\u1AC1-\u1AFF\u1B4C-\u1B4F\u1B5A-\u1B6A\u1B74-\u1B7F\u1BF4-\u1BFF\u1C38-\u1C3F\u1C4A-\u1C4C\u1C7E\u1C7F\u1C89-\u1C8F\u1CBB\u1CBC\u1CC0-\u1CCF\u1CD3\u1CFB-\u1CFF\u1DFA\u1F16\u1F17\u1F1E\u1F1F\u1F46\u1F47\u1F4E\u1F4F\u1F58\u1F5A\u1F5C\u1F5E\u1F7E\u1F7F\u1FB5\u1FBD\u1FBF-\u1FC1\u1FC5\u1FCD-\u1FCF\u1FD4\u1FD5\u1FDC-\u1FDF\u1FED-\u1FF1\u1FF5\u1FFD-\u203E\u2041-\u2053\u2055-\u2070\u2072-\u207E\u2080-\u208F\u209D-\u20CF\u20F1-\u2101\u2103-\u2106\u2108\u2109\u2114\u2116-\u2118\u211E-\u2123\u2125\u2127\u2129\u212E\u213A\u213B\u2140-\u2144\u214A-\u214D\u214F-\u215F\u2189-\u24B5\u24EA-\u2BFF\u2C2F\u2C5F\u2CE5-\u2CEA\u2CF4-\u2CFF\u2D26\u2D28-\u2D2C\u2D2E\u2D2F\u2D68-\u2D6E\u2D70-\u2D7E\u2D97-\u2D9F\u2DA7\u2DAF\u2DB7\u2DBF\u2DC7\u2DCF\u2DD7\u2DDF\u2E00-\u2E2E\u2E30-\u3004\u3008-\u3020\u3030\u3036\u3037\u303D-\u3040\u3097\u3098\u309B\u309C\u30A0\u30FB\u3100-\u3104\u3130\u318F-\u319F\u31C0-\u31EF\u3200-\u33FF\u4DC0-\u4DFF\u9FFD-\u9FFF\uA48D-\uA4CF\uA4FE\uA4FF\uA60D-\uA60F\uA62C-\uA63F\uA673\uA67E\uA6F2-\uA716\uA720\uA721\uA789\uA78A\uA7C0\uA7C1\uA7CB-\uA7F4\uA828-\uA82B\uA82D-\uA83F\uA874-\uA87F\uA8C6-\uA8CF\uA8DA-\uA8DF\uA8F8-\uA8FA\uA8FC\uA92E\uA92F\uA954-\uA95F\uA97D-\uA97F\uA9C1-\uA9CE\uA9DA-\uA9DF\uA9FF\uAA37-\uAA3F\uAA4E\uAA4F\uAA5A-\uAA5F\uAA77-\uAA79\uAAC3-\uAADA\uAADE\uAADF\uAAF0\uAAF1\uAAF7-\uAB00\uAB07\uAB08\uAB0F\uAB10\uAB17-\uAB1F\uAB27\uAB2F\uAB5B\uAB6A-\uAB6F\uABEB\uABEE\uABEF\uABFA-\uABFF\uD7A4-\uD7AF\uD7C7-\uD7CA\uD7FC-\uD7FF\uE000-\uF8FF\uFA6E\uFA6F\uFADA-\uFAFF\uFB07-\uFB12\uFB18-\uFB1C\uFB29\uFB37\uFB3D\uFB3F\uFB42\uFB45\uFBB2-\uFBD2\uFD3E-\uFD4F\uFD90\uFD91\uFDC8-\uFDEF\uFDFC-\uFDFF\uFE10-\uFE1F\uFE30-\uFE32\uFE35-\uFE4C\uFE50-\uFE6F\uFE75\uFEFD-\uFF0F\uFF1A-\uFF20\uFF3B-\uFF3E\uFF40\uFF5B-\uFF65\uFFBF-\uFFC1\uFFC8\uFFC9\uFFD0\uFFD1\uFFD8\uFFD9\uFFDD-\uFFFF]|\uD800[\uDC0C\uDC27\uDC3B\uDC3E\uDC4E\uDC4F\uDC5E-\uDC7F\uDCFB-\uDD3F\uDD75-\uDDFC\uDDFE-\uDE7F\uDE9D-\uDE9F\uDED1-\uDEDF\uDEE1-\uDEFF\uDF20-\uDF2C\uDF4B-\uDF4F\uDF7B-\uDF7F\uDF9E\uDF9F\uDFC4-\uDFC7\uDFD0\uDFD6-\uDFFF]|\uD801[\uDC9E\uDC9F\uDCAA-\uDCAF\uDCD4-\uDCD7\uDCFC-\uDCFF\uDD28-\uDD2F\uDD64-\uDDFF\uDF37-\uDF3F\uDF56-\uDF5F\uDF68-\uDFFF]|\uD802[\uDC06\uDC07\uDC09\uDC36\uDC39-\uDC3B\uDC3D\uDC3E\uDC56-\uDC5F\uDC77-\uDC7F\uDC9F-\uDCDF\uDCF3\uDCF6-\uDCFF\uDD16-\uDD1F\uDD3A-\uDD7F\uDDB8-\uDDBD\uDDC0-\uDDFF\uDE04\uDE07-\uDE0B\uDE14\uDE18\uDE36\uDE37\uDE3B-\uDE3E\uDE40-\uDE5F\uDE7D-\uDE7F\uDE9D-\uDEBF\uDEC8\uDEE7-\uDEFF\uDF36-\uDF3F\uDF56-\uDF5F\uDF73-\uDF7F\uDF92-\uDFFF]|\uD803[\uDC49-\uDC7F\uDCB3-\uDCBF\uDCF3-\uDCFF\uDD28-\uDD2F\uDD3A-\uDE7F\uDEAA\uDEAD-\uDEAF\uDEB2-\uDEFF\uDF1D-\uDF26\uDF28-\uDF2F\uDF51-\uDFAF\uDFC5-\uDFDF\uDFF7-\uDFFF]|\uD804[\uDC47-\uDC65\uDC70-\uDC7E\uDCBB-\uDCCF\uDCE9-\uDCEF\uDCFA-\uDCFF\uDD35\uDD40-\uDD43\uDD48-\uDD4F\uDD74\uDD75\uDD77-\uDD7F\uDDC5-\uDDC8\uDDCD\uDDDB\uDDDD-\uDDFF\uDE12\uDE38-\uDE3D\uDE3F-\uDE7F\uDE87\uDE89\uDE8E\uDE9E\uDEA9-\uDEAF\uDEEB-\uDEEF\uDEFA-\uDEFF\uDF04\uDF0D\uDF0E\uDF11\uDF12\uDF29\uDF31\uDF34\uDF3A\uDF45\uDF46\uDF49\uDF4A\uDF4E\uDF4F\uDF51-\uDF56\uDF58-\uDF5C\uDF64\uDF65\uDF6D-\uDF6F\uDF75-\uDFFF]|\uD805[\uDC4B-\uDC4F\uDC5A-\uDC5D\uDC62-\uDC7F\uDCC6\uDCC8-\uDCCF\uDCDA-\uDD7F\uDDB6\uDDB7\uDDC1-\uDDD7\uDDDE-\uDDFF\uDE41-\uDE43\uDE45-\uDE4F\uDE5A-\uDE7F\uDEB9-\uDEBF\uDECA-\uDEFF\uDF1B\uDF1C\uDF2C-\uDF2F\uDF3A-\uDFFF]|\uD806[\uDC3B-\uDC9F\uDCEA-\uDCFE\uDD07\uDD08\uDD0A\uDD0B\uDD14\uDD17\uDD36\uDD39\uDD3A\uDD44-\uDD4F\uDD5A-\uDD9F\uDDA8\uDDA9\uDDD8\uDDD9\uDDE2\uDDE5-\uDDFF\uDE3F-\uDE46\uDE48-\uDE4F\uDE9A-\uDE9C\uDE9E-\uDEBF\uDEF9-\uDFFF]|\uD807[\uDC09\uDC37\uDC41-\uDC4F\uDC5A-\uDC71\uDC90\uDC91\uDCA8\uDCB7-\uDCFF\uDD07\uDD0A\uDD37-\uDD39\uDD3B\uDD3E\uDD48-\uDD4F\uDD5A-\uDD5F\uDD66\uDD69\uDD8F\uDD92\uDD99-\uDD9F\uDDAA-\uDEDF\uDEF7-\uDFAF\uDFB1-\uDFFF]|\uD808[\uDF9A-\uDFFF]|\uD809[\uDC6F-\uDC7F\uDD44-\uDFFF]|[\uD80A\uD80B\uD80E-\uD810\uD812-\uD819\uD824-\uD82B\uD82D\uD82E\uD830-\uD833\uD837\uD839\uD83D\uD83F\uD87B-\uD87D\uD87F\uD885-\uDB3F\uDB41-\uDBFF][\uDC00-\uDFFF]|\uD80D[\uDC2F-\uDFFF]|\uD811[\uDE47-\uDFFF]|\uD81A[\uDE39-\uDE3F\uDE5F\uDE6A-\uDECF\uDEEE\uDEEF\uDEF5-\uDEFF\uDF37-\uDF3F\uDF44-\uDF4F\uDF5A-\uDF62\uDF78-\uDF7C\uDF90-\uDFFF]|\uD81B[\uDC00-\uDE3F\uDE80-\uDEFF\uDF4B-\uDF4E\uDF88-\uDF8E\uDFA0-\uDFDF\uDFE2\uDFE5-\uDFEF\uDFF2-\uDFFF]|\uD821[\uDFF8-\uDFFF]|\uD823[\uDCD6-\uDCFF\uDD09-\uDFFF]|\uD82C[\uDD1F-\uDD4F\uDD53-\uDD63\uDD68-\uDD6F\uDEFC-\uDFFF]|\uD82F[\uDC6B-\uDC6F\uDC7D-\uDC7F\uDC89-\uDC8F\uDC9A-\uDC9C\uDC9F-\uDFFF]|\uD834[\uDC00-\uDD64\uDD6A-\uDD6C\uDD73-\uDD7A\uDD83\uDD84\uDD8C-\uDDA9\uDDAE-\uDE41\uDE45-\uDFFF]|\uD835[\uDC55\uDC9D\uDCA0\uDCA1\uDCA3\uDCA4\uDCA7\uDCA8\uDCAD\uDCBA\uDCBC\uDCC4\uDD06\uDD0B\uDD0C\uDD15\uDD1D\uDD3A\uDD3F\uDD45\uDD47-\uDD49\uDD51\uDEA6\uDEA7\uDEC1\uDEDB\uDEFB\uDF15\uDF35\uDF4F\uDF6F\uDF89\uDFA9\uDFC3\uDFCC\uDFCD]|\uD836[\uDC00-\uDDFF\uDE37-\uDE3A\uDE6D-\uDE74\uDE76-\uDE83\uDE85-\uDE9A\uDEA0\uDEB0-\uDFFF]|\uD838[\uDC07\uDC19\uDC1A\uDC22\uDC25\uDC2B-\uDCFF\uDD2D-\uDD2F\uDD3E\uDD3F\uDD4A-\uDD4D\uDD4F-\uDEBF\uDEFA-\uDFFF]|\uD83A[\uDCC5-\uDCCF\uDCD7-\uDCFF\uDD4C-\uDD4F\uDD5A-\uDFFF]|\uD83B[\uDC00-\uDDFF\uDE04\uDE20\uDE23\uDE25\uDE26\uDE28\uDE33\uDE38\uDE3A\uDE3C-\uDE41\uDE43-\uDE46\uDE48\uDE4A\uDE4C\uDE50\uDE53\uDE55\uDE56\uDE58\uDE5A\uDE5C\uDE5E\uDE60\uDE63\uDE65\uDE66\uDE6B\uDE73\uDE78\uDE7D\uDE7F\uDE8A\uDE9C-\uDEA0\uDEA4\uDEAA\uDEBC-\uDFFF]|\uD83C[\uDC00-\uDD2F\uDD4A-\uDD4F\uDD6A-\uDD6F\uDD8A-\uDFFF]|\uD83E[\uDC00-\uDFEF\uDFFA-\uDFFF]|\uD869[\uDEDE-\uDEFF]|\uD86D[\uDF35-\uDF3F]|\uD86E[\uDC1E\uDC1F]|\uD873[\uDEA2-\uDEAF]|\uD87A[\uDFE1-\uDFFF]|\uD87E[\uDE1E-\uDFFF]|\uD884[\uDF4B-\uDFFF]|\uDB40[\uDC00-\uDCFF\uDDF0-\uDFFF]/g;
13507
+
13505
13508
  // node_modules/github-slugger/index.js
13506
13509
  var own4 = Object.hasOwnProperty;
13510
+ var BananaSlug = class {
13511
+ /**
13512
+ * Create a new slug class.
13513
+ */
13514
+ constructor() {
13515
+ this.occurrences;
13516
+ this.reset();
13517
+ }
13518
+ /**
13519
+ * Generate a unique slug.
13520
+ *
13521
+ * Tracks previously generated slugs: repeated calls with the same value
13522
+ * will result in different slugs.
13523
+ * Use the `slug` function to get same slugs.
13524
+ *
13525
+ * @param {string} value
13526
+ * String of text to slugify
13527
+ * @param {boolean} [maintainCase=false]
13528
+ * Keep the current case, otherwise make all lowercase
13529
+ * @return {string}
13530
+ * A unique slug string
13531
+ */
13532
+ slug(value, maintainCase) {
13533
+ const self2 = this;
13534
+ let result = slug(value, maintainCase === true);
13535
+ const originalSlug = result;
13536
+ while (own4.call(self2.occurrences, result)) {
13537
+ self2.occurrences[originalSlug]++;
13538
+ result = originalSlug + "-" + self2.occurrences[originalSlug];
13539
+ }
13540
+ self2.occurrences[result] = 0;
13541
+ return result;
13542
+ }
13543
+ /**
13544
+ * Reset - Forget all previous slugs
13545
+ *
13546
+ * @return void
13547
+ */
13548
+ reset() {
13549
+ this.occurrences = /* @__PURE__ */ Object.create(null);
13550
+ }
13551
+ };
13552
+ function slug(value, maintainCase) {
13553
+ if (typeof value !== "string") return "";
13554
+ if (!maintainCase) value = value.toLowerCase();
13555
+ return value.replace(regex, "").replace(/ /g, "-");
13556
+ }
13507
13557
 
13508
13558
  // node_modules/@astrojs/markdown-remark/dist/rehype-collect-headings.js
13509
13559
  import { visit as visit2 } from "unist-util-visit";
@@ -13522,8 +13572,10 @@ import { visit as visit4 } from "unist-util-visit";
13522
13572
  var isPerformanceBenchmark = Boolean(process.env.ASTRO_PERFORMANCE_BENCHMARK);
13523
13573
 
13524
13574
  export {
13575
+ toString2 as toString,
13525
13576
  remarkParse,
13526
13577
  unified,
13578
+ BananaSlug,
13527
13579
  parseFrontmatter
13528
13580
  };
13529
13581
  /*! Bundled license information:
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseFrontmatter
3
- } from "./chunk-WFN7OFMJ.js";
3
+ } from "./chunk-EEPQVOZC.js";
4
4
 
5
5
  // src/plugins/astro-sync-docs-to-public/index.ts
6
6
  import { fileURLToPath } from "url";
@@ -138,17 +138,18 @@ var DEFAULT_SRC_DIR = "src/content/docs";
138
138
  var DEFAULT_PUBLIC_DIR = "public";
139
139
  function validateOptions(options) {
140
140
  const mdProbePaths = ["test.md", "test.mdx", "foo/test.md", "foo/test.mdx"];
141
- if (!options.exposePageSrcButton) return;
141
+ const needsRawMd = options.exposePageSrcButton || options.enableAskAiOption;
142
+ if (!needsRawMd) return;
142
143
  const offendingMdPatterns = (options.ignorePatterns ?? []).filter(
143
144
  (pattern) => mdProbePaths.some((probe) => minimatch(probe, pattern, { dot: true }))
144
145
  );
145
146
  const offendingMtPatternsStr = offendingMdPatterns.map((p) => ` - "${p}"`).join("\n");
146
147
  if (offendingMdPatterns.length > 0) {
147
148
  throw new Error(
148
- `The 'exposePageSrcButton' option is enabled but the following 'ignorePatterns' values conflict with it:
149
+ `The 'exposePageSrcButton'/'enableAskAiOption' option is enabled but the following 'ignorePatterns' values conflict with it:
149
150
  ${offendingMtPatternsStr}
150
151
 
151
- Since these ignore patterns match md/mdx files, the page source button would break. Either disable 'exposePageSrcButton' or remove these patterns.`
152
+ Since these ignore patterns match md/mdx files, the page action button would break. Either disable these options or remove these patterns.`
152
153
  );
153
154
  }
154
155
  }
@@ -256,7 +257,8 @@ function syncDocsToPublic(options) {
256
257
  const {
257
258
  preserveDirs: rawPreserveDirs,
258
259
  ignorePatterns = [],
259
- exposePageSrcButton = false
260
+ exposePageSrcButton = false,
261
+ enableAskAiOption = false
260
262
  } = options;
261
263
  const preserveDirs = rawPreserveDirs.map((d) => d.replace(/\/+$/, ""));
262
264
  return {
@@ -264,7 +266,7 @@ function syncDocsToPublic(options) {
264
266
  hooks: {
265
267
  "astro:config:setup": ({ injectScript }) => {
266
268
  validateOptions(options);
267
- if (exposePageSrcButton) {
269
+ if (exposePageSrcButton || enableAskAiOption) {
268
270
  injectScript(
269
271
  "page",
270
272
  `
@@ -288,8 +290,16 @@ function syncDocsToPublic(options) {
288
290
  import.meta.url
289
291
  );
290
292
  }
291
- const scriptPath = fileURLToPath(pageScriptUrl);
292
- injectScript("page", `import ${JSON.stringify(scriptPath)};`);
293
+ const scriptPath = JSON.stringify(fileURLToPath(pageScriptUrl));
294
+ const actionBarOptions = JSON.stringify({
295
+ exposePageSrcButton,
296
+ enableAskAiOption
297
+ });
298
+ injectScript(
299
+ "page",
300
+ `import { initPageActionBar } from ${scriptPath};
301
+ initPageActionBar(${actionBarOptions});`
302
+ );
293
303
  }
294
304
  },
295
305
  "astro:build:start": async () => {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseFrontmatter
3
- } from "./chunk-C2VXRQOK.js";
3
+ } from "./chunk-7D2KLZGE.js";
4
4
 
5
5
  // src/plugins/utils/workspace-utils.ts
6
6
  import * as fs from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseFrontmatter
3
- } from "./chunk-C2VXRQOK.js";
3
+ } from "./chunk-7D2KLZGE.js";
4
4
 
5
5
  // src/plugins/astro-latex-compile/index.ts
6
6
  import { readFileSync as readFileSync2 } from "fs";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  parseFrontmatter
3
- } from "./chunk-WFN7OFMJ.js";
3
+ } from "./chunk-EEPQVOZC.js";
4
4
 
5
5
  // src/plugins/starlight-index-only-sidebar/index.ts
6
6
  import { join as join2 } from "path";
package/dist/index.d.ts CHANGED
@@ -3,6 +3,8 @@ import { HookParameters } from '@astrojs/starlight/types';
3
3
  export { DomPatchesOptions, starlightDomPatches } from './plugins/starlight-dom-patches.js';
4
4
  export { SyncDocsToPublicOptions, syncDocsToPublic } from './plugins/astro-sync-docs-to-public.js';
5
5
  export { rehypeValidateLinks } from './plugins/rehype-validate-links.js';
6
+ import { Root } from 'mdast';
7
+ import { Plugin } from 'unified';
6
8
  export { astroNormalizePaths } from './plugins/astro-normalize-paths.js';
7
9
  export { expressiveCodeEmphasis } from './plugins/expressive-code-emphasis.js';
8
10
  import { AstroIntegration } from 'astro';
@@ -10,7 +12,6 @@ import { LatexCompilePluginOptions } from './plugins/astro-latex-compile.js';
10
12
  import 'hast';
11
13
  import 'vfile';
12
14
  import 'expressive-code';
13
- import 'mdast';
14
15
 
15
16
  type TBadgeVariant = "note" | "danger" | "success" | "caution" | "tip" | "default";
16
17
  type TIndexMarker = {
@@ -66,6 +67,14 @@ declare function starlightIndexSourcedSidebar(options: TOptions): {
66
67
  };
67
68
  };
68
69
 
70
+ /**
71
+ * Remark plugin that assigns heading IDs matching VS Code's markdown link
72
+ * resolver: GFM slugification of the raw heading text, with math nodes stripped.
73
+ * Runs before Astro's rehypeHeadingIds, which skips headings that already
74
+ * have an id, so this takes precedence.
75
+ */
76
+ declare const remarkGfmSlug: Plugin<[], Root>;
77
+
69
78
  interface LatexCompileOptions extends LatexCompilePluginOptions {
70
79
  /**
71
80
  * When `true`, SVG files in `svgOutputDir` that are no longer referenced by
@@ -78,4 +87,4 @@ interface LatexCompileOptions extends LatexCompilePluginOptions {
78
87
  }
79
88
  declare function astroLatexCompile(options: LatexCompileOptions): AstroIntegration;
80
89
 
81
- export { type LatexCompileOptions, astroLatexCompile, starlightIndexSourcedSidebar };
90
+ export { type LatexCompileOptions, astroLatexCompile, remarkGfmSlug, starlightIndexSourcedSidebar };
package/dist/index.js CHANGED
@@ -3,27 +3,29 @@ import {
3
3
  } from "./chunk-O2TZASB7.js";
4
4
  import {
5
5
  starlightIndexOnlySidebar
6
- } from "./chunk-JFHWCFHF.js";
6
+ } from "./chunk-GTVQDNAE.js";
7
7
  import {
8
8
  rehypeValidateLinks
9
9
  } from "./chunk-3UMY7T6G.js";
10
10
  import {
11
11
  latexCompileTransformer
12
- } from "./chunk-RAKGKJJO.js";
12
+ } from "./chunk-FDEHNCBK.js";
13
13
  import {
14
14
  astroNormalizePaths
15
15
  } from "./chunk-TLOFSB33.js";
16
16
  import "./chunk-LJWSZSPB.js";
17
17
  import {
18
18
  syncDocsToPublic
19
- } from "./chunk-QPHWDN2I.js";
19
+ } from "./chunk-BM7LMGAH.js";
20
20
  import {
21
21
  parseFrontmatter
22
- } from "./chunk-WFN7OFMJ.js";
22
+ } from "./chunk-EEPQVOZC.js";
23
23
  import {
24
+ BananaSlug,
24
25
  remarkParse,
26
+ toString,
25
27
  unified
26
- } from "./chunk-C2VXRQOK.js";
28
+ } from "./chunk-7D2KLZGE.js";
27
29
  import {
28
30
  starlightDomPatches
29
31
  } from "./chunk-SKQV57MG.js";
@@ -228,6 +230,32 @@ function starlightIndexSourcedSidebar(options) {
228
230
  };
229
231
  }
230
232
 
233
+ // src/plugins/remark-gfm-slug.ts
234
+ import { visit as visit2 } from "unist-util-visit";
235
+ var STRIP_NODE_TYPES = /* @__PURE__ */ new Set(["math", "inlineMath"]);
236
+ function headingText(node) {
237
+ const parts = [];
238
+ for (const child of node.children) {
239
+ if (STRIP_NODE_TYPES.has(child.type)) continue;
240
+ parts.push(toString(child, { includeImageAlt: false }));
241
+ }
242
+ return parts.join("").trim();
243
+ }
244
+ var remarkGfmSlug = () => {
245
+ return (tree) => {
246
+ const slugger = new BananaSlug();
247
+ visit2(tree, "heading", (node) => {
248
+ const text = headingText(node);
249
+ const id = slugger.slug(text);
250
+ const data = node.data ??= {};
251
+ data.id = id;
252
+ data.hProperties ??= {};
253
+ data.hProperties.id = id;
254
+ });
255
+ };
256
+ };
257
+ var remark_gfm_slug_default = remarkGfmSlug;
258
+
231
259
  // src/plugins/astro-latex-compile/astro-integration.ts
232
260
  import fs2 from "fs";
233
261
  import { readdir, rm } from "fs/promises";
@@ -328,6 +356,7 @@ export {
328
356
  astroNormalizePaths,
329
357
  expressiveCodeEmphasis,
330
358
  rehypeValidateLinks,
359
+ remark_gfm_slug_default as remarkGfmSlug,
331
360
  starlightDomPatches,
332
361
  starlightIndexOnlySidebar,
333
362
  starlightIndexSourcedSidebar,
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  compileLatexToSvg,
3
3
  latexCompileTransformer
4
- } from "../chunk-RAKGKJJO.js";
5
- import "../chunk-C2VXRQOK.js";
4
+ } from "../chunk-FDEHNCBK.js";
5
+ import "../chunk-7D2KLZGE.js";
6
6
  import "../chunk-4VNS5WPM.js";
7
7
  export {
8
8
  compileLatexToSvg,
@@ -1,2 +1,7 @@
1
+ interface ActionBarOptions {
2
+ exposePageSrcButton: boolean;
3
+ enableAskAiOption: boolean;
4
+ }
5
+ declare function initPageActionBar(options: ActionBarOptions): void;
1
6
 
2
- export { }
7
+ export { initPageActionBar };
@@ -1,3 +1,5 @@
1
+ import "../../chunk-4VNS5WPM.js";
2
+
1
3
  // src/plugins/astro-sync-docs-to-public/page-script.ts
2
4
  function showBanner(message, variant) {
3
5
  const banner = document.createElement("div");
@@ -28,7 +30,21 @@ async function getRawMdUrl() {
28
30
  }
29
31
  return new URL("index.md", base).toString();
30
32
  }
31
- function createActionBar() {
33
+ var LLM_ASK_TARGETS = [
34
+ {
35
+ label: "Ask ChatGPT",
36
+ buildUrl: (prompt) => `https://chatgpt.com/?${new URLSearchParams({ q: prompt })}`
37
+ },
38
+ {
39
+ label: "Ask Claude",
40
+ buildUrl: (prompt) => `https://claude.ai/new?${new URLSearchParams({ q: prompt })}`
41
+ },
42
+ {
43
+ label: "Ask Gemini",
44
+ buildUrl: (prompt) => `https://gemini.google.com/app?${new URLSearchParams({ q: prompt })}`
45
+ }
46
+ ];
47
+ function createActionBar(options) {
32
48
  const bar = document.createElement("div");
33
49
  bar.className = "page-src-action-bar";
34
50
  const menuBtn = document.createElement("button");
@@ -39,29 +55,48 @@ function createActionBar() {
39
55
  }
40
56
  const menu = document.createElement("div");
41
57
  menu.className = "page-src-action-bar__menu";
42
- const menuItems = [
43
- {
44
- label: "Copy Page",
45
- action: () => {
46
- setLoading(true);
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
- }
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
- },
57
- {
58
- label: "View raw",
59
- action: () => {
60
- setLoading(true);
61
- getRawMdUrl().then((url) => window.open(url, "_blank")).catch(() => showBanner("Failed to open raw source.", "error")).finally(() => setLoading(false));
58
+ const menuItems = [];
59
+ if (options.exposePageSrcButton) {
60
+ menuItems.push(
61
+ {
62
+ label: "Copy Page",
63
+ action: () => {
64
+ setLoading(true);
65
+ getRawMdUrl().then((url) => fetch(url)).then((resp) => resp.text()).then((text) => navigator.clipboard.writeText(text)).then(
66
+ () => showBanner("Page source copied to clipboard!", "success")
67
+ ).catch(() => showBanner("Failed to copy page source.", "error")).finally(() => setLoading(false));
68
+ }
69
+ },
70
+ {
71
+ label: "Copy raw URL",
72
+ action: () => {
73
+ setLoading(true);
74
+ 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));
75
+ }
76
+ },
77
+ {
78
+ label: "View raw",
79
+ action: () => {
80
+ setLoading(true);
81
+ getRawMdUrl().then((url) => window.open(url, "_blank")).catch(() => showBanner("Failed to open raw source.", "error")).finally(() => setLoading(false));
82
+ }
62
83
  }
84
+ );
85
+ }
86
+ if (options.enableAskAiOption) {
87
+ for (const { label, buildUrl } of LLM_ASK_TARGETS) {
88
+ menuItems.push({
89
+ label,
90
+ action: () => {
91
+ setLoading(true);
92
+ getRawMdUrl().then((url) => {
93
+ const prompt = `Read from ${url} so I can ask questions about it.`;
94
+ window.open(buildUrl(prompt), "_blank");
95
+ }).catch(() => showBanner(`Failed to open ${label}.`, "error")).finally(() => setLoading(false));
96
+ }
97
+ });
63
98
  }
64
- ];
99
+ }
65
100
  for (const item of menuItems) {
66
101
  const btn = document.createElement("button");
67
102
  btn.className = "page-src-action-bar__menu-item";
@@ -93,13 +128,15 @@ function createActionBar() {
93
128
  bar.appendChild(menu);
94
129
  return bar;
95
130
  }
96
- function main() {
131
+ function initPageActionBar(options) {
97
132
  const h1 = document.querySelector(".sl-container > h1");
98
133
  if (h1 === null) return;
99
134
  const wrapper = document.createElement("div");
100
135
  wrapper.className = "page-src-h1-wrapper";
101
136
  h1.replaceWith(wrapper);
102
137
  wrapper.appendChild(h1);
103
- wrapper.appendChild(createActionBar());
138
+ wrapper.appendChild(createActionBar(options));
104
139
  }
105
- main();
140
+ export {
141
+ initPageActionBar
142
+ };
@@ -17,6 +17,15 @@ interface SyncDocsToPublicOptions {
17
17
  * dev and build modes.
18
18
  */
19
19
  exposePageSrcButton?: boolean;
20
+ /**
21
+ * When true, adds "Ask ChatGPT", "Ask Claude", and "Ask Gemini" entries to
22
+ * the page action dropdown menu, each opening the respective chat app with
23
+ * a prompt pre-filled to read the current page's raw markdown URL.
24
+ *
25
+ * Independent of `exposePageSrcButton` — enabling this on its own still
26
+ * injects the dropdown button, containing only the "Ask" entries.
27
+ */
28
+ enableAskAiOption?: boolean;
20
29
  }
21
30
  declare function syncDocsToPublic(options: SyncDocsToPublicOptions): AstroIntegration;
22
31
 
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  syncDocsToPublic
3
- } from "../chunk-QPHWDN2I.js";
4
- import "../chunk-WFN7OFMJ.js";
5
- import "../chunk-C2VXRQOK.js";
3
+ } from "../chunk-BM7LMGAH.js";
4
+ import "../chunk-EEPQVOZC.js";
5
+ import "../chunk-7D2KLZGE.js";
6
6
  import "../chunk-4VNS5WPM.js";
7
7
  export {
8
8
  syncDocsToPublic
@@ -50,7 +50,6 @@ function syncTocLabelsFromHeadings() {
50
50
  });
51
51
  }
52
52
  function tabbedH2Content() {
53
- const LS_KEY = "starlight-dom-patches:tabbed-content";
54
53
  const contentContainer = document.querySelector(
55
54
  ".main-pane .sl-markdown-content"
56
55
  );
@@ -241,16 +240,10 @@ function tabbedH2Content() {
241
240
  );
242
241
  viewToggleCheckbox2.addEventListener("change", () => {
243
242
  setEnabled(viewToggleCheckbox2.checked);
244
- localStorage.setItem(
245
- LS_KEY,
246
- viewToggleCheckbox2.checked ? "enabled" : "disabled"
247
- );
248
243
  });
249
244
  }
250
245
  const viewToggleCheckbox = buildToggle();
251
- const initialEnabled = localStorage.getItem(LS_KEY) === "enabled";
252
- viewToggleCheckbox.checked = initialEnabled;
253
- setEnabled(initialEnabled);
246
+ setEnabled(false);
254
247
  if (window.location.hash) navigateToHash(window.location.hash);
255
248
  wireInteractions(viewToggleCheckbox);
256
249
  }
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  starlightIndexOnlySidebar
3
- } from "../chunk-JFHWCFHF.js";
4
- import "../chunk-WFN7OFMJ.js";
5
- import "../chunk-C2VXRQOK.js";
3
+ } from "../chunk-GTVQDNAE.js";
4
+ import "../chunk-EEPQVOZC.js";
5
+ import "../chunk-7D2KLZGE.js";
6
6
  import "../chunk-4VNS5WPM.js";
7
7
  export {
8
8
  starlightIndexOnlySidebar
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "starlight-cannoli-plugins",
3
3
  "type": "module",
4
- "version": "2.16.0",
4
+ "version": "2.17.2",
5
5
  "description": "Starlight plugins for automatic sidebar generation and link validation",
6
6
  "license": "ISC",
7
7
  "main": "./dist/index.js",