memento-dev/app/components/Highlight.tsx

401 lines
7.9 KiB
TypeScript

import type { ComponentProps, ParentComponent } from "solid-js";
import {
createEffect,
createMemo,
For,
mergeProps,
on,
splitProps,
} from "solid-js";
import * as Prismjs from "prismjs";
import clsx from "clsx";
/**
* @see https://prismjs.com/#supported-languages
*/
export const Language = {
JAVASCRIPT: "javascript",
HTML: "html",
CSS: "css",
MARKUP: "markup",
XML: "xml",
SVG: "svg",
ABAP: "abap",
ABNF: "abnf",
ACTION_SCRIPT: "actionscript",
ADA: "ada",
AGDA: "agda",
AL: "al",
ANTLR4: "antlr4",
G4: "g4",
APACHE_CONFIGURATION: "apacheconf",
APEX: "apex",
APL: "apl",
APPLESCRIPT: "applescript",
AQL: "aql",
ARDUINO: "arduino",
ARFF: "arff",
ARM_ASSEMBLY: "armasm",
ARTURO: "arturo",
ASCIIDOC: "asciidoc",
ASP_NET: "aspnet",
ASSEMBLY_6502: "asm6502",
ASSEMBLY_ATMEL_AVR: "asmatmel",
AUTO_HOTKEY: "autohotkey",
AUTO_IT: "autoit",
AVI_SYNTH: "avisynth",
AVRO_IDL: "avro-idl",
AWK: "awk",
BASH: "bash",
BASIC: "basic",
BATCH: "batch",
BBCODE: "bbcode",
BBJ: "bbj",
BICEP: "bicep",
BIRB: "birb",
BISON: "bison",
BNF: "bnf",
BQN: "bqn",
BRAINFUCK: "brainfuck",
BRIGHT_SCRIPT: "brightscript",
BRO: "bro",
BSL: "bsl",
C: "c",
CSHARP: "csharp",
CPP: "cpp",
CF_SCRIPT: "cfscript",
CHAI_SCRIPT: "chaiscript",
CIL: "cil",
CILK_C: "cilkc",
CILK_CPP: "cilkcpp",
CLOJURE: "clojure",
CMAKE: "cmake",
COBOL: "cobol",
COFFEE_SCRIPT: "coffeescript",
CONCURNAS: "concurnas",
CONTENT_SECURITY_POLICY: "csp",
COOKLANG: "cooklang",
Coq: "coq",
CRYSTAL: "crystal",
CSS_EXTRAS: "css-extras",
CSV: "csv",
CUE: "cue",
CYPHER: "cypher",
D: "d",
DART: "dart",
DATA_WEAVE: "dataweave",
DAX: "dax",
DHALL: "dhall",
DIFF: "diff",
DJANGO: "django",
JINJA2: "jinja2",
DNS_ZONE_FILE: "dns-zone-file",
DOCKER: "docker",
DOT: "dot",
EBNF: "ebnf",
EDITOR_CONFIG: "editorconfig",
EIFFEL: "eiffel",
EJS: "ejs",
ELIXIR: "elixir",
ELM: "elm",
EMBEDDED_LUA_TEMPLATING: "etlua",
ERB: "erb",
ERLANG: "erlang",
EXCEL_FORMULA: "excel-formula",
FSHARP: "fsharp",
FACTOR: "factor",
FALSE: "false",
FIRESTORE_SECURITY_RULES: "firestore-security-rules",
FLOW: "flow",
FORTRAN: "fortran",
FREEMARKER_TEMPLATE_LANGUAGE: "ftl",
GAMEMAKER_LANGUAGE: "gml",
GAP: "gap",
GCODE: "gcode",
GD_SCRIPT: "gdscript",
GEDCOM: "gedcom",
GETTEXT: "gettext",
GHERKIN: "gherkin",
GIT: "git",
GLSL: "glsl",
GN: "gn",
GNU_LINKER_SCRIPT: "linker-script",
GO: "go",
GO_MODULE: "go-module",
GRADLE: "gradle",
GRAPHQL: "graphql",
GROOVY: "groovy",
HAML: "haml",
HANDLEBARS: "handlebars",
HASKELL: "haskell",
HAXE: "haxe",
HCL: "hcl",
HLSL: "hlsl",
HOON: "hoon",
HTTP: "http",
HTTP_PUBLIC_KEY_PINS: "hpkp",
HTTP_STRICT_TRANSPORT_SECURITY: "hsts",
ICHIGOJAM: "ichigojam",
ICON: "icon",
ICU_MESSAGE_FORMAT: "icu-message-format",
IDRIS: "idris",
IGNORE: "ignore",
INFORM_7: "inform7",
INI: "ini",
IO: "io",
J: "j",
JAVA: "java",
JAVADOC: "javadoc",
JAVADOC_LIKE: "javadoclike",
JAVA_STACK_TRACE: "javastacktrace",
JEXL: "jexl",
JOLIE: "jolie",
JQ: "jq",
JSDOC: "jsdoc",
JS_EXTRAS: "js-extras",
JSON: "json",
JSON5: "json5",
JSONP: "jsonp",
JS_STACK_TRACE: "jsstacktrace",
JS_TEMPLATES: "js-templates",
JULIA: "julia",
KEEPALIVED_CONFIGURE: "keepalived",
KEYMAN: "keyman",
KOTLIN: "kotlin",
KUMIR: "kumir",
KUSTO: "kusto",
LATEX: "latex",
LATTE: "latte",
LESS: "less",
LILYPOND: "lilypond",
LIQUID: "liquid",
LISP: "lisp",
LIVESCRIPT: "livescript",
LLVM: "llvm",
LOG: "log",
LOLCODE: "lolcode",
LUA: "lua",
MAGMA: "magma",
MAKEFILE: "makefile",
MARKDOWN: "markdown",
MARKUP_TEMPLATING: "markup-templating",
MATA: "mata",
MATLAB: "matlab",
MAXSCRIPT: "maxscript",
MEL: "mel",
MERMAID: "mermaid",
METAFONT: "metafont",
MIZAR: "mizar",
MONGODB: "mongodb",
MONKEY: "monkey",
MOONSCRIPT: "moonscript",
N1QL: "n1ql",
N4JS: "n4js",
NAND_TO_TETRIS_HDL: "nand2tetris",
NANINOVEL_SCRIPT: "naniscript",
NASM: "nasm",
NEON: "neon",
NEVOD: "nevod",
NGINX: "nginx",
NIM: "nim",
NIX: "nix",
NSIS: "nsis",
OBJECTIVE_C: "objectivec",
OCAML: "ocaml",
ODIN: "odin",
OPENCL: "opencl",
OPENQASM: "openqasm",
OZ: "oz",
PARI_GP: "parigp",
PARSER: "parser",
PASCAL: "pascal",
PASCALIGO: "pascaligo",
PATROL_SCRIPTING_LANGUAGE: "psl",
PC_AXIS: "pcaxis",
PEOPLECODE: "peoplecode",
PERL: "perl",
PHP: "php",
PHP_DOC: "phpdoc",
PHP_EXTRAS: "php-extras",
PLANT_UML: "plant-uml",
PL_SQL: "plsql",
POWERQUERY: "powerquery",
POWERSHELL: "powershell",
PROCESSING: "processing",
PROLOG: "prolog",
PROMQL: "promql",
PROPERTIES: "properties",
PROTOBUF: "protobuf",
PUG: "pug",
PUPPET: "puppet",
PURE: "pure",
PUREBASIC: "purebasic",
PURESCRIPT: "purescript",
PYTHON: "python",
QSHARP: "qsharp",
Q: "q",
QML: "qml",
QORE: "qore",
R: "r",
RACKET: "racket",
RAZOR: "razor",
REACT_JSX: "jsx",
REACT_TSX: "tsx",
REASON: "reason",
REGEX: "regex",
REGO: "rego",
RENPY: "renpy",
RESCRIPT: "rescript",
REST: "rest",
RIP: "rip",
ROBOCONF: "roboconf",
ROBOT_FRAMEWORK: "robotframework",
RUBY: "ruby",
RUST: "rust",
SAS: "sas",
SASS: "sass",
SCSS: "scss",
SCALA: "scala",
SCHEME: "scheme",
SHELL_SESSION: "shell-session",
SMALI: "smali",
SMALLTALK: "smalltalk",
SMARTY: "smarty",
SML: "sml",
SOLIDITY: "solidity",
SOLUTION_FILE: "solution-file",
SOY: "soy",
SPARQL: "sparql",
SPLUNK_SPL: "splunk-spl",
SQF: "sqf",
SQL: "sql",
SQUIRREL: "squirrel",
STAN: "stan",
STATA_ADO: "stata",
STRUCTURED_TEXT: "iecst",
STYLUS: "stylus",
SUPERCOLLIDER: "supercollider",
SWIFT: "swift",
SYSTEMD: "systemd",
T4_TEMPLATING: "t4-templating",
T4_CSHARP: "t4-cs",
T4_VB: "t4-vb",
TAP: "tap",
TCL: "tcl",
TEMPLATE_TOOLKIT_2: "tt2",
TEXTILE: "textile",
TOML: "toml",
TREMOR: "tremor",
TURTLE: "turtle",
TWIG: "twig",
TYPESCRIPT: "typescript",
TYPOSCRIPT: "typoscript",
UNREALSCRIPT: "unrealscript",
UO_RAZOR_SCRIPT: "uorazor",
URI: "uri",
V: "v",
VALA: "vala",
VB_NET: "vbnet",
VELOCITY: "velocity",
VERILOG: "verilog",
VHDL: "vhdl",
VIM: "vim",
VISUAL_BASIC: "visual-basic",
WARP_SCRIPT: "warpscript",
WEB_ASSEMBLY: "wasm",
WEB_IDL: "web-idl",
WGSL: "wgsl",
WIKI: "wiki",
WOLFRAM: "wolfram",
WREN: "wren",
XEORA: "xeora",
XML_DOC: "xml-doc",
XOJO: "xojo",
XQUERY: "xquery",
YAML: "yaml",
YANG: "yang",
ZIG: "zig",
} as const;
export type Language = (typeof Language)[keyof typeof Language];
type Props = {
language: string;
class?: string;
withLineNumbers?: boolean;
} & ComponentProps<"code">;
export const Highlight: ParentComponent<Props> = (_props) => {
const props = mergeProps({ language: "javascript" }, _props);
const [, rest] = splitProps(props, [
"language",
"children",
"class",
"innerHTML",
]);
const languageClass = createMemo(() => `language-${props.language}`);
const highlightedCode = createMemo<string | undefined>(() => {
const childrenString = props.children?.toString();
if (!childrenString) return;
const grammar = Prismjs.languages[props.language];
if (!grammar) return;
const result = Prismjs.highlight(childrenString, grammar, props.language);
return result;
});
createEffect(
on([languageClass, highlightedCode], () => {
Prismjs.highlightAll();
}),
);
return (
<div
class={clsx(
"rounded-xl shadow-lg flex items-start px-4 py-2 w-full",
props.class,
)}
>
{props.withLineNumbers && props.children?.toString() && (
<div
aria-hidden="true"
class="border-r leading-6 border-slate-300/5 pr-4 font-mono text-slate-600 select-none"
>
<For
each={Array.from({
length: props.children.toString().split("\n").length,
})}
>
{(_, index) => (
<>
{(index() + 1).toString().padStart(2, "0")}
<br />
</>
)}
</For>
</div>
)}
<pre
class={clsx(
"not-prose w-full prism-code flex overflow-x-auto",
languageClass(),
)}
>
<code
class={clsx("px-4", "leading-6")}
innerHTML={highlightedCode()}
{...rest}
>
{props.children}
</code>
</pre>
</div>
);
};