Skip to content

Commit 006b229

Browse files
committed
promote local media assets to file attachments
1 parent 5f34821 commit 006b229

File tree

2 files changed

+97
-8
lines changed

2 files changed

+97
-8
lines changed

src/markdown.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -316,22 +316,32 @@ function renderIntoPieces(renderer: Renderer, root: string, sourcePath: string):
316316
}
317317
let result = "";
318318
for (const piece of context.pieces) {
319-
result += piece.html = normalizePieceHtml(piece.html, root, sourcePath, context);
319+
result += piece.html = normalizePieceHtml(piece.html, sourcePath, context);
320320
}
321321
return result;
322322
};
323323
}
324324

325-
function normalizePieceHtml(html: string, root: string, sourcePath: string, context: ParseContext): string {
325+
const SUPPORTED_PROPERTIES: readonly {query: string; src: string}[] = Object.freeze([
326+
{query: "img[src]", src: "src"},
327+
{query: "video[src]", src: "src"},
328+
{query: "video source[src]", src: "src"},
329+
{query: "audio[src]", src: "src"},
330+
{query: "audio source[src]", src: "src"},
331+
{query: "link[href]", src: "href"}
332+
]);
333+
export function normalizePieceHtml(html: string, sourcePath: string, context: ParseContext): string {
326334
const {document} = parseHTML(html);
327335

328336
// Extracting references to files (such as from linked stylesheets).
329-
for (const element of document.querySelectorAll("link[href]") as any as Iterable<Element>) {
330-
const href = element.getAttribute("href")!;
331-
const path = getLocalPath(sourcePath, href);
332-
if (path) {
333-
context.files.push(fileReference(href, sourcePath));
334-
element.setAttribute("href", relativeUrl(sourcePath, join("_file", path)));
337+
for (const {query, src} of SUPPORTED_PROPERTIES) {
338+
for (const element of document.querySelectorAll(query) as any as Iterable<Element>) {
339+
const relativePath = element.getAttribute(src)!;
340+
const path = getLocalPath(sourcePath, relativePath);
341+
if (path) {
342+
context.files.push(fileReference(relativePath, sourcePath));
343+
element.setAttribute(src!, relativeUrl(sourcePath, join("_file", path)));
344+
}
335345
}
336346
}
337347

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import assert from "node:assert";
2+
import {normalizePieceHtml} from "../src/markdown.js";
3+
import {resolvePath} from "../src/url.js";
4+
5+
const html = (strings, ...values) => String.raw({raw: strings}, ...values);
6+
const mockContext = () => ({files: [], imports: [], pieces: [], startLine: 0, currentLine: 0});
7+
8+
describe("file attachments", () => {
9+
describe("success", () => {
10+
const sourcePath = "/attachments.md";
11+
12+
it("img[src]", () => {
13+
const htmlStr = html`<img src="./test.png">`;
14+
const expected = html`<img src="./_file/test.png">`;
15+
const context = mockContext();
16+
const actual = normalizePieceHtml(htmlStr, sourcePath, context);
17+
18+
assert.equal(expected, actual);
19+
assert.deepEqual(context.files, [
20+
{
21+
mimeType: "image/png",
22+
name: "./test.png",
23+
path: "./_file/test.png"
24+
}
25+
]);
26+
});
27+
28+
it("video[src]", () => {
29+
const htmlStr = html`<video src="observable.mov" controls>
30+
Learn more about Observable CLI
31+
</video>`;
32+
const expected = html`<video src="./_file/observable.mov" controls>
33+
Learn more about Observable CLI
34+
</video>`;
35+
const context = mockContext();
36+
const actual = normalizePieceHtml(htmlStr, sourcePath, context);
37+
38+
assert.equal(expected, actual);
39+
assert.deepEqual(context.files, [
40+
{
41+
mimeType: "video/quicktime",
42+
name: "observable.mov",
43+
path: "./_file/observable.mov"
44+
}
45+
]);
46+
});
47+
48+
it("video source[src]", () => {
49+
const htmlStr = html`<video width="320" height="240" controls>
50+
<source src="observable.mp4" type="video/mp4">
51+
<source src="observable.mov" type="video/mov">
52+
Learn more about Observable CLI
53+
</video>`;
54+
55+
const expected = html`<video width="320" height="240" controls>
56+
<source src="./_file/observable.mp4" type="video/mp4">
57+
<source src="./_file/observable.mov" type="video/mov">
58+
Learn more about Observable CLI
59+
</video>`;
60+
61+
const context = mockContext();
62+
const actual = normalizePieceHtml(htmlStr, sourcePath, context);
63+
64+
assert.equal(expected, actual);
65+
assert.deepEqual(context.files, [
66+
{
67+
mimeType: "video/mp4",
68+
name: "observable.mp4",
69+
path: "./_file/observable.mp4"
70+
},
71+
{
72+
mimeType: "video/quicktime",
73+
name: "observable.mov",
74+
path: "./_file/observable.mov"
75+
}
76+
]);
77+
});
78+
});
79+
});

0 commit comments

Comments
 (0)