use prismjs

This commit is contained in:
EGOIST 2022-05-13 23:01:02 +08:00
parent 23f92dabc9
commit 00df250ff5
10 changed files with 120 additions and 47 deletions

View File

@ -37,6 +37,7 @@
"nanoid": "^3.3.3",
"next": "^12.1.6",
"pica": "^9.0.1",
"prismjs": "^1.28.0",
"react": "^18.1.0",
"react-avatar-editor": "^12.0.0",
"react-dom": "^18.1.0",
@ -44,7 +45,6 @@
"react-hot-toast": "^2.2.0",
"react-query": "^3.39.0",
"react-sortablejs": "^6.1.1",
"shiki": "^0.10.1",
"sortablejs": "^1.15.0",
"ua-parser-js": "^1.0.2",
"uuid": "^8.3.2",
@ -58,6 +58,7 @@
"@types/markdown-it": "^12.2.3",
"@types/node": "^17.0.31",
"@types/pica": "^9.0.0",
"@types/prismjs": "^1.26.0",
"@types/react": "^18.0.9",
"@types/react-avatar-editor": "^12.0.0",
"@types/react-dom": "^18.0.3",

View File

@ -20,6 +20,7 @@ specifiers:
'@types/markdown-it': ^12.2.3
'@types/node': ^17.0.31
'@types/pica': ^9.0.0
'@types/prismjs': ^1.26.0
'@types/react': ^18.0.9
'@types/react-avatar-editor': ^12.0.0
'@types/react-dom': ^18.0.3
@ -44,6 +45,7 @@ specifiers:
pica: ^9.0.1
postcss-import: ^14.1.0
prisma: ^3.14.0
prismjs: ^1.28.0
react: ^18.1.0
react-avatar-editor: ^12.0.0
react-dom: ^18.1.0
@ -51,7 +53,6 @@ specifiers:
react-hot-toast: ^2.2.0
react-query: ^3.39.0
react-sortablejs: ^6.1.1
shiki: ^0.10.1
sortablejs: ^1.15.0
tailwindcss: ^3.0.24
typescript: ^4.5.5
@ -88,6 +89,7 @@ dependencies:
nanoid: 3.3.4
next: 12.1.6_ef5jwxihqo6n7gxfmzogljlgcm
pica: 9.0.1
prismjs: 1.28.0
react: 18.1.0
react-avatar-editor: 12.0.0_ef5jwxihqo6n7gxfmzogljlgcm
react-dom: 18.1.0_react@18.1.0
@ -95,7 +97,6 @@ dependencies:
react-hot-toast: 2.2.0_ef5jwxihqo6n7gxfmzogljlgcm
react-query: 3.39.0_ef5jwxihqo6n7gxfmzogljlgcm
react-sortablejs: 6.1.1_kjz72dk5lh6hp4ogspefhm7r2u
shiki: 0.10.1
sortablejs: 1.15.0
ua-parser-js: 1.0.2
uuid: 8.3.2
@ -109,6 +110,7 @@ devDependencies:
'@types/markdown-it': 12.2.3
'@types/node': 17.0.31
'@types/pica': 9.0.0
'@types/prismjs': 1.26.0
'@types/react': 18.0.9
'@types/react-avatar-editor': 12.0.0
'@types/react-dom': 18.0.3
@ -1659,6 +1661,10 @@ packages:
resolution: {integrity: sha512-RyIhhyESvtvUdAviCqpk8fmNHNdSCMQB4ZVyPJLoNndRC8Jml/3zfC0RisHIuGFziM9+vGJ2/1nWXoTwAkVE2Q==}
dev: true
/@types/prismjs/1.26.0:
resolution: {integrity: sha512-ZTaqn/qSqUuAq1YwvOFQfVW1AR/oQJlLSZVustdjwI+GZ8kr0MSHBj0tsXPW1EqHubx50gtBEjbPGsdZwQwCjQ==}
dev: true
/@types/prop-types/15.7.5:
resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==}
dev: true
@ -3087,10 +3093,6 @@ packages:
minimist: 1.2.6
dev: true
/jsonc-parser/3.0.0:
resolution: {integrity: sha512-fQzRfAbIBnR0IQvftw9FJveWiHp72Fg20giDrHz6TdfB12UH/uue0D3hm57UB5KgAVuniLMCaS8P1IMj9NR7cA==}
dev: false
/jsx-ast-utils/3.3.0:
resolution: {integrity: sha512-XzO9luP6L0xkxwhIJMTJQpZo/eeN60K08jHdexfD569AGxeNug6UketeHXEhROoM8aR7EcUoOQmIhcJQjcuq8Q==}
engines: {node: '>=4.0'}
@ -3591,6 +3593,11 @@ packages:
'@prisma/engines': 3.14.0-36.2b0c12756921c891fec4f68d9444e18c7d5d4a6a
dev: true
/prismjs/1.28.0:
resolution: {integrity: sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==}
engines: {node: '>=6'}
dev: false
/prop-types/15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
dependencies:
@ -3830,14 +3837,6 @@ packages:
engines: {node: '>=8'}
dev: true
/shiki/0.10.1:
resolution: {integrity: sha512-VsY7QJVzU51j5o1+DguUd+6vmCmZ5v/6gYu4vyYAhzjuNQU6P/vmSy4uQaOhvje031qQMiW0d2BwgMH52vqMng==}
dependencies:
jsonc-parser: 3.0.0
vscode-oniguruma: 1.6.2
vscode-textmate: 5.2.0
dev: false
/side-channel/1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
@ -4109,14 +4108,6 @@ packages:
resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==}
dev: true
/vscode-oniguruma/1.6.2:
resolution: {integrity: sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==}
dev: false
/vscode-textmate/5.2.0:
resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==}
dev: false
/w3c-keyname/2.2.4:
resolution: {integrity: sha512-tOhfEwEzFLJzf6d1ZPkYfGj+FWhIpBux9ppoP3rlclw3Z0BZv3N7b7030Z1kYth+6rDuAsXUFr+d0VE6Ed1ikw==}
dev: false

