diff --git a/.gitignore b/.gitignore index 5b2ce611..3bed9a90 100644 --- a/.gitignore +++ b/.gitignore @@ -4,11 +4,16 @@ Thumbs.db # Generated files *.html +!examples/*.html api plugins +components.json +file-sizes.json +_redirects # Cache .cache -# Local Netlify folder +# Local Netlify folders and files .netlify +deno.lock diff --git a/README.json b/README.json index d0efc02c..4abebb76 100644 --- a/README.json +++ b/README.json @@ -1,4 +1,4 @@ { "layout": "home.njk", - "resources": ["plugins/keep-markup/prism-keep-markup.js", "https://dev.prismjs.com/components/prism-bash.js"] + "resources": ["/plugins/keep-markup.js { type=module }", "/languages/bash.js { type=module }"] } diff --git a/README.md b/README.md index 162e922c..c53797ea 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,7 @@ Prism is used on several websites, small and large. Some of them are: # Examples The Prism source, highlighted with Prism (don’t you just love how meta this is?): -
+ This page’s CSS code, highlighted with Prism: @@ -56,7 +56,7 @@ If you’re still not sold, you can [view more examples](examples.html) or [try - Highlights embedded languages (e.g. CSS inside HTML, JavaScript inside HTML). - Highlights inline code as well, not just code blocks. - It doesn’t force you to use any Prism-specific markup, not even a Prism-specific class name, only standard markup you should be using anyway. So, you can just try it for a while, remove it if you don’t like it and leave no traces behind. -- Highlight specific lines and/or line ranges (requires [plugin](plugins/line-highlight/)). +- Highlight specific lines and/or line ranges (requires [plugin](plugins/line-highlight/index.html)). - Show invisible characters like tabs, line breaks etc (requires [plugin](plugins/show-invisibles/)). - Autolink URLs and emails, use Markdown links in comments (requires [plugin](plugins/autolinker/)). @@ -221,7 +221,7 @@ This is the list of all {{ languages | length }} languages currently supported b{{ alias }}{{ ", " if not loop.last }}
{%- endfor %}
diff --git a/_build/copy-plugins.mjs b/_build/copy-plugins.mjs
index 2e45be16..e175f8ec 100644
--- a/_build/copy-plugins.mjs
+++ b/_build/copy-plugins.mjs
@@ -27,9 +27,8 @@ async function copy () {
continue;
}
- let name = path.parse(file.name).name;
- // Copy only the README.md and demo.* files
- if (["README", "demo"].includes(name)) {
+ let filename = path.parse(file.name).base;
+ if (["README.md", "demo.md"].includes(filename)) {
await fs.copyFile(path.join(source, file.name), path.join(dest, file.name));
}
}
diff --git a/_build/eleventy.js b/_build/eleventy.js
index 83e7a33f..85d81cba 100644
--- a/_build/eleventy.js
+++ b/_build/eleventy.js
@@ -4,12 +4,14 @@ import markdownItDeflist from "markdown-it-deflist";
import pluginTOC from "eleventy-plugin-toc";
import * as filters from "./filters.js";
-import components from "../node_modules/prismjs/src/components.json" with { type: "json" };
+import components from "../components.json" with { type: "json" };
+import file_sizes from "../file-sizes.json" with { type: "json" };
/** @param {import("@11ty/eleventy").UserConfig} config */
export default config => {
let data = {
components,
+ file_sizes,
layout: "page.njk",
theme_switcher: true,
toc: true,
diff --git a/_build/postinstall.mjs b/_build/postinstall.mjs
new file mode 100644
index 00000000..b0933cd1
--- /dev/null
+++ b/_build/postinstall.mjs
@@ -0,0 +1,96 @@
+import fs from "fs/promises";
+import path from "path";
+import { fileURLToPath } from "url";
+import { execSync } from "child_process";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+
+const root = path.resolve(__dirname, "../node_modules");
+const prismPath = path.join(root, "prismjs");
+
+let sourcePath, destPath;
+
+// --- Cloning & Installing Prism ---
+console.log("[postinstall] Cloning Prism...");
+// Ensure we work with a fresh copy
+await fs.rm(prismPath, { recursive: true, force: true });
+execSync("git clone https://github.com/PrismJS/prism.git prismjs", {
+ cwd: root,
+ stdio: "inherit",
+});
+
+console.log("[postinstall] Installing Prism dependencies...");
+execSync("npm install", {
+ cwd: prismPath,
+ stdio: "inherit",
+});
+
+console.log("[postinstall] Building Prism...");
+execSync("npm run build", {
+ cwd: prismPath,
+ stdio: "inherit",
+});
+
+// --- Working with plugins ---
+sourcePath = path.join(prismPath, "src/plugins");
+destPath = path.resolve(__dirname, "../plugins");
+
+async function copy () {
+ // We need { recursive: true } so the script doesn't fail if the folder already exists
+ await fs.mkdir(destPath, { recursive: true });
+
+ let plugins = await fs.readdir(sourcePath, { withFileTypes: true });
+ for (let plugin of plugins) {
+ if (!plugin.isDirectory()) {
+ continue;
+ }
+
+ let source = path.join(sourcePath, plugin.name);
+ let dest = path.join(destPath, plugin.name);
+ await fs.mkdir(dest, { recursive: true });
+
+ let files = await fs.readdir(source, { withFileTypes: true });
+ for (let file of files) {
+ if (!file.isFile()) {
+ continue;
+ }
+
+ let filename = path.parse(file.name).base;
+ if (["README.md", "demo.md"].includes(filename)) {
+ await fs.copyFile(path.join(source, file.name), path.join(dest, file.name));
+ }
+ }
+ }
+}
+
+console.log("[postinstall] Copying Prism plugins docs...");
+try {
+ await copy();
+}
+catch (error) {
+ console.error(`[postinstall] Failed to copy Prism plugins docs: ${error.message}`);
+}
+
+// Create plugins.json in the plugins folder with global data
+console.log("[postinstall] Creating plugins.json...");
+let json = {
+ permalink: "{{ page.filePathStem.replace('README', '/index') }}.html",
+ tags: ["plugin"],
+};
+
+await fs.writeFile(path.join(destPath, "plugins.json"), JSON.stringify(json, null, "\t"));
+
+// --- Copying other files (components.json, file-sizes.json, etc.) ---
+sourcePath = path.join(prismPath, "dist");
+destPath = path.resolve(__dirname, "..");
+
+let filenames = ["components.json", "file-sizes.json"];
+for (let file of filenames) {
+ console.log(`[postinstall] Copying ${file}...`);
+ try {
+ await fs.copyFile(path.join(sourcePath, file), path.join(destPath, file));
+ }
+ catch (error) {
+ console.error(`[postinstall] Failed to copy ${file}: ${error.message}`);
+ }
+}
diff --git a/_data/eleventyComputed.js b/_data/eleventyComputed.js
index d9c102f8..1c8cfd26 100644
--- a/_data/eleventyComputed.js
+++ b/_data/eleventyComputed.js
@@ -13,7 +13,7 @@ export default {
for (let id in languages) {
let ret = [id];
- let alias = languages[id].alias;
+ let alias = Object.keys(languages[id].aliasTitles ?? {});
if (alias) {
ret = ret.concat(Array.isArray(alias) ? alias : [alias]);
}
@@ -59,19 +59,12 @@ export default {
}
// We are working with plugin resources
- ret.push(`./prism-${id}.js { type="module" }`);
+ ret.push(`/plugins/${id}.js { type="module" }`);
if (!data.noCSS) {
- ret.push(`./prism-${id}.css`);
+ ret.push(`/plugins/${id}.css`);
}
return ret;
},
- files_sizes (data) {
- let ret = {};
- for (let file of data.tree) {
- ret[file.path] = file.size;
- }
- return ret;
- },
};
diff --git a/_data/tree.js b/_data/tree.js
deleted file mode 100644
index 4adb7264..00000000
--- a/_data/tree.js
+++ /dev/null
@@ -1,14 +0,0 @@
-import Fetch from "@11ty/eleventy-fetch";
-
-export default async () => {
- let repo = await Fetch(
- // TODO: Replace with "https://api.github.com/repos/PrismJS/prism/git/trees/main?recursive=1" when v2 is launched
- "https://api.github.com/repos/PrismJS/prism/git/trees/master?recursive=1",
- {
- duration: "1d",
- type: "json",
- },
- );
-
- return repo.tree;
-};
diff --git a/_layouts/page.njk b/_layouts/page.njk
index 43234c0f..e36f9b5a 100644
--- a/_layouts/page.njk
+++ b/_layouts/page.njk
@@ -14,7 +14,7 @@
-
+
@@ -89,7 +89,8 @@
-
+
+
{% if theme_switcher -%}
{%- endif %}
diff --git a/_redirects b/_redirects
deleted file mode 100644
index 80f349f9..00000000
--- a/_redirects
+++ /dev/null
@@ -1,12 +0,0 @@
-# Do not redirect
-/plugins/:plugin/index.html /plugins/:plugin/index.html 200
-/plugins/:plugin/demo.html /plugins/:plugin/demo.html 200
-/plugins/:plugin/demo.js /plugins/:plugin/demo.js 200
-/plugins/:plugin/demo.css /plugins/:plugin/demo.css 200
-
-# Components: languages, themes, plugins, etc.
-/components/* https://dev.prismjs.com/components/:splat 200
-/plugins/:plugin/:file https://dev.prismjs.com/plugins/:plugin/:file 200
-
-# Make the autoloader plugin work
-/plugins/:plugin/components/* https://dev.prismjs.com/components/:splat 200
diff --git a/_redirects.njk b/_redirects.njk
new file mode 100644
index 00000000..2a4deaa2
--- /dev/null
+++ b/_redirects.njk
@@ -0,0 +1,22 @@
+---
+permalink: _redirects
+layout: null
+eleventyExcludeFromCollections: true
+---
+
+# Themes and languages
+/themes/:file https://v2.dev.prismjs.com/dist/themes/:file 301
+/languages/:file https://v2.dev.prismjs.com/dist/languages/:file 301
+
+# Plugins
+{% for plugin in collections.plugin -%}
+{%- set id = plugin.data.id -%}
+/plugins/{{ id }}.js https://v2.dev.prismjs.com/dist/plugins/{{ id }}.js 301
+/plugins/{{ id }}/{{ id }}.js https://v2.dev.prismjs.com/src/plugins/{{ id }}/{{ id }}.js 301
+{% if not meta.noCSS -%}
+/plugins/{{ id }}.css https://v2.dev.prismjs.com/dist/plugins/{{ id }}.css 301
+/plugins/{{ id }}/{{ id }}.css https://v2.dev.prismjs.com/src/plugins/{{ id }}/{{ id }}.css 301
+{% endif -%}
+{% endfor -%}
+/plugins/:plugin/demo.js https://v2.dev.prismjs.com/dist/plugins/:plugin/demo.js 301
+/plugins/:plugin/demo.css https://v2.dev.prismjs.com/dist/plugins/:plugin/demo.css 301
diff --git a/assets/download.js b/assets/download.js
index 0249bced..2984aacd 100644
--- a/assets/download.js
+++ b/assets/download.js
@@ -4,14 +4,11 @@
import { getFileContents, toArray } from "./util.js";
-let components = await (await fetch("https://dev.prismjs.com/components.json")).json();
-
-let treeURL = "https://api.github.com/repos/PrismJS/prism/git/trees/master?recursive=1";
-let tree = (await (await fetch(treeURL)).json()).tree;
+let components = await (await fetch("/components.json")).json();
+let fileSizes = await (await fetch("/file-sizes.json")).json();
let cache = {};
let form = document.querySelector("form");
-let minified = true;
let dependencies = {};
let timerId = 0;
@@ -30,7 +27,7 @@ if (hstr) {
}
}
if (category === "themes" && ids.length) {
- let themeInput = document.querySelector(`#theme input[value="${ ids[0] }"]`);
+ let themeInput = document.querySelector(`#theme input[value="${ids[0]}"]`);
if (themeInput) {
themeInput.checked = true;
themeInput.dispatchEvent(new Event("change"));
@@ -63,7 +60,9 @@ for (let category in components) {
let all = components[category];
all.meta.section = form.querySelector(`#category-${category}`);
- all.meta.section.querySelector(`[name="check-all-${category}"]`)?.addEventListener("change", ({ target }) => {
+ all.meta.section.querySelector(`[name="check-all-${category}"]`)?.addEventListener("change", ({
+ target,
+ }) => {
all.meta.section.querySelectorAll(`input[name="download-${category}"]`).forEach(input => {
all[input.value].enabled = input.checked = target.checked;
});
@@ -83,8 +82,10 @@ for (let category in components) {
let option = all[id].option || all.meta.option;
switch (option) {
- case "mandatory": disabled = true; // fallthrough
- case "default": checked = true;
+ case "mandatory":
+ disabled = true; // fallthrough
+ case "default":
+ checked = true;
}
if (category === "themes" && storedTheme) {
@@ -94,9 +95,15 @@ for (let category in components) {
input.checked = checked;
input.disabled = disabled;
- let filepath = all.meta.path.replace(/\{id\}/g, id);
+ let filepath = all.meta.path;
+ if (category === "plugins") {
+ // When built, the plugins live in one directory called `plugins/`, not every plugin in its own directory
+ filepath = filepath.replace("{id}/", "");
+ }
+
+ filepath = filepath.replace(/\{id\}/g, id);
- let info = all[id] = {
+ let info = (all[id] = {
noCSS: all[id].noCSS || all.meta.noCSS,
noJS: all[id].noJS || all.meta.noJS,
enabled: checked,
@@ -104,33 +111,21 @@ for (let category in components) {
after: toArray(all[id].after),
modify: toArray(all[id].modify),
files: {
- minified: {
- paths: [],
- size: 0
- },
- dev: {
- paths: [],
- size: 0
- }
- }
- };
+ paths: [],
+ size: 0,
+ },
+ });
info.require.forEach(v => {
dependencies[v] = (dependencies[v] || []).concat(id);
});
if (!all[id].noJS && !/\.css$/.test(filepath)) {
- info.files.minified.paths.push(filepath.replace(/(\.js)?$/, ".min.js"));
- info.files.dev.paths.push(filepath.replace(/(\.js)?$/, ".js"));
+ info.files.paths.push(filepath.replace(/(\.js)?$/, ".js"));
}
-
if ((!all[id].noCSS && !/\.js$/.test(filepath)) || /\.css$/.test(filepath)) {
- let cssFile = filepath.replace(/(\.css)?$/, ".css");
- let minCSSFile = cssFile.replace(/(?:\.css)$/, ".min.css");
-
- info.files.minified.paths.push(minCSSFile);
- info.files.dev.paths.push(cssFile);
+ info.files.paths.push(filepath.replace(/(\.css)?$/, ".css"));
}
input.addEventListener("change", ({ target }) => {
@@ -146,7 +141,8 @@ for (let category in components) {
});
}
- if (dependencies[id] && !target.checked) { // It’s required by others
+ if (dependencies[id] && !target.checked) {
+ // It’s required by others
dependencies[id].forEach(dependent => {
let input = form.querySelector(`label[data-id="${dependent}"] > input`);
input.checked = false;
@@ -170,24 +166,20 @@ for (let category in components) {
}
}
-form.elements.compression[0].onclick =
- form.elements.compression[1].onclick = function () {
- minified = !!+this.value;
-
- getFilesSizes();
- };
-
getFilesSizes();
-function getFileSize(filepath) {
- for (let i = 0, l = tree.length; i < l; i++) {
- if (tree[i].path === filepath) {
- return tree[i].size;
- }
+function getFileSize (category, id, filepath) {
+ let type = filepath.match(/\.(css|js)$/)[1];
+
+ if (category === "core") {
+ return fileSizes.core.js;
+ }
+ else {
+ return fileSizes[category][id]?.[type] ?? 0;
}
}
-function getFilesSizes() {
+function getFilesSizes () {
for (let category in components) {
let all = components[category];
@@ -196,14 +188,14 @@ function getFilesSizes() {
continue;
}
- let distro = all[id].files[minified ? "minified" : "dev"];
+ let distro = all[id].files;
let files = distro.paths;
files.forEach(filepath => {
- let file = cache[filepath] = cache[filepath] || {};
+ let file = (cache[filepath] = cache[filepath] || {});
if (!file.size) {
- let size = getFileSize(filepath);
+ let size = getFileSize(category, id, filepath);
if (size) {
file.size = size;
distro.size += file.size;
@@ -219,13 +211,14 @@ function getFilesSizes() {
}
}
-function prettySize(size) {
- return Math.round(100 * size / 1024) / 100 + "KB";
+function prettySize (size) {
+ return Math.round((100 * size) / 1024) / 100 + "KB";
}
-function update(updatedCategory, updatedId) {
+function update (updatedCategory, updatedId) {
// Update total size
- let total = { js: 0, css: 0 }; let updated = { js: 0, css: 0 };
+ let total = { js: 0, css: 0 };
+ let updated = { js: 0, css: 0 };
for (let category in components) {
let all = components[category];
@@ -235,7 +228,7 @@ function update(updatedCategory, updatedId) {
let info = all[id];
if (info.enabled || id === updatedId) {
- let distro = info.files[minified ? "minified" : "dev"];
+ let distro = info.files;
distro.paths.forEach(path => {
if (cache[path]) {
@@ -245,9 +238,11 @@ function update(updatedCategory, updatedId) {
let size = file.size || 0;
if (info.enabled) {
-
if (!file.contentsPromise) {
- file.contentsPromise = getFileContents("https://dev.prismjs.com/" + path);
+ // FIXME: Remove “v2” when Prism v2 is released
+ file.contentsPromise = getFileContents(
+ "https://v2.dev.prismjs.com/dist/" + path,
+ );
}
total[type] += size;
@@ -285,36 +280,37 @@ function update(updatedCategory, updatedId) {
Object.assign(form.querySelector(`label[data-id="${updatedId}"] .filesize`), {
textContent: prettySize(updated.all),
- title: (updated.js ? Math.round(100 * updated.js / updated.all) + "% JavaScript" : "") +
+ title:
+ (updated.js ? Math.round((100 * updated.js) / updated.all) + "% JavaScript" : "") +
(updated.js && updated.css ? " + " : "") +
- (updated.css ? Math.round(100 * updated.css / updated.all) + "% CSS" : "")
+ (updated.css ? Math.round((100 * updated.css) / updated.all) + "% CSS" : ""),
});
}
form.querySelector("#filesize").textContent = prettySize(total.all);
Object.assign(form.querySelector("#percent-js"), {
- textContent: Math.round(100 * total.js / total.all) + "%",
- title: prettySize(total.js)
+ textContent: Math.round((100 * total.js) / total.all) + "%",
+ title: prettySize(total.js),
});
Object.assign(form.querySelector("#percent-css"), {
- textContent: Math.round(100 * total.css / total.all) + "%",
- title: prettySize(total.css)
+ textContent: Math.round((100 * total.css) / total.all) + "%",
+ title: prettySize(total.css),
});
delayedGenerateCode();
}
// "debounce" multiple rapid requests to generate and highlight code
-function delayedGenerateCode() {
+function delayedGenerateCode () {
if (timerId !== 0) {
clearTimeout(timerId);
}
timerId = setTimeout(generateCode, 500);
}
-async function generateCode() {
+async function generateCode () {
/** @type {CodePromiseInfo[]} */
let promises = [];
let redownload = {};
@@ -331,7 +327,7 @@ async function generateCode() {
redownload[category] = redownload[category] || [];
redownload[category].push(id);
}
- info.files[minified ? "minified" : "dev"].paths.forEach(path => {
+ info.files.paths.forEach(path => {
if (cache[path]) {
let type = path.match(/\.(\w+)$/)[1];
@@ -340,7 +336,7 @@ async function generateCode() {
id: id,
category: category,
path: path,
- type: type
+ type: type,
});
}
});
@@ -381,11 +377,14 @@ async function generateCode() {
let newCode = Object.assign(document.createElement("code"), {
className: codeElement.className,
- textContent: text
+ textContent: text,
});
- Prism.highlightElement(newCode, false, () => {
- codeElement.replaceWith(newCode);
+ Prism.highlightElement(newCode, {
+ async: false,
+ callback: () => {
+ codeElement.replaceWith(newCode);
+ },
});
form.querySelector(`#download-${type} .download-button`).onclick = () => {
@@ -407,7 +406,7 @@ async function generateCode() {
* @property {string} path
* @property {string} type
*/
-function buildCode(promises) {
+function buildCode (promises) {
// sort the promises
/** @type {CodePromiseInfo[]} */
@@ -418,7 +417,8 @@ function buildCode(promises) {
promises.forEach(p => {
if (p.category === "core" || p.category === "themes") {
finalPromises.push(p);
- } else {
+ }
+ else {
let infos = toSortMap[p.id];
if (!infos) {
toSortMap[p.id] = infos = [];
@@ -428,12 +428,14 @@ function buildCode(promises) {
});
// this assumes that the ids in `toSortMap` are complete under transitive requirements
- getLoader(components, Object.keys(toSortMap)).getIds().forEach(id => {
- if (!toSortMap[id]) {
- console.error(`${ id } not found.`);
- }
- finalPromises.push.apply(finalPromises, toSortMap[id]);
- });
+ getLoader(components, Object.keys(toSortMap))
+ .getIds()
+ .forEach(id => {
+ if (!toSortMap[id]) {
+ console.error(`${id} not found.`);
+ }
+ finalPromises.push.apply(finalPromises, toSortMap[id]);
+ });
promises = finalPromises;
// build
@@ -446,18 +448,22 @@ function buildCode(promises) {
if (i < l) {
let p = promises[i];
p.contentsPromise.then(function (contents) {
- code[p.type] += contents + (p.type === "js" && !/;\s*$/.test(contents) ? ";" : "") + "\n";
+ code[p.type] +=
+ contents + (p.type === "js" && !/;\s*$/.test(contents) ? ";" : "") + "\n";
i++;
f(resolve);
});
p.contentsPromise["catch"](function () {
- errors.push(Object.assign(document.createElement("p"), {
- textContent: `An error occurred while fetching the file "${ p.path }".`
- }));
+ errors.push(
+ Object.assign(document.createElement("p"), {
+ textContent: `An error occurred while fetching the file "${p.path}".`,
+ }),
+ );
i++;
f(resolve);
});
- } else {
+ }
+ else {
resolve({ code: code, errors: errors });
}
};
@@ -468,7 +474,7 @@ function buildCode(promises) {
/**
* @returns {PromiseTo use this language, use one of the following classes:
"; - header += `"language-${ id }""language-${id}""language-${ alias }""language-${alias}"To use this language, use the class "language-${ id }".
To use this language, use the class "language-${id}".
${ text }`;
+ return `${text}`;
}
let deps = [];
if (language.require) {
@@ -65,13 +65,13 @@ function buildContentsHeader (id) {
header += `Dependencies:`;
header += " This component";
if (deps.length === 1) {
- header += ` ${ deps[0] }.`;
+ header += ` ${deps[0]}.`;
}
else {
header += ":";
header += "