Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 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
3 changes: 2 additions & 1 deletion public/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,8 @@ export function define(cell) {
v.define(outputs.length ? `cell ${id}` : null, inputs, body);
variables.push(v);
for (const o of outputs) variables.push(main.define(o, [`cell ${id}`], (exports) => exports[o]));
for (const f of files) attachedFiles.set(f.name, {url: `/_file${(new URL(f.name, location)).pathname}`, mimeType: f.mimeType}); // prettier-ignore
const fadir = new URL(globalThis._FileAttachmentBase, location).href;
for (const f of files) attachedFiles.set(f.name, {url: new URL(f.name, fadir).pathname, mimeType: f.mimeType});
for (const d of databases) databaseTokens.set(d.name, d);
}

Expand Down
1 change: 1 addition & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export interface Section {

export interface Config {
title?: string;
base?: string;
pages?: (Page | Section)[]; // TODO rename to sidebar?
}

Expand Down
8 changes: 6 additions & 2 deletions src/javascript/fetches.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
import {dirname, join} from "node:path";
import {simple} from "acorn-walk";
import {isLocalFetch} from "./features.js";
import {relativeUrl} from "../url.js";
import {getStringLiteralValue, isLocalFetch} from "./features.js";

export function rewriteFetches(output, rootNode, sourcePath) {
simple(rootNode.body, {
CallExpression(node) {
if (isLocalFetch(node, rootNode.references, sourcePath)) {
output.insertLeft(node.arguments[0].start + 3, "_file/");
const arg = node.arguments[0];
const value = relativeUrl(sourcePath, "/_file/" + join(dirname(sourcePath), getStringLiteralValue(arg)));
output.replaceLeft(arg.start, arg.end, JSON.stringify(value));
}
}
});
Expand Down
3 changes: 2 additions & 1 deletion src/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import {type RuleInline} from "markdown-it/lib/parser_inline.js";
import {type RenderRule, type default as Renderer} from "markdown-it/lib/renderer.js";
import MarkdownItAnchor from "markdown-it-anchor";
import mime from "mime";
import {relativeUrl} from "../src/url.js";
import {getLocalPath} from "./files.js";
import {computeHash} from "./hash.js";
import {parseInfo} from "./info.js";
Expand Down Expand Up @@ -330,7 +331,7 @@ function normalizePieceHtml(html: string, root: string, sourcePath: string, cont
const path = getLocalPath(sourcePath, href);
if (path) {
context.files.push({name: href, mimeType: mime.getType(href)});
element.setAttribute("href", `/_file/${path}`);
element.setAttribute("href", relativeUrl(sourcePath, `/_file/${path}`));
}
}
return isSingleElement(document) ? String(document) : `<span>${document}</span>`;
Expand Down
14 changes: 13 additions & 1 deletion src/preview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,18 @@ class Server {
try {
const url = new URL(req.url!, "http://localhost");
let {pathname} = url;
const config = {base: "/", ...(await readConfig(this.root))};
const {base} = config;
if (!base || !pathname.startsWith(base)) {
if (!base.match(/^[/]\w+[/]$/)) throw new Error(`unsupported base option ${base}`);
if (pathname === "/") {
res.writeHead(302, {Location: base});
res.end();
return;
}
throw new HttpError("Not found", 404);
}
pathname = pathname.slice(config.base.length - 1);
if (pathname === "/_observablehq/runtime.js") {
send(req, "/@observablehq/runtime/dist/runtime.js", {root: "./node_modules"}).pipe(res);
} else if (pathname.startsWith("/_observablehq/")) {
Expand Down Expand Up @@ -120,7 +132,7 @@ class Server {
root: this.root,
path: pathname,
pages,
title: (await readConfig(this.root))?.title,
title: config.title,
resolver: this._resolver!
});
const etag = `"${createHash("sha256").update(html).digest("base64")}"`;
Expand Down
13 changes: 7 additions & 6 deletions src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ ${Array.from(getImportPreloads(parseResult, path))
<script type="module">

import {${preview ? "open, " : ""}define} from "${relativeUrl(path, "/_observablehq/client.js")}";
globalThis._FileAttachmentBase = ${JSON.stringify(relativeUrl(path, "/_file" + dirname(path) + "/"))};

${preview ? `open({hash: ${JSON.stringify(hash)}});\n` : ""}${parseResult.cells
.map(resolver)
Expand Down Expand Up @@ -207,15 +208,15 @@ function footer(path: string, options?: Pick<Config, "pages" | "title">): string
: `<nav>${
!link.prev
? ""
: `<a rel="prev" href="${escapeDoubleQuoted(prettyPath(link.prev.path))}"><span>${escapeData(
link.prev.name
)}</span></a>`
: `<a rel="prev" href="${escapeDoubleQuoted(
relativeUrl(path, prettyPath(link.prev.path))
)}"><span>${escapeData(link.prev.name)}</span></a>`
}${
!link.next
? ""
: `<a rel="next" href="${escapeDoubleQuoted(prettyPath(link.next.path))}"><span>${escapeData(
link.next.name
)}</span></a>`
: `<a rel="next" href="${escapeDoubleQuoted(
relativeUrl(path, prettyPath(link.next.path))
)}"><span>${escapeData(link.next.name)}</span></a>`
}</nav>\n`
}<div>© ${new Date().getUTCFullYear()} Observable, Inc.</div>
</footer>`;
Expand Down
1 change: 1 addition & 0 deletions test/input/build/files/custom-styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a { color: pink; }
2 changes: 2 additions & 0 deletions test/input/build/files/file-top.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hello,world
1,2
18 changes: 18 additions & 0 deletions test/input/build/files/files.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<link rel=stylesheet href="custom-styles.css" />
<link rel=stylesheet href="subsection/additional-styles.css" />

```js
fetch("./file-top.csv")
```

```js
fetch("./subsection/file-sub.csv")
```

```js
FileAttachment("file-top.csv")
```

```js
FileAttachment("subsection/file-sub.csv")
```
1 change: 1 addition & 0 deletions test/input/build/files/subsection/additional-styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a { color: green; }
2 changes: 2 additions & 0 deletions test/input/build/files/subsection/file-sub.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Bonjour,monde
1,2
18 changes: 18 additions & 0 deletions test/input/build/files/subsection/subfiles.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<link rel=stylesheet href="../custom-styles.css" />
<link rel=stylesheet href="additional-styles.css" />

```js
fetch("../file-top.csv")
```

```js
fetch("./file-sub.csv")
```

```js
FileAttachment("../file-top.csv")
```

```js
FileAttachment("file-sub.csv")
```
3 changes: 2 additions & 1 deletion test/output/build/config/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script type="module">

import {define} from "./_observablehq/client.js";
globalThis._FileAttachmentBase = "./_file/";


</script>
Expand All @@ -35,7 +36,7 @@ <h1 id="index" tabindex="-1"><a class="observablehq-header-anchor" href="#index"
</ul>
</main>
<footer id="observablehq-footer">
<nav><a rel="next" href="/one"><span>One&#60;Two</span></a></nav>
<nav><a rel="next" href="./one"><span>One&#60;Two</span></a></nav>
<div>© 2023 Observable, Inc.</div>
</footer>
</div>
3 changes: 2 additions & 1 deletion test/output/build/config/one.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script type="module">

import {define} from "./_observablehq/client.js";
globalThis._FileAttachmentBase = "./_file/";


</script>
Expand All @@ -31,7 +32,7 @@
<h1 id="one" tabindex="-1"><a class="observablehq-header-anchor" href="#one">One</a></h1>
</main>
<footer id="observablehq-footer">
<nav><a rel="prev" href="/"><span>Index</span></a><a rel="next" href="/sub/two"><span>Two</span></a></nav>
<nav><a rel="prev" href="./"><span>Index</span></a><a rel="next" href="./sub/two"><span>Two</span></a></nav>
<div>© 2023 Observable, Inc.</div>
</footer>
</div>
3 changes: 2 additions & 1 deletion test/output/build/config/sub/two.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script type="module">

import {define} from "../_observablehq/client.js";
globalThis._FileAttachmentBase = "../_file/sub/";


</script>
Expand All @@ -31,7 +32,7 @@
<h1 id="two" tabindex="-1"><a class="observablehq-header-anchor" href="#two">Two</a></h1>
</main>
<footer id="observablehq-footer">
<nav><a rel="prev" href="/one"><span>One&#60;Two</span></a></nav>
<nav><a rel="prev" href="../one"><span>One&#60;Two</span></a></nav>
<div>© 2023 Observable, Inc.</div>
</footer>
</div>
1 change: 1 addition & 0 deletions test/output/build/files/_file/custom-styles.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a { color: pink; }
2 changes: 2 additions & 0 deletions test/output/build/files/_file/file-top.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Hello,world
1,2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
a { color: green; }
2 changes: 2 additions & 0 deletions test/output/build/files/_file/subsection/file-sub.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Bonjour,monde
1,2
61 changes: 61 additions & 0 deletions test/output/build/files/files.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap">
<link rel="stylesheet" type="text/css" href="./_observablehq/style.css">
<link rel="modulepreload" href="./_observablehq/runtime.js">
<script type="module">

import {define} from "./_observablehq/client.js";
globalThis._FileAttachmentBase = "./_file/";

define({id: "a7808707", inputs: ["display"], files: [{"name":"./file-top.csv","mimeType":"text/csv"}], body: (display) => {
display((
fetch("./_file/file-top.csv")
))
}});
define({id: "03b99abc", inputs: ["display"], files: [{"name":"./subsection/file-sub.csv","mimeType":"text/csv"}], body: (display) => {
display((
fetch("./_file/subsection/file-sub.csv")
))
}});
define({id: "10037545", inputs: ["FileAttachment","display"], files: [{"name":"file-top.csv","mimeType":"text/csv"}], body: (FileAttachment,display) => {
display((
FileAttachment("file-top.csv")
))
}});
define({id: "453a8147", inputs: ["FileAttachment","display"], files: [{"name":"subsection/file-sub.csv","mimeType":"text/csv"}], body: (FileAttachment,display) => {
display((
FileAttachment("subsection/file-sub.csv")
))
}});

</script>
<input id="observablehq-sidebar-toggle" type="checkbox">
<nav id="observablehq-sidebar">
<ol>
<li class="observablehq-link observablehq-link-active"><a href="./files">Untitled</a></li>
<li class="observablehq-link"><a href="./subsection/subfiles">Untitled</a></li>
</ol>
</nav>
<script>{
const toggle = document.querySelector("#observablehq-sidebar-toggle");
const initialState = localStorage.getItem("observablehq-sidebar");
if (initialState) toggle.checked = initialState === "true";
else toggle.indeterminate = true;
}</script>
<div id="observablehq-center">
<main id="observablehq-main" class="observablehq">
<span><link rel="stylesheet" href="./_file/custom-styles.css">
<link rel="stylesheet" href="./_file/subsection/additional-styles.css">
</span><div id="cell-a7808707" class="observablehq observablehq--block"></div>
<div id="cell-03b99abc" class="observablehq observablehq--block"></div>
<div id="cell-10037545" class="observablehq observablehq--block"></div>
<div id="cell-453a8147" class="observablehq observablehq--block"></div>
</main>
<footer id="observablehq-footer">
<nav><a rel="next" href="./subsection/subfiles"><span>Untitled</span></a></nav>
<div>© 2023 Observable, Inc.</div>
</footer>
</div>
61 changes: 61 additions & 0 deletions test/output/build/files/subsection/subfiles.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap">
<link rel="stylesheet" type="text/css" href="../_observablehq/style.css">
<link rel="modulepreload" href="../_observablehq/runtime.js">
<script type="module">

import {define} from "../_observablehq/client.js";
globalThis._FileAttachmentBase = "../_file/subsection/";

define({id: "62f1fd0c", inputs: ["display"], files: [{"name":"../file-top.csv","mimeType":"text/csv"}], body: (display) => {
display((
fetch("../_file/file-top.csv")
))
}});
define({id: "94d347ec", inputs: ["display"], files: [{"name":"./file-sub.csv","mimeType":"text/csv"}], body: (display) => {
display((
fetch("../_file/subsection/file-sub.csv")
))
}});
define({id: "ef9a31ef", inputs: ["FileAttachment","display"], files: [{"name":"../file-top.csv","mimeType":"text/csv"}], body: (FileAttachment,display) => {
display((
FileAttachment("../file-top.csv")
))
}});
define({id: "834ecf9f", inputs: ["FileAttachment","display"], files: [{"name":"file-sub.csv","mimeType":"text/csv"}], body: (FileAttachment,display) => {
display((
FileAttachment("file-sub.csv")
))
}});

</script>
<input id="observablehq-sidebar-toggle" type="checkbox">
<nav id="observablehq-sidebar">
<ol>
<li class="observablehq-link"><a href="../files">Untitled</a></li>
<li class="observablehq-link observablehq-link-active"><a href="./subfiles">Untitled</a></li>
</ol>
</nav>
<script>{
const toggle = document.querySelector("#observablehq-sidebar-toggle");
const initialState = localStorage.getItem("observablehq-sidebar");
if (initialState) toggle.checked = initialState === "true";
else toggle.indeterminate = true;
}</script>
<div id="observablehq-center">
<main id="observablehq-main" class="observablehq">
<span><link rel="stylesheet" href="../_file/custom-styles.css">
<link rel="stylesheet" href="../_file/subsection/additional-styles.css">
</span><div id="cell-62f1fd0c" class="observablehq observablehq--block"></div>
<div id="cell-94d347ec" class="observablehq observablehq--block"></div>
<div id="cell-ef9a31ef" class="observablehq observablehq--block"></div>
<div id="cell-834ecf9f" class="observablehq observablehq--block"></div>
</main>
<footer id="observablehq-footer">
<nav><a rel="prev" href="../files"><span>Untitled</span></a></nav>
<div>© 2023 Observable, Inc.</div>
</footer>
</div>
1 change: 1 addition & 0 deletions test/output/build/imports/foo/foo.html
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<script type="module">

import {define} from "../_observablehq/client.js";
globalThis._FileAttachmentBase = "../_file/foo/";

define({id: "a9220fae", inputs: ["display"], outputs: ["d3","bar"], body: async (display) => {
const d3 = await import("https://cdn.jsdelivr.net/npm/d3/+esm");
Expand Down
3 changes: 2 additions & 1 deletion test/output/build/multi/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script type="module">

import {define} from "./_observablehq/client.js";
globalThis._FileAttachmentBase = "./_file/";

define({id: "1bcb5df5", inputs: ["FileAttachment"], outputs: ["f1"], files: [{"name":"file1.csv","mimeType":"text/csv"}], body: (FileAttachment) => {
const f1 = FileAttachment("file1.csv").csv();
Expand Down Expand Up @@ -46,7 +47,7 @@ <h1 id="multi-test" tabindex="-1"><a class="observablehq-header-anchor" href="#m
<div id="cell-aaa5c01d" class="observablehq observablehq--block"></div>
</main>
<footer id="observablehq-footer">
<nav><a rel="prev" href="/subsection/"><span>Sub-Section</span></a></nav>
<nav><a rel="prev" href="./subsection/"><span>Sub-Section</span></a></nav>
<div>© 2023 Observable, Inc.</div>
</footer>
</div>
3 changes: 2 additions & 1 deletion test/output/build/multi/subsection/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script type="module">

import {define} from "../_observablehq/client.js";
globalThis._FileAttachmentBase = "../_file/subsection/";


</script>
Expand All @@ -31,7 +32,7 @@ <h1 id="sub-section" tabindex="-1"><a class="observablehq-header-anchor" href="#
<p>This is a sub-section of the multi-section page.</p>
</main>
<footer id="observablehq-footer">
<nav><a rel="next" href="/"><span>Multi test</span></a></nav>
<nav><a rel="next" href="../"><span>Multi test</span></a></nav>
<div>© 2023 Observable, Inc.</div>
</footer>
</div>
1 change: 1 addition & 0 deletions test/output/build/simple/simple.html
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
<script type="module">

import {define} from "./_observablehq/client.js";
globalThis._FileAttachmentBase = "./_file/";

define({id: "115586ff", inputs: ["FileAttachment"], outputs: ["result"], files: [{"name":"data.txt","mimeType":"text/plain"}], body: (FileAttachment) => {
let result = FileAttachment("data.txt").text();
Expand Down