View File

@ -19,7 +19,7 @@ export const Input = forwardRef<HTMLInputElement, Props>(function Input(
return (
<div>
{label && (
<label className="label" htmlFor={inputProps.id}>
<label className="form-label" htmlFor={inputProps.id}>
{label}
</label>
)}

View File

@ -1,19 +1,22 @@
.code {
@apply my-5;
@apply relative;
color: var(--code-fg);
}
.code pre {
@apply p-5;
@apply rounded-lg;
@apply whitespace-pre-wrap;
@apply whitespace-pre;
@apply font-mono;
background-color: var(--code-bg);
}
.code:before {
content: attr(data-lang);
color: var(--lang-color);
@apply absolute;
@apply right-3;
@apply top-2;
@apply text-zinc-500;
@apply text-xs;
}

View File

@ -2,3 +2,4 @@
@import "./tailwind.css";
@import "./prose.css";
@import "./code.css";
@import "./prism-dark.css";

81
src/css/prism-dark.css Normal file
View File

@ -0,0 +1,81 @@
/** based on prism tomorror */
.code {
--lang-color: #8d8c8c;
--code-bg: #2d2d2d;
--code-fg: #ccc;
}
.token.comment,
.token.block-comment,
.token.prolog,
.token.doctype,
.token.cdata {
color: #999;
}
.token.punctuation {
color: #ccc;
}
.token.tag,
.token.attr-name,
.token.namespace,
.token.deleted {
color: #e2777a;
}
.token.function-name {
color: #6196cc;
}
.token.boolean,
.token.number,
.token.function {
color: #f08d49;
}
.token.property,
.token.class-name,
.token.constant,
.token.symbol {
color: #f8c555;
}
.token.selector,
.token.important,
.token.atrule,
.token.keyword,
.token.builtin {
color: #cc99cd;
}
.token.string,
.token.char,
.token.attr-value,
.token.regex,
.token.variable {
color: #7ec699;
}
.token.operator,
.token.entity,
.token.url {
color: #67cdcc;
}
.token.important,
.token.bold {
font-weight: bold;
}
.token.italic {
font-style: italic;
}
.token.entity {
cursor: help;
}
.token.inserted {
color: green;
}

View File

@ -119,7 +119,7 @@ textarea.input {
@apply px-3;
}
.label {
.form-label {
@apply block;
@apply mb-2;
@apply text-sm;

View File

@ -1,5 +1,6 @@
import Markdown from "markdown-it"
import * as shiki from "shiki"
import Prism from "prismjs"
import loadLanguages from "prismjs/components/index"
import { getUserContentsUrl } from "./user-contents"
const isExternLink = (url: string) => /^https?:\/\//.test(url)
@ -20,20 +21,18 @@ const handleImages = (md: Markdown) => {
}
}
const codeBlock = (
md: Markdown,
{ highlighter }: { highlighter: shiki.Highlighter }
) => {
const theme = highlighter.getTheme()
const languages = highlighter.getLoadedLanguages()
const codeBlock = (md: Markdown) => {
const highlight = (code: string, lang: string) => {
if (!languages.includes(lang as any)) {
return `<pre style="background-color:${theme.bg};color:${
theme.fg
};"><code>${md.utils.escapeHtml(code)}</code></pre>`
if (lang === "vue" || lang === "svelte") {
lang = "html"
}
return highlighter.codeToHtml(code, { lang })
loadLanguages(lang)
const grammer = Prism.languages[lang]
const html = grammer
? `<pre><code>${Prism.highlight(code, grammer, lang)}</code></pre>`
: `<pre><code>${md.utils.escapeHtml(code)}</code></pre>`
return html
}
md.renderer.rules.fence = (tokens, idx, options, env, self) => {
@ -44,16 +43,13 @@ const codeBlock = (
}
export const renderPageContent = async (content: string) => {
const highlighter = await shiki.getHighlighter({
theme: "vitesse-dark",
})
const md = new Markdown({
html: false,
linkify: true,
})
md.use(handleImages)
md.use(codeBlock, { highlighter })
md.use(codeBlock)
const html = md.render(content)
return { html }

View File

@ -49,7 +49,7 @@ export default function AccountProfilePage() {
<SettingsLayout title="Account" type="account">
{viewer.data && (
<div>
<label className="label">Profile Picture</label>
<label className="form-label">Profile Picture</label>
<AvatarForm filename={viewer.data.avatar} name={viewer.data.name} />
</div>
)}

View File

@ -56,7 +56,7 @@ export default function SiteSettingsGeneralPage() {
<SettingsLayout title="Site Settings" type="site">
{site && (
<div>
<label className="label">Icon</label>
<label className="form-label">Icon</label>
<AvatarForm
site={site.id}
name={site.name}
@ -69,7 +69,7 @@ export default function SiteSettingsGeneralPage() {
<Input required label="Name" id="name" {...form.register("name")} />
</div>
<div className="mt-5">
<label htmlFor="description" className="label">
<label htmlFor="description" className="form-label">
Description
</label>
<textarea