Skip to content

Commit 22bb344

Browse files
Merge branch 'main' into patch-20
2 parents 6e54bc7 + 44d9281 commit 22bb344

40 files changed

+1632
-302
lines changed

.github/workflows/lint.yml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,32 @@ jobs:
3737
working-directory: "reference_gen"
3838
run: deno task doc
3939

40+
- name: Restore OG image cache
41+
uses: actions/cache@v4
42+
id: og-cache
43+
with:
44+
path: .og-cache
45+
key: og-images-${{ hashFiles('open_graph/**') }}
46+
restore-keys: og-images-
47+
4048
- name: Build
4149
env:
4250
ORAMA_PROJECT_ID: ${{ vars.ORAMA_PROJECT_ID }}
4351
ORAMA_DATASOURCE_ID: ${{ vars.ORAMA_DATASOURCE_ID }}
4452
ORAMA_PRIVATE_API_KEY: ${{ secrets.ORAMA_PRIVATE_API_KEY }}
53+
SKIP_OG: ${{ steps.og-cache.outputs.cache-hit == 'true' && '1' || '' }}
4554
run: deno task build
4655

56+
- name: Restore cached OG images
57+
if: steps.og-cache.outputs.cache-hit == 'true'
58+
run: cp -r .og-cache/_site/* _site/ 2>/dev/null || true
59+
60+
- name: Save OG images to cache
61+
if: steps.og-cache.outputs.cache-hit != 'true'
62+
run: |
63+
mkdir -p .og-cache/_site
64+
cd _site && find . -name "index.png" -exec cp --parents {} ../.og-cache/_site/ \;
65+
4766
- name: Run server
4867
run: deno run --allow-read=. --allow-net --allow-env --lock=deno.lock server.ts &
4968

_components/CopyPage.tsx

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
export default function CopyPage({ file }: { file: string | undefined }) {
2+
if (!file || file.includes("[")) return null;
3+
4+
const markdownUrl = `https://docs.deno.com${file}`;
5+
const claudeUrl = `https://claude.ai/new?q=${
6+
encodeURIComponent(
7+
`Read this page from the Deno docs: ${markdownUrl} and answer questions about the content.`,
8+
)
9+
}`;
10+
11+
return (
12+
<div class="copy-page-split inline-flex shrink-0 rounded-full border-2 border-foreground-primary dark:border-background-tertiary">
13+
{/* Primary: directly copies the URL */}
14+
<button
15+
type="button"
16+
class="copy-page-main-btn flex items-center gap-2 px-4 py-2 text-sm font-semibold text-foreground-primary bg-transparent hover:bg-header-highlight dark:hover:text-background-primary rounded-l-full transition-colors cursor-pointer select-none"
17+
>
18+
<svg
19+
xmlns="http://www.w3.org/2000/svg"
20+
aria-hidden="true"
21+
fill="currentColor"
22+
width="12"
23+
height="12"
24+
viewBox="0 0 16 16"
25+
>
26+
<path d="M0 6.75C0 5.784.784 5 1.75 5h1.5a.75.75 0 0 1 0 1.5h-1.5a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-1.5a.75.75 0 0 1 1.5 0v1.5A1.75 1.75 0 0 1 9.25 16h-7.5A1.75 1.75 0 0 1 0 14.25Z" />
27+
<path d="M5 1.75C5 .784 5.784 0 6.75 0h7.5C15.216 0 16 .784 16 1.75v7.5A1.75 1.75 0 0 1 14.25 11h-7.5A1.75 1.75 0 0 1 5 9.25Zm1.75-.25a.25.25 0 0 0-.25.25v7.5c0 .138.112.25.25.25h7.5a.25.25 0 0 0 .25-.25v-7.5a.25.25 0 0 0-.25-.25Z" />
28+
</svg>
29+
<span class="copy-page-main-label">Copy page</span>
30+
</button>
31+
32+
{/* Visual divider between the two halves */}
33+
<span
34+
class="self-stretch w-0.5 bg-foreground-primary dark:bg-background-tertiary shrink-0"
35+
aria-hidden="true"
36+
>
37+
</span>
38+
39+
{/* Chevron: opens the popover panel */}
40+
<button
41+
type="button"
42+
class="copy-page-toggle-btn flex items-center px-3 py-2 text-foreground-primary bg-transparent hover:bg-header-highlight dark:hover:text-background-primary rounded-r-full transition-colors cursor-pointer select-none"
43+
popovertarget="copy-page-menu"
44+
aria-label="More copy options"
45+
aria-haspopup="menu"
46+
>
47+
<svg
48+
class="copy-page-chevron transition-transform duration-200"
49+
xmlns="http://www.w3.org/2000/svg"
50+
aria-hidden="true"
51+
fill="currentColor"
52+
width="12"
53+
height="12"
54+
viewBox="0 0 16 16"
55+
>
56+
<path d="M12.78 5.22a.749.749 0 0 1 0 1.06l-4.25 4.25a.749.749 0 0 1-1.06 0L3.22 6.28a.749.749 0 1 1 1.06-1.06L8 8.939l3.72-3.719a.749.749 0 0 1 1.06 0Z" />
57+
</svg>
58+
</button>
59+
60+
{/* Popover panel — lives in top layer, positioned via JS */}
61+
<div id="copy-page-menu" popover="auto" class="copy-page-panel">
62+
<a
63+
href={file}
64+
target="_blank"
65+
class="no-underline flex items-start gap-3 px-4 py-3 hover:bg-background-secondary dark:hover:bg-gray-800 transition-colors"
66+
>
67+
<svg
68+
xmlns="http://www.w3.org/2000/svg"
69+
aria-hidden="true"
70+
fill="currentColor"
71+
width="16"
72+
height="16"
73+
viewBox="0 0 16 16"
74+
class="mt-0.5 shrink-0"
75+
>
76+
<path d="M3.75 2h3.5a.75.75 0 0 1 0 1.5h-3.5a.25.25 0 0 0-.25.25v8.5c0 .138.112.25.25.25h8.5a.25.25 0 0 0 .25-.25v-3.5a.75.75 0 0 1 1.5 0v3.5A1.75 1.75 0 0 1 12.25 14h-8.5A1.75 1.75 0 0 1 2 12.25v-8.5C2 2.784 2.784 2 3.75 2Zm6.854-1h4.146a.25.25 0 0 1 .25.25v4.146a.25.25 0 0 1-.427.177L13.03 4.03 9.28 7.78a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042l3.75-3.75-1.543-1.543A.25.25 0 0 1 10.604 1Z" />
77+
</svg>
78+
<span>
79+
<span class="block text-sm font-medium">View Page as Markdown</span>
80+
<span class="block text-xs text-foreground-secondary mt-0.5">
81+
Open the Markdown file in a new tab
82+
</span>
83+
</span>
84+
</a>
85+
<a
86+
href={claudeUrl}
87+
target="_blank"
88+
class="no-underline flex items-start gap-3 px-4 py-3 border-t border-foreground-tertiary hover:bg-background-secondary dark:hover:bg-gray-800 transition-colors"
89+
>
90+
<svg
91+
xmlns="http://www.w3.org/2000/svg"
92+
aria-hidden="true"
93+
fill="currentColor"
94+
width="16"
95+
height="16"
96+
viewBox="0 0 24 24"
97+
class="mt-0.5 shrink-0"
98+
>
99+
<path d="M12 0C12 0 10.5 10.5 0 12C10.5 12 12 24 12 24C12 24 13.5 13.5 24 12C13.5 12 12 0 12 0Z" />
100+
</svg>
101+
<span>
102+
<span class="block text-sm font-medium">Open in Claude</span>
103+
<span class="block text-xs text-foreground-secondary mt-0.5">
104+
Ask Claude about this page
105+
</span>
106+
</span>
107+
</a>
108+
</div>
109+
</div>
110+
);
111+
}

