diff --git a/src/lib/components/PromptBanner.svelte b/src/lib/components/PromptBanner.svelte index 1d0b82ca9d..9e47ab88e6 100644 --- a/src/lib/components/PromptBanner.svelte +++ b/src/lib/components/PromptBanner.svelte @@ -10,9 +10,16 @@ import AiPromptIcon from '$lib/components/ui/aiPromptIcon.svelte'; import { browser } from '$app/environment'; - // Only support co-located prompt.md - const routeExists = hasRoutePrompt(); - const prompt = routeExists ? (getRoutePrompt() ?? '') : ''; + // Optional path to a prompt located elsewhere (e.g., "/docs/quick-starts/nextjs") + // If not provided, falls back to co-located prompt.md for current page + interface Props { + promptPath?: string; + } + let { promptPath }: Props = $props(); + + // Support co-located prompt.md or prompt from a different route via promptPath + const routeExists = hasRoutePrompt(promptPath); + const prompt = routeExists ? (getRoutePrompt(promptPath) ?? '') : ''; const exists = routeExists; const { copied, copy } = createCopy(prompt); diff --git a/src/lib/layouts/DocsArticle.svelte b/src/lib/layouts/DocsArticle.svelte index 18c093f560..820b0fde96 100644 --- a/src/lib/layouts/DocsArticle.svelte +++ b/src/lib/layouts/DocsArticle.svelte @@ -33,13 +33,15 @@ export let toc: Array; export let back: string | undefined = undefined; export let date: string | undefined = undefined; + export let promptPath: string | undefined = undefined; const reducedArticleSize = setContext('articleHasNumericBadge', writable(false)); const headerSectionInfoAlert = hasContext('headerSectionInfoAlert') ? getContext>('headerSectionInfoAlert') : readable(null); - const showCopyPage = !hasRoutePrompt(); + // Hide "Copy Page" button if a prompt exists (either co-located or via promptPath) + const showCopyPage = !hasRoutePrompt(promptPath);
diff --git a/src/lib/preprocessors/promptAsContent.js b/src/lib/preprocessors/promptAsContent.js new file mode 100644 index 0000000000..498259b4c0 --- /dev/null +++ b/src/lib/preprocessors/promptAsContent.js @@ -0,0 +1,68 @@ +import { readFileSync, existsSync } from 'fs'; +import { join, dirname } from 'path'; +import { fileURLToPath } from 'url'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const routesDir = join(__dirname, '../../routes'); + +/** + * Svelte preprocessor that injects prompt.md content into markdoc files + * when `promptAsContent: true` is set in the frontmatter. + * + * @returns {import('svelte/compiler').PreprocessorGroup} + */ +export function promptAsContentPreprocessor() { + return { + name: 'prompt-as-content', + markup({ content, filename }) { + // Only process .markdoc files + if (!filename?.endsWith('.markdoc')) { + return; + } + + // Check for promptAsContent in frontmatter + const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/); + if (!frontmatterMatch) { + return; + } + + const frontmatter = frontmatterMatch[1]; + + // Check if promptAsContent is true + if (!/promptAsContent:\s*true/.test(frontmatter)) { + return; + } + + // Extract the prompt path + const promptMatch = frontmatter.match(/prompt:\s*(.+)/); + if (!promptMatch) { + console.warn( + `[prompt-as-content] promptAsContent is true but no prompt path found in ${filename}` + ); + return; + } + + const promptPath = promptMatch[1].trim(); + // Convert route path like "/docs/quick-starts/nextjs" to file path + const promptFilePath = join(routesDir, promptPath.replace(/^\//, ''), 'prompt.md'); + + if (!existsSync(promptFilePath)) { + console.warn(`[prompt-as-content] prompt.md not found at ${promptFilePath}`); + return; + } + + // Read the prompt content + const promptContent = readFileSync(promptFilePath, 'utf-8'); + + // Inject the prompt content after the frontmatter + const newContent = content.replace( + /^(---\n[\s\S]*?\n---)\n*/, + `$1\n\n${promptContent}\n` + ); + + return { + code: newContent + }; + } + }; +} diff --git a/src/markdoc/layouts/Article.svelte b/src/markdoc/layouts/Article.svelte index 3006def0ff..c57fd0bbaf 100644 --- a/src/markdoc/layouts/Article.svelte +++ b/src/markdoc/layouts/Article.svelte @@ -30,6 +30,7 @@ export let difficulty: string | undefined = undefined; export let readtime: string | undefined = undefined; export let date: string | undefined = undefined; + export let prompt: string | undefined = undefined; setContext('headings', writable({})); @@ -79,7 +80,7 @@ - + {#if difficulty}
  • {difficulty}
  • @@ -88,7 +89,7 @@
  • {readtime} min
  • {/if}
    - +
    diff --git a/src/routes/docs/tooling/ai/quickstart-prompts/nextjs/+page.markdoc b/src/routes/docs/tooling/ai/quickstart-prompts/nextjs/+page.markdoc index 51295b758b..d49cd30693 100644 --- a/src/routes/docs/tooling/ai/quickstart-prompts/nextjs/+page.markdoc +++ b/src/routes/docs/tooling/ai/quickstart-prompts/nextjs/+page.markdoc @@ -2,6 +2,6 @@ layout: article title: Next.js description: Quickstart prompt for integrating Appwrite with Next.js. +prompt: /docs/quick-starts/nextjs +promptAsContent: true --- - -Quickstart prompt for integrating Appwrite with Next.js. diff --git a/svelte.config.js b/svelte.config.js index 5170f11599..a9837e401a 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -4,6 +4,7 @@ import { vitePreprocess } from '@sveltejs/vite-plugin-svelte'; import { dirname, join } from 'path'; import { markdoc } from 'svelte-markdoc-preprocess'; import { fileURLToPath } from 'url'; +import { promptAsContentPreprocessor } from './src/lib/preprocessors/promptAsContent.js'; /** @type {import('@sveltejs/kit').Config}*/ const config = { @@ -11,6 +12,7 @@ const config = { // for more information about preprocessors preprocess: sequence([ vitePreprocess(), + promptAsContentPreprocessor(), markdoc({ generateSchema: true, nodes: absolute('./src/markdoc/nodes/_Module.svelte'),