From 55d07b4fdab93c6cd8ab2a4be373bde1019b2332 Mon Sep 17 00:00:00 2001 From: OnkarRuikar <87750369+OnkarRuikar@users.noreply.github.com> Date: Thu, 26 May 2022 13:28:47 +0530 Subject: [PATCH] Enhance code example blocks --- build/syntax-highlight.js | 103 +++++++++++++++++++++++++++++--- client/src/document/index.scss | 35 +++++++++++ client/src/ui/_vars.scss | 6 ++ client/src/ui/base/_themes.scss | 6 ++ 4 files changed, 143 insertions(+), 7 deletions(-) diff --git a/build/syntax-highlight.js b/build/syntax-highlight.js index 5a9ede018a83..a06846261097 100644 --- a/build/syntax-highlight.js +++ b/build/syntax-highlight.js @@ -58,7 +58,7 @@ function syntaxHighlight($, doc) { // match. The wildcard would technically match `
`
   // too. But within the loop, we do a more careful regex on the class name
   // and only proceed if it's something sensible we can use in Prism.
-  $("pre[class*=brush]").each((_, element) => {
+  $("pre[class*=brush]").each((index, element) => {
     // The language is whatever string comes after the `brush(:)`
     // portion of the class name.
     const $pre = $(element).wrapAll("
"); @@ -68,24 +68,113 @@ function syntaxHighlight($, doc) { if (!match) { return; } - const name = ALIASES.get(match[1]) || match[1]; - if (IGNORE.has(name)) { + const language = ALIASES.get(match[1]) || match[1]; + if (IGNORE.has(language)) { // Seems to exist a couple of these in our docs. Just bail. return; } - const grammar = Prism.languages[name]; + const grammar = Prism.languages[language]; if (!grammar) { console.warn( - `Unable to find a Prism grammar for '${name}' found in ${doc.mdn_url}` + `Unable to find a Prism grammar for '${language}' found in ${doc.mdn_url}` ); return; // bail! } + + const addLineNumbers = className.match(/linenumbers/); const code = $pre.text(); - const html = Prism.highlight(code, grammar, name); - const $code = $("").html(html); + let html = ""; + + if (!addLineNumbers) { + html = Prism.highlight(code, grammar, language); + } else { + let highlights = className.match(/(?<=-)\d+/g); + highlights = highlights ? highlights : []; + + const env = { + code: code, + grammar: grammar, + language: language, + codeBlockNo: index + 1, + highlights: highlights, + }; + + // use lower level APIs for finer control + env.tokens = Prism.tokenize(code, grammar); + Prism.plugins.enhance.addLines(env); + html = Prism.Token.stringify(Prism.util.encode(env.tokens), language); + } + const $code = $("").html(html); $pre.empty().append($code); }); } +// plugin to add line numbers, highlighting, and anchors +Prism.plugins.enhance = { + createLineToken(children, env, lineNo) { + const line = new Prism.Token("line", children); + line.codeBlockNo = env.codeBlockNo; + line.lineNo = lineNo; + + if (env.highlights.includes(String(lineNo))) { + line.alias = "highlight"; + } + return line; + }, + + createLineNumberToken(codeBlockNo, lineNo) { + const id = `E${codeBlockNo}L${lineNo}`; + const anchor = `${lineNo}`; + return new Prism.Token("lineno", anchor); + }, + + addLines(env) { + if (Array.isArray(env.tokens) && env.tokens.length > 0) { + const newList = new Array(); + let lineNo = 1; + let children = [this.createLineNumberToken(env.codeBlockNo, lineNo)]; + + // separate the tokens into lines + env.tokens.forEach((token) => { + if (typeof token === "string" && token.includes("\n")) { + let part = ""; + while (token !== "") { + const position = token.indexOf("\n"); + if (position >= 0) { + part = token.substring(0, position); + token = token.substring(position + 1); + } else { + part = token; + token = ""; + } + + children.push(part ? part : "\u200b"); + if (position >= 0) { + newList.push(this.createLineToken(children, env, lineNo)); + lineNo++; + children = [this.createLineNumberToken(env.codeBlockNo, lineNo)]; + } + } + } else { + children.push(token); + } + }); + + if (children.length > 1) { + newList.push(this.createLineToken(children, env, lineNo)); + } + + env.tokens = newList; + } + }, +}; + +// add a hook to unescape the anchor tag strings +Prism.hooks.add("wrap", function (token) { + if (token.type === "lineno") { + token.content = token.content.replaceAll("<", "<"); + } +}); + module.exports = { syntaxHighlight }; diff --git a/client/src/document/index.scss b/client/src/document/index.scss index 0178066c6031..8e09dbb58179 100644 --- a/client/src/document/index.scss +++ b/client/src/document/index.scss @@ -580,6 +580,41 @@ pre { .example-bad { padding: 1rem; } + + .line { + display: block; + position: relative; + padding-left: 0.5rem; + margin-left: 0.5rem; + white-space: pre-wrap; + border-left: 1px solid var(--code-line-number); + } + + .lineno { + display: flex; + justify-content: flex-end; + padding-top: 0.35rem; + width: 1rem; + height: 100%; + left: -1.3rem; + position: absolute; + user-select: none; + } + + .highlight { + background-color: var(--code-line-number); + } + + a { + color: var(--code-token-comment) !important; + font-size: var(--type-tiny-font-size); + text-decoration: none !important; + } + + a:hover { + text-decoration: underline !important; + color: var(--code-line-number-hover) !important; + } } .only-in-en-us { diff --git a/client/src/ui/_vars.scss b/client/src/ui/_vars.scss index fc7e4b95ed27..bf678cb66f4f 100644 --- a/client/src/ui/_vars.scss +++ b/client/src/ui/_vars.scss @@ -195,6 +195,9 @@ $mdn-theme-light-code-token-default: $mdn-color-neutral-90; $mdn-theme-light-code-token-selector: $mdn-color-light-theme-violet-60; $mdn-theme-light-code-background-inline: $mdn-color-neutral-light-80; $mdn-theme-light-code-background-block: $mdn-color-neutral-light-80; +$mdn-theme-light-code-line-number: $mdn-color-neutral-10; +$mdn-theme-light-code-line-number-hover: $mdn-color-neutral-70; +$mdn-theme-light-code-line-highlight: $mdn-color-neutral-10; $mdn-theme-dark-text-primary: $mdn-color-white; $mdn-theme-dark-text-secondary: $mdn-color-neutral-20; @@ -237,6 +240,9 @@ $mdn-theme-dark-code-token-default: $mdn-color-white; $mdn-theme-dark-code-token-selector: $mdn-color-dark-theme-violet-30; $mdn-theme-dark-code-background-inline: $mdn-color-neutral-80; $mdn-theme-dark-code-background-block: $mdn-color-neutral-80; +$mdn-theme-dark-code-line-number: $mdn-color-neutral-70; +$mdn-theme-dark-code-line-number-hover: $mdn-color-neutral-10; +$mdn-theme-dark-code-line-highlight: $mdn-color-neutral-70; $screen-sm: 426px; $screen-md: 769px; diff --git a/client/src/ui/base/_themes.scss b/client/src/ui/base/_themes.scss index e3a7c9dffef0..3c7046066bf0 100644 --- a/client/src/ui/base/_themes.scss +++ b/client/src/ui/base/_themes.scss @@ -89,6 +89,9 @@ --code-token-selector: #{$mdn-theme-light-code-token-selector}; --code-background-inline: #{$mdn-theme-light-code-background-inline}; --code-background-block: #{$mdn-theme-light-code-background-block}; + --code-line-number: #{$mdn-theme-light-code-line-number}; + --code-line-number-hover: #{$mdn-theme-light-code-line-number-hover}; + --code-line-highlight: #{$mdn-theme-light-code-line-highlight}; --notecard-link-color: #{$mdn-color-neutral-80}; @@ -256,6 +259,9 @@ --code-token-selector: #{$mdn-theme-dark-code-token-selector}; --code-background-inline: #{$mdn-theme-dark-code-background-inline}; --code-background-block: #{$mdn-theme-dark-code-background-block}; + --code-line-number: #{$mdn-theme-dark-code-line-number}; + --code-line-number-hover: #{$mdn-theme-dark-code-line-number-hover}; + --code-line-highlight: #{$mdn-theme-dark-code-line-highlight}; --notecard-link-color: #{$mdn-color-neutral-10};