From f47ec6ded6c96a16d6c69506e29fb96b3494e244 Mon Sep 17 00:00:00 2001 From: Oscar Otero Date: Sun, 22 Dec 2024 00:15:09 +0100 Subject: [PATCH] cssFile & placeholder options to prism and code_highlight --- CHANGELOG.md | 6 + plugins/code_highlight.ts | 35 ++- plugins/prism.ts | 43 ++- .../__snapshots__/code_highlight.test.ts.snap | 252 ++++++++++++++++- tests/__snapshots__/prism.test.ts.snap | 256 +++++++++++++++++- tests/code_highlight.test.ts | 18 +- tests/prism.test.ts | 18 +- 7 files changed, 610 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 801749f1..fa7c8ee8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ Go to the `v1` branch to see the changelog of Lume 1. - New plugin: `purgecss` to remove unused CSS. [#693] - New middleware `router`. - `google_fonts`: Added `subset` option to filter character ranges. [#692] +- `prism` plugin: Added `cssFile` and `placeholder` option to themes. +- `code_highlight` plugin: Added `cssFile` and `placeholder` option to themes. ### Changed - Files with extension `.d.ts` are ignored by default [#707]. @@ -20,6 +22,10 @@ Go to the `v1` branch to see the changelog of Lume 1. - Firefox: 100 (from 97) - Samsung: 19 (from 16) +### Deprecated +- `theme.path` option of `prism` plugin. +- `theme.path` option of `code_highlight` plugin. + ### Fixed - Updated deps: `sass`, `preact`, `xml`, `katex`, `jsr`, `pagefind`, `highlight.js`, `tailwindcss`, `magic-string` and some icons. - `esbuild`: JSR using import maps [#706]. diff --git a/plugins/code_highlight.ts b/plugins/code_highlight.ts index b4703f2e..0aae3441 100644 --- a/plugins/code_highlight.ts +++ b/plugins/code_highlight.ts @@ -5,6 +5,8 @@ import hljs, { } from "../deps/highlight.ts"; import { merge } from "../core/utils/object.ts"; import { log } from "../core/utils/log.ts"; +import { readFile } from "../core/utils/read.ts"; +import { insertContent } from "../core/utils/page_content.ts"; import type Site from "../core/site.ts"; import type { Page } from "../core/file.ts"; @@ -33,8 +35,17 @@ interface Theme { /** The name of the theme */ name: string; - /** The path to the theme file */ - path: string; + /** + * The path to the theme file + * @deprecated Use cssFile instead + */ + path?: string; + + /** The CSS file to output the font-face rules */ + cssFile?: string; + + /** A placeholder to replace with the generated CSS (only for cssFile) */ + placeholder?: string; } // Default options @@ -72,11 +83,21 @@ export function codeHighlight(userOptions?: Options) { ? options.theme : [options.theme]; - for (const { name, path } of themes) { - site.remoteFile( - path, - `${themesPath}${name}.min.css`, - ); + for (const { name, path, cssFile, placeholder } of themes) { + if (cssFile) { + site.addEventListener("afterRender", async () => { + const cssCode = await readFile(`${themesPath}${name}.min.css`); + const page = await site.getOrCreatePage(cssFile); + insertContent(page, cssCode, placeholder); + }); + return; + } + + if (path) { + site.remoteFile(path, `${themesPath}${name}.min.css`); + } else { + throw new Error(`The theme ${name} must have a path or cssFile`); + } } } diff --git a/plugins/prism.ts b/plugins/prism.ts index e54f1d10..b5f4dfb5 100644 --- a/plugins/prism.ts +++ b/plugins/prism.ts @@ -1,5 +1,7 @@ import Prism, { themesPath } from "../deps/prism.ts"; import { merge } from "../core/utils/object.ts"; +import { readFile } from "../core/utils/read.ts"; +import { insertContent } from "../core/utils/page_content.ts"; import type Site from "../core/site.ts"; import type { Page } from "../core/file.ts"; @@ -22,8 +24,17 @@ interface Theme { /** The name of the theme */ name: string; - /** The path to the theme file */ - path: string; + /** + * The path to the theme file + * @deprecated Use cssFile instead + */ + path?: string; + + /** The CSS file to output the font-face rules */ + cssFile?: string; + + /** A placeholder to replace with the generated CSS (only for cssFile) */ + placeholder?: string; } // Default options @@ -47,11 +58,21 @@ export function prism(userOptions?: Options) { ? options.theme : [options.theme]; - for (const { name, path } of themes) { - site.remoteFile( - path, - `${themesPath}prism-${name}.min.css`, - ); + for (const { name, path, cssFile, placeholder } of themes) { + if (cssFile) { + site.addEventListener("afterRender", async () => { + const cssCode = await readFile(getCssUrl(name)); + const page = await site.getOrCreatePage(cssFile); + insertContent(page, cssCode, placeholder); + }); + return; + } + + if (path) { + site.remoteFile(path, getCssUrl(name)); + } else { + throw new Error(`The theme ${name} must have a path or cssFile`); + } } } @@ -63,3 +84,11 @@ export function prism(userOptions?: Options) { } export default prism; + +function getCssUrl(name: string) { + if (name === "default") { + return `${themesPath}prism.min.css`; + } + + return `${themesPath}prism-${name}.min.css`; +} diff --git a/tests/__snapshots__/code_highlight.test.ts.snap b/tests/__snapshots__/code_highlight.test.ts.snap index 84f0a180..b2878726 100644 --- a/tests/__snapshots__/code_highlight.test.ts.snap +++ b/tests/__snapshots__/code_highlight.test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`code_hightlight plugin 1`] = ` +snapshot[`code_hightlight plugin with path 1`] = ` { formats: [ { @@ -112,10 +112,260 @@ snapshot[`code_hightlight plugin 1`] = ` } `; +snapshot[`code_hightlight plugin with path 2`] = `[]`; + +snapshot[`code_hightlight plugin with path 3`] = ` +[ + { + content: ' +

Code highlight plugin testing

+
<p>This is a HTML code</p>
+
+
Not highlighted code
+
+
p {
+  color: red
+}
+
+', + data: { + basename: "index", + children: '

Code highlight plugin testing

+
<p>This is a HTML code</p>
+
+
Not highlighted code
+
+
p {
+  color: red
+}
+
+', + content: "# Code highlight plugin testing + +\`\`\`html +

This is a HTML code

+\`\`\` + +\`\`\` +Not highlighted code +\`\`\` + +\`\`\`css +p { + color: red +} +\`\`\` +", + date: [], + mergedKeys: [ + "tags", + ], + page: [ + "src", + "data", + "asset", + ], + paginate: "paginate", + search: [], + tags: "Array(0)", + url: "/", + }, + src: { + asset: false, + ext: ".md", + path: "/index", + remote: undefined, + }, + }, + { + content: ' + +
Code inside a PRE
+ Code inside a CODE +
Not highlighted code
+
p { display: none }
+ +', + data: { + basename: "other", + children: ' + +
Code inside a PRE
+ Code inside a CODE +
Not highlighted code
+
p { display: none }
+ +', + content: ' + +
Code inside a PRE
+ Code inside a CODE +
Not highlighted code
+
p { display: none }
+ +', + date: [], + mergedKeys: [ + "tags", + ], + page: [ + "src", + "data", + "asset", + ], + paginate: "paginate", + search: [], + tags: "Array(0)", + url: "/other/", + }, + src: { + asset: false, + ext: ".vto", + path: "/other", + remote: undefined, + }, + }, +] +`; + +snapshot[`code_hightlight plugin 1`] = ` +{ + formats: [ + { + engines: 0, + ext: ".page.toml", + loader: [AsyncFunction: toml], + pageType: "page", + }, + { + engines: 1, + ext: ".page.ts", + loader: [AsyncFunction: module], + pageType: "page", + }, + { + engines: 1, + ext: ".page.js", + loader: [AsyncFunction: module], + pageType: "page", + }, + { + engines: 0, + ext: ".page.jsonc", + loader: [AsyncFunction: json], + pageType: "page", + }, + { + engines: 0, + ext: ".page.json", + loader: [AsyncFunction: json], + pageType: "page", + }, + { + dataLoader: [AsyncFunction: json], + engines: 0, + ext: ".json", + loader: [AsyncFunction: json], + }, + { + dataLoader: [AsyncFunction: json], + engines: 0, + ext: ".jsonc", + loader: [AsyncFunction: json], + }, + { + engines: 1, + ext: ".md", + loader: [AsyncFunction: text], + pageType: "page", + }, + { + engines: 1, + ext: ".markdown", + loader: [AsyncFunction: text], + pageType: "page", + }, + { + dataLoader: [AsyncFunction: module], + engines: 1, + ext: ".js", + loader: [AsyncFunction: module], + }, + { + dataLoader: [AsyncFunction: module], + engines: 1, + ext: ".ts", + loader: [AsyncFunction: module], + }, + { + engines: 1, + ext: ".vento", + loader: [AsyncFunction: text], + pageType: "page", + }, + { + engines: 1, + ext: ".vto", + loader: [AsyncFunction: text], + pageType: "page", + }, + { + dataLoader: [AsyncFunction: toml], + engines: 0, + ext: ".toml", + loader: [AsyncFunction: toml], + }, + { + dataLoader: [AsyncFunction: yaml], + engines: 0, + ext: ".yaml", + loader: [AsyncFunction: yaml], + pageType: "page", + }, + { + dataLoader: [AsyncFunction: yaml], + engines: 0, + ext: ".yml", + loader: [AsyncFunction: yaml], + pageType: "page", + }, + ], + src: [ + "/", + "/index.md", + "/other.vto", + ], +} +`; + snapshot[`code_hightlight plugin 2`] = `[]`; snapshot[`code_hightlight plugin 3`] = ` [ + { + content: "pre code.hljs{display:block;overflow-x:auto;padding:1em}code.hljs{padding:3px 5px}/*! + Theme: a11y-dark + Author: @ericwbailey + Maintainer: @ericwbailey + + Based on the Tomorrow Night Eighties theme: https://github.com/isagalaev/highlight.js/blob/master/src/styles/tomorrow-night-eighties.css +*/.hljs{background:#2b2b2b;color:#f8f8f2}.hljs-comment,.hljs-quote{color:#d4d0ab}.hljs-deletion,.hljs-name,.hljs-regexp,.hljs-selector-class,.hljs-selector-id,.hljs-tag,.hljs-template-variable,.hljs-variable{color:#ffa07a}.hljs-built_in,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-type{color:#f5ab35}.hljs-attribute{color:gold}.hljs-addition,.hljs-bullet,.hljs-string,.hljs-symbol{color:#abe338}.hljs-section,.hljs-title{color:#00e0e0}.hljs-keyword,.hljs-selector-tag{color:#dcc6e0}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}@media screen and (-ms-high-contrast:active){.hljs-addition,.hljs-attribute,.hljs-built_in,.hljs-bullet,.hljs-comment,.hljs-link,.hljs-literal,.hljs-meta,.hljs-number,.hljs-params,.hljs-quote,.hljs-string,.hljs-symbol,.hljs-type{color:highlight}.hljs-keyword,.hljs-selector-tag{font-weight:700}}", + data: { + basename: "styles", + page: [ + "src", + "data", + "asset", + ], + url: "/styles.css", + }, + src: { + asset: true, + ext: "", + path: "", + remote: undefined, + }, + }, { content: '

Code highlight plugin testing

diff --git a/tests/__snapshots__/prism.test.ts.snap b/tests/__snapshots__/prism.test.ts.snap index 1284fdae..95961f42 100644 --- a/tests/__snapshots__/prism.test.ts.snap +++ b/tests/__snapshots__/prism.test.ts.snap @@ -1,6 +1,6 @@ export const snapshot = {}; -snapshot[`Prism plugin 1`] = ` +snapshot[`Prism plugin with path 1`] = ` { formats: [ { @@ -111,10 +111,264 @@ snapshot[`Prism plugin 1`] = ` } `; +snapshot[`Prism plugin with path 2`] = `[]`; + +snapshot[`Prism plugin with path 3`] = ` +[ + { + content: ' +

Hello world

+
body {
+  background: red;
+}
+
+
@width: 10px;
+@height: @width + 10px;
+
+#header {
+  width: @width;
+  height: @height;
+}
+
+
<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Document</title>
+</head>
+<body>
+  
+</body>
+</html>
+
+
console.log("foo");
+
+', + data: { + basename: "index", + children: '

Hello world

+
body {
+  background: red;
+}
+
+
@width: 10px;
+@height: @width + 10px;
+
+#header {
+  width: @width;
+  height: @height;
+}
+
+
<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport" content="width=device-width, initial-scale=1.0">
+  <title>Document</title>
+</head>
+<body>
+  
+</body>
+</html>
+
+
console.log("foo");
+
+', + content: '# Hello world + +\`\`\`css +body { + background: red; +} +\`\`\` + +\`\`\`less +@width: 10px; +@height: @width + 10px; + +#header { + width: @width; + height: @height; +} +\`\`\` + +\`\`\`html + + + + + + + Document + + + + + +\`\`\` + +\`\`\`js +console.log("foo"); +\`\`\` +', + date: [], + mergedKeys: [ + "tags", + ], + page: [ + "src", + "data", + "asset", + ], + paginate: "paginate", + search: [], + tags: "Array(0)", + url: "/", + }, + src: { + asset: false, + ext: ".md", + path: "/index", + remote: undefined, + }, + }, +] +`; + +snapshot[`Prism plugin 1`] = ` +{ + formats: [ + { + engines: 0, + ext: ".page.toml", + loader: [AsyncFunction: toml], + pageType: "page", + }, + { + engines: 1, + ext: ".page.ts", + loader: [AsyncFunction: module], + pageType: "page", + }, + { + engines: 1, + ext: ".page.js", + loader: [AsyncFunction: module], + pageType: "page", + }, + { + engines: 0, + ext: ".page.jsonc", + loader: [AsyncFunction: json], + pageType: "page", + }, + { + engines: 0, + ext: ".page.json", + loader: [AsyncFunction: json], + pageType: "page", + }, + { + dataLoader: [AsyncFunction: json], + engines: 0, + ext: ".json", + loader: [AsyncFunction: json], + }, + { + dataLoader: [AsyncFunction: json], + engines: 0, + ext: ".jsonc", + loader: [AsyncFunction: json], + }, + { + engines: 1, + ext: ".md", + loader: [AsyncFunction: text], + pageType: "page", + }, + { + engines: 1, + ext: ".markdown", + loader: [AsyncFunction: text], + pageType: "page", + }, + { + dataLoader: [AsyncFunction: module], + engines: 1, + ext: ".js", + loader: [AsyncFunction: module], + }, + { + dataLoader: [AsyncFunction: module], + engines: 1, + ext: ".ts", + loader: [AsyncFunction: module], + }, + { + engines: 1, + ext: ".vento", + loader: [AsyncFunction: text], + pageType: "page", + }, + { + engines: 1, + ext: ".vto", + loader: [AsyncFunction: text], + pageType: "page", + }, + { + dataLoader: [AsyncFunction: toml], + engines: 0, + ext: ".toml", + loader: [AsyncFunction: toml], + }, + { + dataLoader: [AsyncFunction: yaml], + engines: 0, + ext: ".yaml", + loader: [AsyncFunction: yaml], + pageType: "page", + }, + { + dataLoader: [AsyncFunction: yaml], + engines: 0, + ext: ".yml", + loader: [AsyncFunction: yaml], + pageType: "page", + }, + ], + src: [ + "/", + "/index.md", + ], +} +`; + snapshot[`Prism plugin 2`] = `[]`; snapshot[`Prism plugin 3`] = ` [ + { + content: "code[class*=language-],pre[class*=language-]{color:#fff;background:0 0;text-shadow:0 -.1em .2em #000;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}@media print{code[class*=language-],pre[class*=language-]{text-shadow:none}}:not(pre)>code[class*=language-],pre[class*=language-]{background:#4c3f33}pre[class*=language-]{padding:1em;margin:.5em 0;overflow:auto;border:.3em solid #7a6651;border-radius:.5em;box-shadow:1px 1px .5em #000 inset}:not(pre)>code[class*=language-]{padding:.15em .2em .05em;border-radius:.3em;border:.13em solid #7a6651;box-shadow:1px 1px .3em -.1em #000 inset;white-space:normal}.token.cdata,.token.comment,.token.doctype,.token.prolog{color:#997f66}.token.punctuation{opacity:.7}.token.namespace{opacity:.7}.token.boolean,.token.constant,.token.number,.token.property,.token.symbol,.token.tag{color:#d1939e}.token.attr-name,.token.builtin,.token.char,.token.inserted,.token.selector,.token.string{color:#bce051}.language-css .token.string,.style .token.string,.token.entity,.token.operator,.token.url,.token.variable{color:#f4b73d}.token.atrule,.token.attr-value,.token.keyword{color:#d1939e}.token.important,.token.regex{color:#e90}.token.bold,.token.important{font-weight:700}.token.italic{font-style:italic}.token.entity{cursor:help}.token.deleted{color:red}", + data: { + basename: "styles", + page: [ + "src", + "data", + "asset", + ], + url: "/styles.css", + }, + src: { + asset: true, + ext: "", + path: "", + remote: undefined, + }, + }, { content: '

