feat: Code blocks can now optionally display line numbers (#4324)

* feat: Code blocks can now optionally display line numbers as a user preference

* Touch more breathing room
This commit is contained in:
Tom Moor
2022-10-24 09:44:46 -04:00
committed by GitHub
parent 708f9a3fd6
commit 87761e9bf2
10 changed files with 103 additions and 20 deletions

View File

@@ -864,6 +864,21 @@ mark {
}
}
.code-block.with-line-numbers {
pre {
padding-left: calc(var(--line-number-gutter-width, 0) * 1em + 1.5em);
}
}
.code-block .line-numbers {
position: absolute;
left: 1em;
color: ${props.theme.textTertiary};
text-align: right;
font-variant-numeric: tabular-nums;
user-select: none;
}
.mermaid-diagram-wrapper {
display: flex;
align-items: center;

View File

@@ -39,6 +39,7 @@ import sql from "refractor/lang/sql";
import swift from "refractor/lang/swift";
import typescript from "refractor/lang/typescript";
import yaml from "refractor/lang/yaml";
import { UserPreferences } from "@shared/types";
import { Dictionary } from "~/hooks/useDictionary";
import toggleBlockType from "../commands/toggleBlockType";
@@ -81,8 +82,10 @@ const DEFAULT_LANGUAGE = "javascript";
export default class CodeFence extends Node {
constructor(options: {
dictionary: Dictionary;
userPreferences?: UserPreferences | null;
onShowToast: (message: string) => void;
}) {
console.log({ options });
super(options);
}
@@ -174,7 +177,11 @@ export default class CodeFence extends Node {
return [
"div",
{
class: "code-block",
class: `code-block ${
this.options.userPreferences?.codeBlockLineNumbers
? "with-line-numbers"
: ""
}`,
"data-language": node.attrs.language,
},
...(actions ? [["div", { contentEditable: "false" }, actions]] : []),
@@ -301,7 +308,10 @@ export default class CodeFence extends Node {
get plugins() {
return [
Prism({ name: this.name }),
Prism({
name: this.name,
lineNumbers: this.options.userPreferences?.codeBlockLineNumbers,
}),
Mermaid({ name: this.name }),
new Plugin({
key: new PluginKey("triple-click"),

View File

@@ -40,7 +40,18 @@ type ParsedNode = {
const cache: Record<number, { node: Node; decorations: Decoration[] }> = {};
function getDecorations({ doc, name }: { doc: Node; name: string }) {
function getDecorations({
doc,
name,
lineNumbers,
}: {
/** The prosemirror document to operate on. */
doc: Node;
/** The node name. */
name: string;
/** Whether to include decorations representing line numbers */
lineNumbers?: boolean;
}) {
const decorations: Decoration[] = [];
const blocks: { node: Node; pos: number }[] = findBlockNodes(doc).filter(
(item) => item.node.type.name === name
@@ -71,6 +82,27 @@ function getDecorations({ doc, name }: { doc: Node; name: string }) {
}
if (!cache[block.pos] || !cache[block.pos].node.eq(block.node)) {
if (lineNumbers) {
const lineCount =
(block.node.textContent.match(/\n/g) || []).length + 1;
decorations.push(
Decoration.widget(block.pos + 1, () => {
const el = document.createElement("div");
el.innerText = new Array(lineCount)
.fill(0)
.map((_, i) => i + 1)
.join("\n");
el.className = "line-numbers";
return el;
})
);
decorations.push(
Decoration.node(block.pos, block.pos + block.node.nodeSize, {
style: `--line-number-gutter-width: ${String(lineCount).length}`,
})
);
}
const nodes = refractor.highlight(block.node.textContent, language);
const _decorations = flattenDeep(parseNodes(nodes))
.map((node: ParsedNode) => {
@@ -111,7 +143,15 @@ function getDecorations({ doc, name }: { doc: Node; name: string }) {
return DecorationSet.create(doc, decorations);
}
export default function Prism({ name }: { name: string }) {
export default function Prism({
name,
lineNumbers,
}: {
/** The node name. */
name: string;
/** Whether to include decorations representing line numbers */
lineNumbers?: boolean;
}) {
let highlighted = false;
return new Plugin({
@@ -129,7 +169,7 @@ export default function Prism({ name }: { name: string }) {
if (!highlighted || codeBlockChanged || ySyncEdit) {
highlighted = true;
return getDecorations({ doc: transaction.doc, name });
return getDecorations({ doc: transaction.doc, name, lineNumbers });
}
return decorationSet.map(transaction.mapping, transaction.doc);