Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions src/lib/components/PromptBanner.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
4 changes: 3 additions & 1 deletion src/lib/layouts/DocsArticle.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@
export let toc: Array<TocItem>;
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<Readable<HeaderSectionInfoAlert | null>>('headerSectionInfoAlert')
: readable(null);

const showCopyPage = !hasRoutePrompt();
// Hide "Copy Page" button if a prompt exists (either co-located or via promptPath)
const showCopyPage = !hasRoutePrompt(promptPath);
</script>

<main class="contents" id="main">
Expand Down
68 changes: 68 additions & 0 deletions src/lib/preprocessors/promptAsContent.js
Original file line number Diff line number Diff line change
@@ -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;
}
Comment on lines +24 to +27
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we not have a frontmatter parser somewhere already? 👀


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
};
}
};
}
5 changes: 3 additions & 2 deletions src/markdoc/layouts/Article.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -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<LayoutContext>('headings', writable({}));

Expand Down Expand Up @@ -79,7 +80,7 @@
<SeoOgImage {title} {description} />
</svelte:head>

<DocsArticle {title} {back} {toc} {date}>
<DocsArticle {title} {back} {toc} {date} promptPath={prompt}>
<svelte:fragment slot="metadata">
{#if difficulty}
<li>{difficulty}</li>
Expand All @@ -88,7 +89,7 @@
<li>{readtime} min</li>
{/if}
</svelte:fragment>
<PromptBanner />
<PromptBanner promptPath={prompt} />
<slot />
</DocsArticle>
<MainFooter variant="docs" />
Original file line number Diff line number Diff line change
Expand Up @@ -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.
2 changes: 2 additions & 0 deletions svelte.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ 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 = {
// Consult https://kit.svelte.dev/docs/integrations#preprocessors
// for more information about preprocessors
preprocess: sequence([
vitePreprocess(),
promptAsContentPreprocessor(),
markdoc({
generateSchema: true,
nodes: absolute('./src/markdoc/nodes/_Module.svelte'),
Expand Down