_components/Footer.tsx

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
11
export default function Footer_new() {
22
return (
3-
<footer class="text-smaller bg-gray-50 dark:bg-gray-950 p-4 pt-12 sm:px-8 border-t border-t-foreground-tertiary">
3+
<footer className="text-smaller bg-gray-50 dark:bg-gray-950 p-4 pt-12 sm:px-8 border-t border-t-foreground-tertiary">
44
<nav className="flex flex-col gap-x-4 gap-y-12 max-w-7xl md:flex-row md:flex-wrap md:justify-between md:w-full md:gap-y-8 md:mx-auto">
55
{data.map((category) => (
6-
<section class="flex-auto">
7-
<h3 class="mb-2 uppercase font-bold text-foreground-primary whitespace-pre">
6+
<section className="flex-auto">
7+
<h3 className="mb-2 uppercase font-bold text-foreground-primary whitespace-pre">
88
{category.title}
99
</h3>
10-
<ul class="m-0 p-0 pl-3 border-l border-l-background-tertiary list-none">
10+
<ul className="m-0 p-0 pl-3 border-l border-l-background-tertiary list-none">
1111
{category.items.map((item) => (
1212
<li>
1313
<a
14-
class="block mb-2 hover:text-primary hover:underline"
14+
className="block mb-2 hover:text-primary hover:underline"
1515
href={item.to ?? item.href}
1616
dangerouslySetInnerHTML={{ __html: item.label }}
1717
/>
@@ -21,7 +21,7 @@ export default function Footer_new() {
2121
</section>
2222
))}
2323
</nav>
24-
<p class="m-0 mt-16 mx-auto text-center text-xs text-foreground-secondary">
24+
<p className="m-0 mt-16 mx-auto text-center text-xs text-foreground-secondary">
2525
Copyright © {new Date().getFullYear()} the Deno authors.
2626
</p>
2727
</footer>
@@ -153,6 +153,10 @@ const data = [
153153
label: "Privacy Policy",
154154
href: "/deploy/privacy_policy",
155155
},
156+
{
157+
label: "LLMs",
158+
href: "/llms.txt",
159+
},
156160
],
157161
},
158162
] satisfies FooterCategory[];

_components/Header.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export default function (
1010
) {
1111
const hrefIsInCurrentSection = (href: string, currentSection: string) => {
1212
return href.includes(currentSection) ||
13-
href === "/services/" &&
13+
href === "/deploy/" &&
1414
["deploy", "subhosting", "services", "sandbox"].includes(
1515
currentSection,
1616
);

_components/TableOfContents.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ export default function TableOfContents({ data, toc, hasSubNav }: {
1212

1313
return (
1414
<ul
15-
className={`toc-list hidden sticky p-4 pr-0 h-screen-minus-header overflow-y-auto border-l border-l-foreground-tertiary lg:block lg:w-full ${topClasses}`}
15+
className={`toc-list hidden sticky ${topClasses} h-screen-minus-header overflow-y-auto border-l border-l-foreground-tertiary p-4 pr-0 lg:flex lg:flex-col lg:w-full`}
1616
id="toc"
1717
>
1818
{toc.map((item: TableOfContentsItem_) => (

_config.ts

Lines changed: 66 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import title from "https://deno.land/x/lume_markdown_plugins@v0.7.0/title.ts";
1616
import toc from "https://deno.land/x/lume_markdown_plugins@v0.7.0/toc.ts";
1717
// See note below about GFM CSS
1818
// import { CSS as GFM_CSS } from "https://jsr.io/@deno/gfm/0.11.0/style.ts";
19+
import { walk } from "@std/fs";
20+
import { dirname } from "@std/path";
1921
import { log } from "lume/core/utils/log.ts";
2022
import anchor from "npm:markdown-it-anchor@9";
2123
import admonitionPlugin from "./markdown-it/admonition.ts";
@@ -28,6 +30,7 @@ import createRoutingMiddleware from "./middleware/functionRoutes.ts";
2830
import createGAMiddleware from "./middleware/googleAnalytics.ts";
2931
import redirectsMiddleware from "./middleware/redirects.ts";
3032
import createLlmsFilesMiddleware from "./middleware/llmsFiles.ts";
33+
import createMarkdownSourceMiddleware from "./middleware/markdownSource.ts";
3134
import { toFileAndInMemory } from "./utils/redirects.ts";
3235
import { cliNow } from "./timeUtils.ts";
3336

@@ -73,6 +76,7 @@ const site = lume(
7376
server: {
7477
middlewares: [
7578
redirectsMiddleware,
79+
createMarkdownSourceMiddleware({ root: "_site" }),
7680
createRoutingMiddleware(),
7781
createGAMiddleware({
7882
addr: { transport: "tcp", hostname: "localhost", port: 3000 },
@@ -146,7 +150,6 @@ site.copy("runtime/reference/images");
146150
site.copy("runtime/contributing/images");
147151
site.copy("examples/tutorials/images");
148152
site.copy("deploy/manual/images");
149-
site.copy("deploy/images");
150153
site.copy("examples/scripts");
151154

152155
site.use(
@@ -187,6 +190,24 @@ site.addEventListener("afterBuild", async () => {
187190
/* NOTE: we used to get gfm.css from the jsr.io CDN, but now we simply have a local copy. This is because it needs to be placed on a CSS layer, which isn't possible with an imported file. */
188191
// Deno.writeTextFileSync(site.dest("gfm.css"), GFM_CSS);
189192

193+
// Copy lint rule markdown source files to _site so they're served at /lint/rules/*.md.
194+
// These files are excluded from Lume's processing pipeline (site.ignore) because
195+
// lint_rule.page.tsx handles page generation, but we still want the raw .md accessible.
196+
try {
197+
await Deno.mkdir(site.dest("lint/rules"), { recursive: true });
198+
for await (const entry of Deno.readDir("lint/rules")) {
199+
if (entry.isFile && entry.name.endsWith(".md")) {
200+
await Deno.copyFile(
201+
`lint/rules/${entry.name}`,
202+
site.dest(`lint/rules/${entry.name}`),
203+
);
204+
}
205+
}
206+
log.info("Copied lint rule markdown files to _site/lint/rules/");
207+
} catch (error) {
208+
log.error("Error copying lint rule markdown files: " + error);
209+
}
210+
190211
// Generate LLMs documentation files directly to _site directory
191212
if (Deno.env.get("BUILD_TYPE") == "FULL") {
192213
try {
@@ -231,6 +252,49 @@ site.addEventListener("afterBuild", async () => {
231252
log.error("Error generating LLMs files:" + error);
232253
}
233254
}
255+
256+
// Copy source .md files to _site so AI agents can request them directly.
257+
// Excludes "reference/" (dynamically generated, no static .md source files).
258+
const contentDirs = [
259+
"runtime",
260+
"deploy",
261+
"sandbox",
262+
"subhosting",
263+
"examples",
264+
];
265+
let mdCopied = 0;
266+
let mdErrors = false;
267+
for (const dir of contentDirs) {
268+
// Skip directories that don't exist in this build
269+
try {
270+
await Deno.stat(dir);
271+
} catch (error) {
272+
if (!(error instanceof Deno.errors.NotFound)) {
273+
log.error(`Error accessing content directory ${dir}: ${error}`);
274+
}
275+
continue;
276+
}
277+
try {
278+
for await (
279+
const entry of walk(dir, { exts: [".md"], includeDirs: false })
280+
) {
281+
const destPath = site.dest(entry.path);
282+
await Deno.mkdir(dirname(destPath), { recursive: true });
283+
await Deno.copyFile(entry.path, destPath);
284+
mdCopied++;
285+
}
286+
} catch (error) {
287+
log.error(`Error copying markdown files from ${dir}: ${error}`);
288+
mdErrors = true;
289+
}
290+
}
291+
if (mdErrors) {
292+
log.warn(
293+
`Copied ${mdCopied} source markdown files to _site (some directories had errors, see above)`,
294+
);
295+
} else {
296+
log.info(`Copied ${mdCopied} source markdown files to _site`);
297+
}
234298
});
235299

236300
site.copy("reference_gen/gen/deno/page.css", "/api/deno/page.css");
@@ -308,7 +372,7 @@ site.data("apiCategories", {
308372
});
309373

310374
// Do more expensive operations if we're building the full site
311-
if (Deno.env.get("BUILD_TYPE") == "FULL") {
375+
if (Deno.env.get("BUILD_TYPE") == "FULL" && !Deno.env.has("SKIP_OG")) {
312376
// Use Lume's built in date function to get the last modified date of the file
313377
// site.data("date", "Git Last Modified");;
314378

_data.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
"href": "/examples/"
1414
},
1515
{
16-
"name": "Services",
17-
"href": "/services/",
16+
"name": "Deno Deploy",
17+
"href": "/deploy/",
1818
"style": "services"
1919
}
2020
],

_includes/doc.tsx

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,17 @@ export default function Doc(data: Lume.Data, helpers: Lume.Helpers) {
6969
class="markdown-body mt-6 sm:mt-6"
7070
>
7171
{!(isReference && !isApiLandingPage) && (
72-
<h1
73-
dangerouslySetInnerHTML={{
74-
__html: helpers.md(data.title!, true),
75-
}}
76-
>
77-
</h1>
72+
<header class="flex items-start justify-between gap-4">
73+
<h1
74+
dangerouslySetInnerHTML={{
75+
__html: helpers.md(data.title!, true),
76+
}}
77+
>
78+
</h1>
79+
{file && !file.includes("[") && file.endsWith(".md") && (
80+
<data.comp.CopyPage file={file} />
81+
)}
82+
</header>
7883
)}
7984
{data.available_since && (
8085
<div class="bg-gray-200 rounded-md text-sm py-3 px-4 mb-4 font-semibold">

_includes/layout.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,15 @@ export default function Layout(data: Lume.Data) {
5151
type="font/woff2"
5252
crossOrigin="anonymous"
5353
/>
54+
{data.page?.sourcePath?.endsWith(".md") && data.url !== "/" && (
55+
<link
56+
rel="alternate"
57+
type="text/markdown"
58+
href={data.page.sourcePath.endsWith("/index.md")
59+
? `/${data.page.sourcePath}`
60+
: `${data.url.replace(/\/$/, "")}.md`}
61+
/>
62+
)}
5463
<link rel="me" href="https://fosstodon.org/@deno_land" />
5564
<data.comp.OpenGraph
5665
title={data.title}
@@ -68,6 +77,7 @@ export default function Layout(data: Lume.Data) {
6877
<script type="module" defer src="/js/copy.js"></script>
6978
<script type="module" defer src="/js/tabs.js"></script>
7079
<script type="module" defer src="/js/feedback.js"></script>
80+
<script type="module" defer src="/js/copy-page.js"></script>
7181
<script type="module" defer src="/js/search.js"></script>
7282
<script
7383
async

deploy/_data.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ export const sidebar = [
5454
title: "Domains",
5555
href: "/deploy/reference/domains/",
5656
},
57+
{
58+
title: "Cron",
59+
href: "/deploy/reference/cron/",
60+
},
5761
{
5862
title: "Deno KV",
5963
href: "/deploy/reference/deno_kv/",

0 commit comments

Comments
 (0)