Skip to content

Commit

Permalink
feat: + gadget-Shiki
Browse files Browse the repository at this point in the history
  • Loading branch information
dragon-fish committed Dec 5, 2024
1 parent 0c5222b commit 4285129
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 1 deletion.
38 changes: 38 additions & 0 deletions src/gadgets/Shiki/MediaWiki:Gadget-Shiki.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/* base styles */
pre.shiki {
position: relative;
font-family: "JetBrainsMono Nerd Font", "JetBrains Mono NF", "JetBrains Mono", "Fira Code", "Consolas", "Monaco", "Andale Mono", "Ubuntu Mono", monospace;
font-size: 1rem;
padding: 1em;
border-radius: 0.5em;
white-space: pre;
overflow-x: auto;
}

/* lang badge */
pre.shiki > .shiki-lang-badge {
position: absolute;
right: 0.5em;
top: 0.5em;
font-size: 10px;
border-radius: 99vw;
background: #000;
padding: 0.2em 0.5em;
user-select: none;
pointer-events: none;
}

/* line number */
pre.shiki.line-number .shiki-code {
counter-reset: step;
counter-increment: step calc(var(--start, 1) - 1);
}
pre.shiki.line-number .shiki-code .line::before {
content: counter(step);
counter-increment: step;
width: 2em;
margin-right: 1em;
display: inline-block;
text-align: right;
color: rgba(115, 138, 148, 0.4);
}
120 changes: 120 additions & 0 deletions src/gadgets/Shiki/MediaWiki:Gadget-Shiki.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* MediaWiki Gadget Shiki.js Code Highlighter
* @author Dragon-Fish <dragon-fish@qq.com>
* @license MIT
*/

/* eslint-disable prefer-arrow-functions/prefer-arrow-functions */
/* eslint-disable no-use-before-define */

"use strict";

(async () => {
const SHIKI_CDN = "https://esm.sh/shiki@1.24.0";
const TARGET_ELEMENTS = document.querySelectorAll(["pre.highlight", "pre.hljs", "pre.prettyprint", "pre.mw-code", "pre[lang]", "code[lang]", "pre[data-lang]", "code[data-lang]"]);

const shiki = await import(SHIKI_CDN);
TARGET_ELEMENTS.forEach((el) => {
renderBlock(shiki, el);
});
mw.hook("npm:shiki").fire(shiki);

const getLangFromContentModel = () => {
const nsNumber = mw.config.get("wgNamespaceNumber");
const pageName = mw.config.get("wgPageName");
const contentModel = mw.config.get("wgPageContentModel", "").toLowerCase();
if (pageName.endsWith(".js") || contentModel === "javascript") {
return "javascript";
} else if (pageName.endsWith(".css") || contentModel === "css") {
return "css";
} else if (
// Lua
(nsNumber === 828 || ["scribunto", "lua"].includes(contentModel))
&& !pageName.endsWith("/doc")
) {
return "lua";
}
};

/**
* @param {HTMLElement} el
*/
const getLangFromElement = (el) => {
const lang = el.getAttribute("lang") || el.dataset.lang || Array.from(el.classList).find((c) => c.startsWith("language-") || c.startsWith("lang-"));
if (lang) {
return lang.includes("-") ? lang.split("-")[1] : lang;
}
return "";
};

/**
* @param {import('shiki')} shiki
* @param {HTMLElement} el
* @returns {Promise<HTMLElement | null>}
*/
async function renderBlock(shiki, el) {
if (el.classList.contains("shiki") || !!el.dataset.shikiRendered) {
return Promise.resolve(null);
}
const lang = getLangFromElement(el) || getLangFromContentModel();
if (!lang) {
return Promise.resolve(null);
}

const langInfo = shiki.bundledLanguagesInfo.find((i) => i.aliases?.includes(lang) || i.id === lang || i.name === lang);
const langLabel = (() => {
if (!langInfo) {
return lang;
}
return [langInfo.aliases?.[0], langInfo.name, langInfo.id, lang].filter(Boolean).sort((a, b) => a.length - b.length)[0];
})();
console.info("[SHIKI]", "Rendering", el, lang, langInfo);

const lineFrom = el.dataset.lineFrom || el.dataset.from || "1";

const renderedEl = await shiki
.codeToHtml(el.innerText.trimEnd(), {
lang,
theme: "one-dark-pro",
transformers: [
{
pre: (node) => {
node.properties.class += ` lang-${langLabel}`;
node.properties.style += ";";
node.properties.style += `padding-right: ${(10 * langLabel.length + 12).toFixed()}px;`;
if (lineFrom) {
node.properties.class += " line-number";
}
},
code: (node) => {
node.properties.class += " shiki-code";
node.tagName = "div";
node.properties.style += `;--start: ${+lineFrom};`;
},
line: (node, line) => {
node.properties["data-line-number-raw"] = line;
},
postprocess: (html) => {
if (langLabel) {
return html.replace("</pre>", `<span class="shiki-lang-badge">${langLabel}</span></pre>`);
}
},
},
],
})
.then((html) => {
el.style.display = "none";
el.dataset.shikiRendered = "1";
const wrapper = document.createElement("div");
wrapper.innerHTML = html;
const pre = wrapper.querySelector("pre");
el.insertAdjacentElement("afterend", pre);
return pre;
})
.catch((e) => {
console.error("[SHIKI] Render failed", el, e);
return null;
});
return renderedEl;
};
})();
33 changes: 33 additions & 0 deletions src/gadgets/Shiki/definition.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
ResourceLoader: true

hidden: false

default: false

supportsUrlLoad: false

targets: []

skins: []

actions: []

type: general

package: false

rights: []

peers: []

dependencies:

_sites:
- zh
- commons

_section: browsing

_files:
- MediaWiki:Gadget-Shiki.js
- MediaWiki:Gadget-Shiki.css
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
// <pre>
"use strict";
$(() => {
if (mw.user.options.get("gadget-prism")) {
if (mw.user.options.get("gadget-prism") || mw.user.options.get("gadget-Shiki")) {
return;
}
if (mw.config.get("wgPageName").match(/\.js$/)) {
Expand Down

0 comments on commit 4285129

Please sign in to comment.