starlight-cannoli-plugins 2.17.1 → 2.18.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 -1
- package/dist/{chunk-4ZJKEEDA.js → chunk-BM7LMGAH.js} +17 -7
- package/dist/{chunk-FDEHNCBK.js → chunk-GVS36MVT.js} +76 -19
- package/dist/index.js +15 -3
- package/dist/plugins/astro-latex-compile.d.ts +11 -5
- package/dist/plugins/astro-latex-compile.js +1 -1
- package/dist/plugins/astro-sync-docs-to-public/page-script.d.ts +6 -1
- package/dist/plugins/astro-sync-docs-to-public/page-script.js +62 -25
- package/dist/plugins/astro-sync-docs-to-public.d.ts +9 -0
- package/dist/plugins/astro-sync-docs-to-public.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -144,7 +144,7 @@ dvisvgm --version
|
|
|
144
144
|
- `removeOrphanedSvgs` (optional, default: `false`): When `true`, SVG files that are no longer referenced by any `tex compile` block are deleted automatically. In dev mode, stale SVGs are removed immediately when a block is edited. On build, any remaining orphans are swept at the end.
|
|
145
145
|
- `svgClassname` (optional): Default CSS class(es) applied to every compiled SVG `<img>` element. Individual blocks can override this with a `class` meta tag value. The `tex-compiled` class is always present and cannot be overridden.
|
|
146
146
|
- `texInputDirs` (optional): Directories added to the TeX input search path (`TEXINPUTS`), allowing `\input{}` and `\include{}` to resolve files from your project. Use a trailing `/` to search only that directory, or `//` to search it recursively. Multiple directories are supported. When any file in these directories changes, all cached SVGs are invalidated and recompiled.
|
|
147
|
-
- `
|
|
147
|
+
- `debug` (optional): When set, a JPEG copy of each compiled diagram is written to `debug.outputDir` as a flat file list (no mirrored folder structure). Only blocks that carry a `blockid=<n>` meta tag produce a JPEG — blocks without it are ignored. The filename format is `<originating-file>--<blockid>-<8-char-dir-checksum>.jpg` (e.g. `tex-test--5-a1b2c3d4.jpg`), where the checksum is an MD5 hash of the originating file's directory, namespacing files so that same-named files from different directories don't collide. JPEGs are deleted automatically when their block is removed, its `blockid` changes, or its content changes, and the entire `debug.outputDir` is cleared on dev server startup. Every JPEG (re)write is also appended to `debug.logFile`, which rotates automatically once it grows too large (rolling backups `<logFile>-1.log`, `<logFile>-2.log`, etc.). SVG output is unaffected — the JPEG/log output is complementary and intended for local inspection.
|
|
148
148
|
|
|
149
149
|
```ts
|
|
150
150
|
astroLatexCompile({
|
|
@@ -364,11 +364,15 @@ export default defineConfig({
|
|
|
364
364
|
|
|
365
365
|
- `preserveDirs` (required): Names of child directories inside `public/` to preserve during sync. These will not be deleted when re-syncing.
|
|
366
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.
|
|
367
369
|
|
|
368
370
|
```ts
|
|
369
371
|
syncDocsToPublic({
|
|
370
372
|
preserveDirs: ["static"],
|
|
371
373
|
ignorePatterns: ["**/*.txt", "**/drafts/**"],
|
|
374
|
+
exposePageSrcButton: true,
|
|
375
|
+
enableAskAiOption: true,
|
|
372
376
|
});
|
|
373
377
|
```
|
|
374
378
|
|
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
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 () => {
|
|
@@ -9,18 +9,67 @@ import { join as join2, relative as relative2, resolve as resolve2 } from "path"
|
|
|
9
9
|
import { visit, SKIP } from "unist-util-visit";
|
|
10
10
|
import { MetaOptions } from "@expressive-code/core";
|
|
11
11
|
|
|
12
|
+
// src/plugins/utils/rotating-file-logger.ts
|
|
13
|
+
import { existsSync, mkdirSync, renameSync, rmSync, statSync } from "fs";
|
|
14
|
+
import { appendFile } from "fs/promises";
|
|
15
|
+
import { dirname, extname } from "path";
|
|
16
|
+
function rotatedPath(filePath, index) {
|
|
17
|
+
const ext = extname(filePath);
|
|
18
|
+
const base = filePath.slice(0, filePath.length - ext.length);
|
|
19
|
+
return `${base}-${index}${ext}`;
|
|
20
|
+
}
|
|
21
|
+
function formatTimestamp(date) {
|
|
22
|
+
const pad = (n, width = 2) => String(n).padStart(width, "0");
|
|
23
|
+
const datePart = `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())}`;
|
|
24
|
+
const timePart = `${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())},${pad(date.getMilliseconds(), 3)}`;
|
|
25
|
+
return `${datePart} ${timePart}`;
|
|
26
|
+
}
|
|
27
|
+
function rotate(options) {
|
|
28
|
+
const { filePath, backupCount } = options;
|
|
29
|
+
const oldestPath = rotatedPath(filePath, backupCount);
|
|
30
|
+
if (existsSync(oldestPath)) {
|
|
31
|
+
rmSync(oldestPath);
|
|
32
|
+
}
|
|
33
|
+
for (let index = backupCount - 1; index >= 1; index--) {
|
|
34
|
+
const source = rotatedPath(filePath, index);
|
|
35
|
+
if (existsSync(source)) {
|
|
36
|
+
renameSync(source, rotatedPath(filePath, index + 1));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (backupCount >= 1) {
|
|
40
|
+
renameSync(filePath, rotatedPath(filePath, 1));
|
|
41
|
+
} else {
|
|
42
|
+
rmSync(filePath);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
var RotatingFileLogger = class {
|
|
46
|
+
options;
|
|
47
|
+
constructor(options) {
|
|
48
|
+
this.options = options;
|
|
49
|
+
mkdirSync(dirname(options.filePath), { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
async write(message) {
|
|
52
|
+
const { filePath, maxSizeBytes, identifier } = this.options;
|
|
53
|
+
const line = `[${identifier}] ${formatTimestamp(/* @__PURE__ */ new Date())} ${message}${message.endsWith("\n") ? "" : "\n"}`;
|
|
54
|
+
if (existsSync(filePath) && statSync(filePath).size >= maxSizeBytes) {
|
|
55
|
+
rotate(this.options);
|
|
56
|
+
}
|
|
57
|
+
await appendFile(filePath, line);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
|
|
12
61
|
// src/plugins/astro-latex-compile/utils.ts
|
|
13
62
|
import { createHash } from "crypto";
|
|
14
63
|
import {
|
|
15
|
-
existsSync,
|
|
16
|
-
mkdirSync,
|
|
64
|
+
existsSync as existsSync2,
|
|
65
|
+
mkdirSync as mkdirSync2,
|
|
17
66
|
readdirSync,
|
|
18
67
|
readFileSync,
|
|
19
68
|
writeFileSync,
|
|
20
|
-
rmSync,
|
|
69
|
+
rmSync as rmSync2,
|
|
21
70
|
mkdtempSync
|
|
22
71
|
} from "fs";
|
|
23
|
-
import { basename, dirname, join, relative, resolve } from "path";
|
|
72
|
+
import { basename, dirname as dirname2, extname as extname2, join, relative, resolve } from "path";
|
|
24
73
|
import { tmpdir } from "os";
|
|
25
74
|
import sharp from "sharp";
|
|
26
75
|
|
|
@@ -251,18 +300,18 @@ function html(literalValues, ...interpolatedValues) {
|
|
|
251
300
|
|
|
252
301
|
// src/plugins/astro-latex-compile/utils.ts
|
|
253
302
|
var CONTENT_ROOT = "src/content/docs/";
|
|
254
|
-
function computeJpgPath(
|
|
303
|
+
function computeJpgPath(outputDir, filePath, blockId) {
|
|
255
304
|
const normalized = filePath.replace(/\\/g, "/");
|
|
256
305
|
const idx = normalized.indexOf(CONTENT_ROOT);
|
|
257
306
|
const relativePath = idx !== -1 ? normalized.slice(idx + CONTENT_ROOT.length) : basename(normalized);
|
|
258
|
-
const dir =
|
|
259
|
-
const filename = basename(relativePath);
|
|
260
|
-
const
|
|
261
|
-
const
|
|
262
|
-
return
|
|
307
|
+
const dir = dirname2(relativePath);
|
|
308
|
+
const filename = basename(relativePath, extname2(relativePath));
|
|
309
|
+
const dirChecksum = createHash("md5").update(dir).digest("hex").slice(0, 8);
|
|
310
|
+
const jpgFilename = `${filename}--${blockId}-${dirChecksum}.jpg`;
|
|
311
|
+
return join(resolve(outputDir), jpgFilename);
|
|
263
312
|
}
|
|
264
313
|
async function writeJpgFromSvg(svgPath, jpgPath) {
|
|
265
|
-
|
|
314
|
+
mkdirSync2(dirname2(jpgPath), { recursive: true });
|
|
266
315
|
await sharp(svgPath, { density: 300 }).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
|
|
267
316
|
}
|
|
268
317
|
function stripAnsi(text) {
|
|
@@ -313,7 +362,7 @@ async function writeJpgError(jpgPath, header, errorText) {
|
|
|
313
362
|
<text x="${padding}" y="${padding + 40}" font-family="monospace" font-size="12" fill="#666666">${escapeXml(header)}</text>
|
|
314
363
|
${textLines}
|
|
315
364
|
</svg>`;
|
|
316
|
-
|
|
365
|
+
mkdirSync2(dirname2(jpgPath), { recursive: true });
|
|
317
366
|
await sharp(Buffer.from(svg)).flatten({ background: { r: 255, g: 255, b: 255 } }).jpeg({ quality: 90 }).toFile(jpgPath);
|
|
318
367
|
}
|
|
319
368
|
function computeLineOffset(_latexCode) {
|
|
@@ -418,10 +467,10 @@ function buildLatexSource(latexCode) {
|
|
|
418
467
|
async function compileLatexToSvg(latexCode, svgOutputDir, texInputDirs = [], inputsSalt = "") {
|
|
419
468
|
const hash = hashLatexCode(latexCode, inputsSalt);
|
|
420
469
|
const svgPath = join(svgOutputDir, `${hash}.svg`);
|
|
421
|
-
if (
|
|
470
|
+
if (existsSync2(svgPath)) {
|
|
422
471
|
return { hash, svgPath, wasCompiled: false };
|
|
423
472
|
}
|
|
424
|
-
|
|
473
|
+
mkdirSync2(svgOutputDir, { recursive: true });
|
|
425
474
|
const workDir = mkdtempSync(join(tmpdir(), "latex-compile-"));
|
|
426
475
|
const texFile = join(workDir, "diagram.tex");
|
|
427
476
|
const pdfFile = join(workDir, "diagram.pdf");
|
|
@@ -479,7 +528,7 @@ Error: ${errorOutput}`
|
|
|
479
528
|
}
|
|
480
529
|
} finally {
|
|
481
530
|
try {
|
|
482
|
-
|
|
531
|
+
rmSync2(workDir, { recursive: true, force: true });
|
|
483
532
|
} catch {
|
|
484
533
|
}
|
|
485
534
|
}
|
|
@@ -487,6 +536,8 @@ Error: ${errorOutput}`
|
|
|
487
536
|
}
|
|
488
537
|
|
|
489
538
|
// src/plugins/astro-latex-compile/index.ts
|
|
539
|
+
var DEBUG_LOG_MAX_SIZE_BYTES = 5 * 1024 * 1024;
|
|
540
|
+
var DEBUG_LOG_BACKUP_COUNT = 3;
|
|
490
541
|
function getFrontmatterOffset(absoluteFilePath) {
|
|
491
542
|
try {
|
|
492
543
|
const original = readFileSync2(absoluteFilePath, "utf-8");
|
|
@@ -499,6 +550,12 @@ function getFrontmatterOffset(absoluteFilePath) {
|
|
|
499
550
|
}
|
|
500
551
|
function latexCompileTransformer(options) {
|
|
501
552
|
const svgOutputDir = resolve2(options.svgOutputDir);
|
|
553
|
+
const debugLogger = options.debug ? new RotatingFileLogger({
|
|
554
|
+
identifier: "astro-latex-compile",
|
|
555
|
+
filePath: options.debug.logFile,
|
|
556
|
+
maxSizeBytes: DEBUG_LOG_MAX_SIZE_BYTES,
|
|
557
|
+
backupCount: DEBUG_LOG_BACKUP_COUNT
|
|
558
|
+
}) : null;
|
|
502
559
|
return async function transformer(tree, file) {
|
|
503
560
|
const nodes = [];
|
|
504
561
|
visit(tree, "code", (node, index, parent) => {
|
|
@@ -516,7 +573,7 @@ function latexCompileTransformer(options) {
|
|
|
516
573
|
nodes.map(async ({ node, index, parent }) => {
|
|
517
574
|
const lineNumberStr = node.position?.start.line !== void 0 ? String(node.position.start.line + frontmatterOffset) : "?";
|
|
518
575
|
const blockId = new MetaOptions(node.meta ?? "").getInteger("blockid");
|
|
519
|
-
const jpgPath = options.
|
|
576
|
+
const jpgPath = options.debug && blockId !== void 0 ? computeJpgPath(options.debug.outputDir, filePath, blockId) : null;
|
|
520
577
|
try {
|
|
521
578
|
const result = await compileLatexToSvg(
|
|
522
579
|
node.value,
|
|
@@ -532,8 +589,8 @@ function latexCompileTransformer(options) {
|
|
|
532
589
|
options._referencedHashes?.add(result.hash);
|
|
533
590
|
if (jpgPath) {
|
|
534
591
|
await writeJpgFromSvg(result.svgPath, jpgPath);
|
|
535
|
-
|
|
536
|
-
|
|
592
|
+
await debugLogger?.write(
|
|
593
|
+
`${relFilePath}:${lineNumberStr}: wrote ${jpgPath}`
|
|
537
594
|
);
|
|
538
595
|
}
|
|
539
596
|
return {
|
|
@@ -588,7 +645,7 @@ ${details}`
|
|
|
588
645
|
);
|
|
589
646
|
options._fileHashMap.set(filePath, newHashes);
|
|
590
647
|
}
|
|
591
|
-
if (options.
|
|
648
|
+
if (options.debug && options._fileJpgPathMap) {
|
|
592
649
|
const newJpgPaths = new Set(
|
|
593
650
|
results.map((r) => r.jpgPath).filter(Boolean)
|
|
594
651
|
);
|
package/dist/index.js
CHANGED
|
@@ -9,14 +9,14 @@ import {
|
|
|
9
9
|
} from "./chunk-3UMY7T6G.js";
|
|
10
10
|
import {
|
|
11
11
|
latexCompileTransformer
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-GVS36MVT.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-
|
|
19
|
+
} from "./chunk-BM7LMGAH.js";
|
|
20
20
|
import {
|
|
21
21
|
parseFrontmatter
|
|
22
22
|
} from "./chunk-EEPQVOZC.js";
|
|
@@ -311,12 +311,24 @@ function astroLatexCompile(options) {
|
|
|
311
311
|
if (command === "build") {
|
|
312
312
|
await clearContentLayerCache(config);
|
|
313
313
|
}
|
|
314
|
+
if (command === "dev" && options.debug) {
|
|
315
|
+
const debugOutputDir = resolve2(options.debug.outputDir);
|
|
316
|
+
let entries;
|
|
317
|
+
try {
|
|
318
|
+
entries = await readdir(debugOutputDir);
|
|
319
|
+
} catch {
|
|
320
|
+
entries = [];
|
|
321
|
+
}
|
|
322
|
+
await Promise.all(
|
|
323
|
+
entries.filter((f) => f.endsWith(".jpg")).map((f) => rm(join3(debugOutputDir, f), { force: true }))
|
|
324
|
+
);
|
|
325
|
+
}
|
|
314
326
|
const existingPlugins = Array.isArray(config.markdown?.remarkPlugins) ? config.markdown.remarkPlugins.filter(Boolean) : [];
|
|
315
327
|
const pluginOptions = {
|
|
316
328
|
...options,
|
|
317
329
|
_fileHashMap: options.removeOrphanedSvgs ? fileHashMap : void 0,
|
|
318
330
|
_referencedHashes: command === "build" && options.removeOrphanedSvgs ? referencedHashes : void 0,
|
|
319
|
-
_fileJpgPathMap: options.
|
|
331
|
+
_fileJpgPathMap: options.debug ? fileJpgPathMap : void 0
|
|
320
332
|
};
|
|
321
333
|
updateConfig({
|
|
322
334
|
markdown: {
|
|
@@ -36,14 +36,20 @@ interface LatexCompilePluginOptions {
|
|
|
36
36
|
*/
|
|
37
37
|
texInputDirs?: string[];
|
|
38
38
|
/**
|
|
39
|
-
* When set, a JPEG copy of each compiled diagram is written
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
39
|
+
* When set, a JPEG copy of each compiled diagram is written to a flat
|
|
40
|
+
* directory. Only blocks that carry a `blockid=<n>` meta tag produce a
|
|
41
|
+
* JPEG. Filename format: `<originating-file>-<8-char-dir-checksum>--<blockid>.jpg`,
|
|
42
|
+
* where the checksum namespaces files originating from different
|
|
43
|
+
* directories to avoid collisions between files with the same name.
|
|
43
44
|
* JPEGs are deleted automatically when their block is removed or its content
|
|
44
45
|
* changes. Intended for local inspection, not for publishing.
|
|
45
46
|
*/
|
|
46
|
-
|
|
47
|
+
debug?: {
|
|
48
|
+
/** Directory where debug JPEGs are written, as a flat file list. */
|
|
49
|
+
outputDir: string;
|
|
50
|
+
/** Path to a log file that records every JPEG (re)write. Rotates by size. */
|
|
51
|
+
logFile: string;
|
|
52
|
+
};
|
|
47
53
|
/**
|
|
48
54
|
* @internal Populated by the Astro integration to track which hashes were
|
|
49
55
|
* referenced during a build, used for full orphan cleanup at build:done.
|
|
@@ -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
|
-
|
|
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
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
package/package.json
CHANGED