Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enhance code example block: line numbers, highlight lines, line anchors #6372

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 110 additions & 7 deletions build/syntax-highlight.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ function syntaxHighlight($, doc) {
// match. The wildcard would technically match `<pre class="brushetta">`
// 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("<div class='code-example'>");
Expand All @@ -68,24 +68,127 @@ 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 highlightsTag = className.match(/highlight[-\d]+/g);

const code = $pre.text();
const html = Prism.highlight(code, grammar, name);
const $code = $("<code>").html(html);
let html = "";

if (!addLineNumbers) {
if (highlightsTag) {
console.warn(
`Tag 'linenumbers' must be present to use '${highlightsTag}'.`
);
}
html = Prism.highlight(code, grammar, language);
} else {
const highlights = highlightsTag
? highlightsTag[0].match(/(?<=-)\d+/g) || []
: [];

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 = $("<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 = `<a id='${id}' href="#${id}" title="">${lineNo}</a>`;
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));
lineNo++;
}

const outOfRangeNos = env.highlights.filter((h) => h >= lineNo);
if (outOfRangeNos.length > 0) {
outOfRangeNos.sort((a, b) => a - b);
console.warn(`Can not highlight lines: ${outOfRangeNos.join(", ")}`);
}
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("&lt;", "<");
}
});

module.exports = { syntaxHighlight };
35 changes: 35 additions & 0 deletions client/src/document/index.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 6 additions & 0 deletions client/src/ui/_vars.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
6 changes: 6 additions & 0 deletions client/src/ui/base/_themes.scss
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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};

Expand Down