Hello world

diff --git a/tests/code_highlight.test.ts b/tests/code_highlight.test.ts index 17374c02..dd116cfe 100644 --- a/tests/code_highlight.test.ts +++ b/tests/code_highlight.test.ts @@ -1,7 +1,7 @@ import { assertSiteSnapshot, build, getSite } from "./utils.ts"; import codeHighlight from "../plugins/code_highlight.ts"; -Deno.test("code_hightlight plugin", async (t) => { +Deno.test("code_hightlight plugin with path", async (t) => { const site = getSite({ src: "code_highlight", }); @@ -16,3 +16,19 @@ Deno.test("code_hightlight plugin", async (t) => { await build(site); await assertSiteSnapshot(t, site); }); + +Deno.test("code_hightlight plugin", async (t) => { + const site = getSite({ + src: "code_highlight", + }); + + site.use(codeHighlight({ + theme: { + name: "a11y-dark", + cssFile: "styles.css", + }, + })); + + await build(site); + await assertSiteSnapshot(t, site); +}); diff --git a/tests/prism.test.ts b/tests/prism.test.ts index a1477885..b4ca60e3 100644 --- a/tests/prism.test.ts +++ b/tests/prism.test.ts @@ -2,7 +2,7 @@ import { assertSiteSnapshot, build, getSite } from "./utils.ts"; import prism from "../plugins/prism.ts"; import "npm:prismjs@1.29.0/components/prism-less.js"; -Deno.test("Prism plugin", async (t) => { +Deno.test("Prism plugin with path", async (t) => { const site = getSite({ src: "prism", }); @@ -17,3 +17,19 @@ Deno.test("Prism plugin", async (t) => { await build(site); await assertSiteSnapshot(t, site); }); + +Deno.test("Prism plugin", async (t) => { + const site = getSite({ + src: "prism", + }); + + site.use(prism({ + theme: { + name: "dark", + cssFile: "styles.css", + }, + })); + + await build(site); + await assertSiteSnapshot(t, site); +});