feat: add some highlighting to markdown renderer
This commit is contained in:
parent
21871a72e6
commit
70d16913f3
@ -31,7 +31,7 @@ export function RecipeHero(
|
||||
<img
|
||||
src={imageUrl}
|
||||
alt="Recipe Banner"
|
||||
style={{ objectPosition: "0% 30%" }}
|
||||
style={{ objectPosition: "0% 25%" }}
|
||||
class="absolute object-cover w-full h-full -z-10"
|
||||
/>
|
||||
)}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { ComponentChildren } from "preact";
|
||||
import { menu } from "@lib/menus.ts";
|
||||
import { CSS, KATEX_CSS,render } from "https://deno.land/x/gfm/mod.ts";
|
||||
import { Head } from "$fresh/runtime.ts";
|
||||
|
||||
export type Props = {
|
||||
children: ComponentChildren;
|
||||
@ -15,6 +17,10 @@ export const MainLayout = ({ children, url }: Props) => {
|
||||
class="md:grid mx-auto"
|
||||
style={{ gridTemplateColumns: "200px 1fr", maxWidth: "1024px" }}
|
||||
>
|
||||
<Head>
|
||||
<style>{CSS}</style>
|
||||
<style>{KATEX_CSS}</style>
|
||||
</Head>
|
||||
<aside class="p-4 hidden md:block">
|
||||
<nav class="min-h-fit rounded-3xl p-3 grid gap-3 fixed t-0">
|
||||
{menu.map((m) => {
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { unified } from "https://esm.sh/unified@10.1.2";
|
||||
import { render } from "https://deno.land/x/gfm@0.2.5/mod.ts";
|
||||
import "https://esm.sh/prismjs@1.29.0/components/prism-typescript?no-check";
|
||||
import "https://esm.sh/prismjs@1.29.0/components/prism-bash?no-check";
|
||||
import "https://esm.sh/prismjs@1.29.0/components/prism-rust?no-check";
|
||||
import remarkParse from "https://esm.sh/remark-parse@10.0.2";
|
||||
import remarkStringify from "https://esm.sh/remark-stringify@10.0.3";
|
||||
import remarkFrontmatter, {
|
||||
Root,
|
||||
} from "https://esm.sh/remark-frontmatter@4.0.1";
|
||||
import remarkRehype from "https://esm.sh/remark-rehype@10.1.0";
|
||||
import rehypeSanitize from "https://esm.sh/rehype-sanitize@5.0.1";
|
||||
import rehypeStringify from "https://esm.sh/rehype-stringify@9.0.3";
|
||||
import * as cache from "@lib/cache/documents.ts";
|
||||
import { SILVERBULLET_SERVER } from "@lib/env.ts";
|
||||
import { fixRenderedMarkdown } from "@lib/helpers.ts";
|
||||
@ -90,14 +91,9 @@ export function parseDocument(doc: string) {
|
||||
}
|
||||
|
||||
export function renderMarkdown(doc: string) {
|
||||
const out = unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkRehype)
|
||||
.use(rehypeSanitize)
|
||||
.use(rehypeStringify)
|
||||
.processSync(doc);
|
||||
|
||||
return String(out);
|
||||
return render(doc, {
|
||||
allowMath: true,
|
||||
});
|
||||
}
|
||||
|
||||
export type ParsedDocument = ReturnType<typeof parseDocument>;
|
||||
|
79
lib/highlight.ts
Normal file
79
lib/highlight.ts
Normal file
@ -0,0 +1,79 @@
|
||||
import { refractor } from "https://esm.sh/refractor@4.8.1";
|
||||
import { visit } from "https://esm.sh/unist-util-visit@5.0.0";
|
||||
import { toString } from "https://esm.sh/hast-util-to-string@2.0.0";
|
||||
|
||||
import jsx from "https://esm.sh/refractor/lang/jsx";
|
||||
import javascript from "https://esm.sh/refractor/lang/javascript";
|
||||
import css from "https://esm.sh/refractor/lang/css";
|
||||
import cssExtras from "https://esm.sh/refractor/lang/css-extras";
|
||||
import jsExtras from "https://esm.sh/refractor/lang/js-extras";
|
||||
import sql from "https://esm.sh/refractor/lang/sql";
|
||||
import typescript from "https://esm.sh/refractor/lang/typescript";
|
||||
import swift from "https://esm.sh/refractor/lang/swift";
|
||||
import objectivec from "https://esm.sh/refractor/lang/objectivec";
|
||||
import markdown from "https://esm.sh/refractor/lang/markdown";
|
||||
import json from "https://esm.sh/refractor/lang/json";
|
||||
|
||||
refractor.register(jsx);
|
||||
refractor.register(json);
|
||||
refractor.register(typescript);
|
||||
refractor.register(javascript);
|
||||
refractor.register(css);
|
||||
refractor.register(cssExtras);
|
||||
refractor.register(jsExtras);
|
||||
refractor.register(sql);
|
||||
refractor.register(swift);
|
||||
refractor.register(objectivec);
|
||||
refractor.register(markdown);
|
||||
|
||||
refractor.alias({ jsx: ["js"] });
|
||||
refractor.alias({typescript:["ts"]})
|
||||
|
||||
const getLanguage = (node) => {
|
||||
const className = node.properties.className || [];
|
||||
|
||||
for (const classListItem of className) {
|
||||
if (classListItem.slice(0, 9) === "language-") {
|
||||
return classListItem.slice(9).toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const rehypePrism = (options) => {
|
||||
options = options || {};
|
||||
|
||||
return (tree) => {
|
||||
visit(tree, "element", visitor);
|
||||
};
|
||||
|
||||
function visitor(node, index, parent) {
|
||||
if (!parent || parent.tagName !== "pre" || node.tagName !== "code") {
|
||||
return;
|
||||
}
|
||||
|
||||
const lang = getLanguage(node);
|
||||
|
||||
if (lang === null) {
|
||||
return;
|
||||
}
|
||||
|
||||
let result;
|
||||
try {
|
||||
parent.properties.className = (parent.properties.className || []).concat(
|
||||
"language-" + lang,
|
||||
);
|
||||
result = refractor.highlight(toString(node), lang);
|
||||
} catch (err) {
|
||||
if (options.ignoreMissing && /Unknown language/.test(err.message)) {
|
||||
return;
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
|
||||
node.children = result;
|
||||
}
|
||||
};
|
||||
|
||||
export default rehypePrism;
|
@ -35,8 +35,7 @@ export const isYoutubeLink = (link: string) => {
|
||||
try {
|
||||
const url = new URL(link);
|
||||
return ["youtu.be", "youtube.com","www.youtube.com" ].includes(url.hostname);
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} catch (_err) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
@ -16,6 +16,7 @@ export default function App({ Component }: AppProps) {
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link href="/global.css" rel="stylesheet" />
|
||||
<link href="/prism-material-dark.css" rel="stylesheet" />
|
||||
</Head>
|
||||
<Component />
|
||||
</>
|
||||
|
@ -73,6 +73,12 @@ async function processCreateArticle(
|
||||
return `})`;
|
||||
}
|
||||
|
||||
if (!src.startsWith("https://") && !src.startsWith("http://")) {
|
||||
return `}/${
|
||||
src.replace(/^\//, "")
|
||||
})`;
|
||||
}
|
||||
|
||||
return ``;
|
||||
},
|
||||
});
|
||||
@ -87,9 +93,16 @@ async function processCreateArticle(
|
||||
}
|
||||
|
||||
if (href.startsWith("#")) {
|
||||
if (content.length < 2) return "";
|
||||
return `[${content}](${url.href}#${href})`.replace("##", "#");
|
||||
}
|
||||
|
||||
if (!href.startsWith("https://") && !href.startsWith("http://")) {
|
||||
return `[${content}](${url.origin.replace(/\/$/, "")}/${
|
||||
href.replace(/^\//, "")
|
||||
})`;
|
||||
}
|
||||
|
||||
return `[${content}](${href})`;
|
||||
},
|
||||
});
|
||||
|
@ -42,7 +42,9 @@ export default function Greet(props: PageProps<Article>) {
|
||||
<YoutubePlayer link={article.meta.link} />
|
||||
)}
|
||||
<pre
|
||||
class="whitespace-break-spaces"
|
||||
class="whitespace-break-spaces markdown-body"
|
||||
data-color-mode="dark"
|
||||
data-dark-theme="dark"
|
||||
dangerouslySetInnerHTML={{ __html: article.content || "" }}
|
||||
>
|
||||
{article.content||""}
|
||||
|
205
static/prism-material-dark.css
Normal file
205
static/prism-material-dark.css
Normal file
@ -0,0 +1,205 @@
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
text-align: left;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
color: #eee;
|
||||
background: #2f2f2f;
|
||||
font-family: Roboto Mono, monospace;
|
||||
font-size: 1em;
|
||||
line-height: 1.5em;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
code[class*="language-"]::-moz-selection,
|
||||
pre[class*="language-"]::-moz-selection,
|
||||
code[class*="language-"] ::-moz-selection,
|
||||
pre[class*="language-"] ::-moz-selection {
|
||||
background: #363636;
|
||||
}
|
||||
|
||||
code[class*="language-"]::selection,
|
||||
pre[class*="language-"]::selection,
|
||||
code[class*="language-"] ::selection,
|
||||
pre[class*="language-"] ::selection {
|
||||
background: #363636;
|
||||
}
|
||||
|
||||
:not(pre) > code[class*="language-"] {
|
||||
white-space: normal;
|
||||
border-radius: 0.2em;
|
||||
padding: 0.1em;
|
||||
}
|
||||
|
||||
pre[class*="language-"] {
|
||||
overflow: auto;
|
||||
position: relative;
|
||||
margin: 0.5em 0;
|
||||
padding: 1.25em 1em;
|
||||
}
|
||||
|
||||
.language-css > code,
|
||||
.language-sass > code,
|
||||
.language-scss > code {
|
||||
color: #fd9170;
|
||||
}
|
||||
|
||||
[class*="language-"] .namespace {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.token.atrule {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.attr-name {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
|
||||
.token.attr-value {
|
||||
color: #a5e844;
|
||||
}
|
||||
|
||||
.token.attribute {
|
||||
color: #a5e844;
|
||||
}
|
||||
|
||||
.token.boolean {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.builtin {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
|
||||
.token.cdata {
|
||||
color: #80cbc4;
|
||||
}
|
||||
|
||||
.token.char {
|
||||
color: #80cbc4;
|
||||
}
|
||||
|
||||
.token.class {
|
||||
color: #ffcb6b;
|
||||
}
|
||||
|
||||
.token.class-name {
|
||||
color: #f2ff00;
|
||||
}
|
||||
|
||||
.token.comment {
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.token.constant {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.deleted {
|
||||
color: #ff6666;
|
||||
}
|
||||
|
||||
.token.doctype {
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
color: #ff6666;
|
||||
}
|
||||
|
||||
.token.function {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.hexcode {
|
||||
color: #f2ff00;
|
||||
}
|
||||
|
||||
.token.id {
|
||||
color: #c792ea;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.important {
|
||||
color: #c792ea;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.token.inserted {
|
||||
color: #80cbc4;
|
||||
}
|
||||
|
||||
.token.keyword {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.number {
|
||||
color: #fd9170;
|
||||
}
|
||||
|
||||
.token.operator {
|
||||
color: #89ddff;
|
||||
}
|
||||
|
||||
.token.prolog {
|
||||
color: #616161;
|
||||
}
|
||||
|
||||
.token.property {
|
||||
color: #80cbc4;
|
||||
}
|
||||
|
||||
.token.pseudo-class {
|
||||
color: #a5e844;
|
||||
}
|
||||
|
||||
.token.pseudo-element {
|
||||
color: #a5e844;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #89ddff;
|
||||
}
|
||||
|
||||
.token.regex {
|
||||
color: #f2ff00;
|
||||
}
|
||||
|
||||
.token.selector {
|
||||
color: #ff6666;
|
||||
}
|
||||
|
||||
.token.string {
|
||||
color: #a5e844;
|
||||
}
|
||||
|
||||
.token.symbol {
|
||||
color: #c792ea;
|
||||
}
|
||||
|
||||
.token.tag {
|
||||
color: #ff6666;
|
||||
}
|
||||
|
||||
.token.unit {
|
||||
color: #fd9170;
|
||||
}
|
||||
|
||||
.token.url {
|
||||
color: #ff6666;
|
||||
}
|
||||
|
||||
.token.variable {
|
||||
color: #ff6666;
|
||||
}
|
169
static/prism-twilight.css
Normal file
169
static/prism-twilight.css
Normal file
@ -0,0 +1,169 @@
|
||||
/**
|
||||
* prism.js Twilight theme
|
||||
* Based (more or less) on the Twilight theme originally of Textmate fame.
|
||||
* @author Remy Bach
|
||||
*/
|
||||
code[class*="language-"],
|
||||
pre[class*="language-"] {
|
||||
color: white;
|
||||
background: none;
|
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
|
||||
font-size: 1em;
|
||||
text-align: left;
|
||||
text-shadow: 0 -.1em .2em black;
|
||||
white-space: pre;
|
||||
word-spacing: normal;
|
||||
word-break: normal;
|
||||
word-wrap: normal;
|
||||
line-height: 1.5;
|
||||
|
||||
-moz-tab-size: 4;
|
||||
-o-tab-size: 4;
|
||||
tab-size: 4;
|
||||
|
||||
-webkit-hyphens: none;
|
||||
-moz-hyphens: none;
|
||||
-ms-hyphens: none;
|
||||
hyphens: none;
|
||||
}
|
||||
|
||||
pre[class*="language-"],
|
||||
:not(pre) > code[class*="language-"] {
|
||||
background: hsl(0, 0%, 8%); /* #141414 */
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre[class*="language-"] {
|
||||
border-radius: .5em;
|
||||
border: .3em solid hsl(0, 0%, 33%); /* #282A2B */
|
||||
box-shadow: 1px 1px .5em black inset;
|
||||
margin: .5em 0;
|
||||
overflow: auto;
|
||||
padding: 1em;
|
||||
}
|
||||
|
||||
pre[class*="language-"]::-moz-selection {
|
||||
/* Firefox */
|
||||
background: hsl(200, 4%, 16%); /* #282A2B */
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection {
|
||||
/* Safari */
|
||||
background: hsl(200, 4%, 16%); /* #282A2B */
|
||||
}
|
||||
|
||||
/* Text Selection colour */
|
||||
pre[class*="language-"]::-moz-selection, pre[class*="language-"] ::-moz-selection,
|
||||
code[class*="language-"]::-moz-selection, code[class*="language-"] ::-moz-selection {
|
||||
text-shadow: none;
|
||||
background: hsla(0, 0%, 93%, 0.15); /* #EDEDED */
|
||||
}
|
||||
|
||||
pre[class*="language-"]::selection, pre[class*="language-"] ::selection,
|
||||
code[class*="language-"]::selection, code[class*="language-"] ::selection {
|
||||
text-shadow: none;
|
||||
background: hsla(0, 0%, 93%, 0.15); /* #EDEDED */
|
||||
}
|
||||
|
||||
/* Inline code */
|
||||
:not(pre) > code[class*="language-"] {
|
||||
border-radius: .3em;
|
||||
border: .13em solid hsl(0, 0%, 33%); /* #545454 */
|
||||
box-shadow: 1px 1px .3em -.1em black inset;
|
||||
padding: .15em .2em .05em;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: hsl(0, 0%, 47%); /* #777777 */
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.namespace {
|
||||
opacity: .7;
|
||||
}
|
||||
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.deleted {
|
||||
color: hsl(14, 58%, 55%); /* #CF6A4C */
|
||||
}
|
||||
|
||||
.token.keyword,
|
||||
.token.property,
|
||||
.token.selector,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.builtin {
|
||||
color: hsl(53, 89%, 79%); /* #F9EE98 */
|
||||
}
|
||||
|
||||
.token.attr-name,
|
||||
.token.attr-value,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url,
|
||||
.language-css .token.string,
|
||||
.style .token.string,
|
||||
.token.variable,
|
||||
.token.inserted {
|
||||
color: hsl(76, 21%, 52%); /* #8F9D6A */
|
||||
}
|
||||
|
||||
.token.atrule {
|
||||
color: hsl(218, 22%, 55%); /* #7587A6 */
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important {
|
||||
color: hsl(42, 75%, 65%); /* #E9C062 */
|
||||
}
|
||||
|
||||
.token.important,
|
||||
.token.bold {
|
||||
font-weight: bold;
|
||||
}
|
||||
.token.italic {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.token.entity {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
/* Markup */
|
||||
.language-markup .token.tag,
|
||||
.language-markup .token.attr-name,
|
||||
.language-markup .token.punctuation {
|
||||
color: hsl(33, 33%, 52%); /* #AC885B */
|
||||
}
|
||||
|
||||
/* Make the tokens sit above the line highlight so the colours don't look faded. */
|
||||
.token {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.line-highlight.line-highlight {
|
||||
background: hsla(0, 0%, 33%, 0.25); /* #545454 */
|
||||
background: linear-gradient(to right, hsla(0, 0%, 33%, .1) 70%, hsla(0, 0%, 33%, 0)); /* #545454 */
|
||||
border-bottom: 1px dashed hsl(0, 0%, 33%); /* #545454 */
|
||||
border-top: 1px dashed hsl(0, 0%, 33%); /* #545454 */
|
||||
margin-top: 0.75em; /* Same as .prism’s padding-top */
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.line-highlight.line-highlight:before,
|
||||
.line-highlight.line-highlight[data-end]:after {
|
||||
background-color: hsl(215, 15%, 59%); /* #8794A6 */
|
||||
color: hsl(24, 20%, 95%); /* #F5F2F0 */
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user