Skip to content

Commit 4b0773d

Browse files
committed
feat: add code blocks with syntax highlighting
1 parent 6599693 commit 4b0773d

7 files changed

Lines changed: 254 additions & 35 deletions

File tree

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@
1111
"goober": "^2.1.18",
1212
"preact": "^10.27.2",
1313
"quill": "^2.0.3",
14-
"sn-extension-api": "0.4.0"
14+
"sn-extension-api": "0.4.0",
15+
"highlight.js": "^11.11.1"
1516
},
1617
"devDependencies": {
1718
"@babel/core": "^7.28.5",

pnpm-lock.yaml

Lines changed: 31 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/QuillEditor.tsx

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,47 @@ import {styled} from "goober";
33

44
import Quill from 'quill';
55
import './quill.css';
6+
import hljs from 'highlight.js';
7+
import './hljs.css';
8+
9+
const CODE_LANGUAGES = [
10+
{ key: 'plain', label: 'Plain' },
11+
{ key: 'asciidoc', label: 'Asciidoc' },
12+
{ key: 'bash', label: 'Bash' },
13+
{ key: 'c', label: 'C' },
14+
{ key: 'cpp', label: 'C++' },
15+
{ key: 'csharp', label: 'C#' },
16+
{ key: 'css', label: 'CSS' },
17+
{ key: 'csv', label: 'CSV' },
18+
{ key: 'diff', label: 'Diff' },
19+
{ key: 'elixir', label: 'Elixir' },
20+
{ key: 'go', label: 'Go' },
21+
{ key: 'html', label: 'HTML' },
22+
{ key: 'java', label: 'Java' },
23+
{ key: 'javascript', label: 'JavaScript' },
24+
{ key: 'json', label: 'JSON' },
25+
{ key: 'jsx', label: 'JSX' },
26+
{ key: 'kotlin', label: 'Kotlin' },
27+
{ key: 'lua', label: 'Lua' },
28+
{ key: 'markdown', label: 'Markdown' },
29+
{ key: 'nix', label: 'Nix' },
30+
{ key: 'perl', label: 'Perl' },
31+
{ key: 'php', label: 'PHP' },
32+
{ key: 'python', label: 'Python' },
33+
{ key: 'ruby', label: 'Ruby' },
34+
{ key: 'rust', label: 'Rust' },
35+
{ key: 'scss', label: 'SCSS' },
36+
{ key: 'shell', label: 'Shell' },
37+
{ key: 'solidity', label: 'Solidity' },
38+
{ key: 'sql', label: 'SQL' },
39+
{ key: 'swift', label: 'Swift' },
40+
{ key: 'toml', label: 'TOML' },
41+
{ key: 'tsx', label: 'TSX' },
42+
{ key: 'typescript', label: 'TypeScript' },
43+
{ key: 'xml', label: 'XML' },
44+
{ key: 'yaml', label: 'YAML' },
45+
{ key: 'zig', label: 'Zig' },
46+
];
647
import {MarkdownShortcuts} from "./quill-markdown";
748
import snApi from "sn-extension-api";
849
import {getPreviewText} from "./utils";
@@ -22,6 +63,8 @@ Quill.register(DividerBlot as any, true);
2263

2364
const icons = Quill.import('ui/icons') as Record<string, string>;
2465
icons.divider = '<svg viewBox="0 0 18 18" class="ql-fill"><rect height="2" width="14" x="2" y="8"></rect></svg>';
66+
// https://github.com/slab/quill/pull/3917
67+
icons['code-block'] = '<svg viewBox="0 0 18 18" transform="scale(0.8)"> <polyline class="ql-even ql-stroke" points="5 7 3 9 5 11" transform="scale(0.9)"></polyline> <polyline class="ql-even ql-stroke" points="13 7 15 9 13 11" transform="scale(0.9)"></polyline> <line class="ql-stroke" x1="10" x2="8" y1="5" y2="13" transform="scale(0.9)"></line> <path d="M0.868784,12.949447v4.05075l16.147395-.000001v-16.098555c0,0-2.647404,0-3.782006,0" fill="none" stroke-linecap="round" stroke-linejoin="round" class="ql-stroke" style="stroke-width: 2;"></path></svg>';
2568

2669
const Container = styled('div')`
2770
position: absolute;
@@ -33,14 +76,19 @@ const Container = styled('div')`
3376
flex-direction: column;
3477
`;
3578

79+
const HLJS_MAX_LENGTH = 5000;
80+
3681
const QuillEditor = () => {
3782
let quill;
3883
useEffect(() => {
84+
const initialText = snApi.text;
85+
const enableSyntax = !initialText || initialText.length <= HLJS_MAX_LENGTH;
86+
3987
quill = new Quill(`#quill`, {
4088
readOnly: snApi.locked,
4189
modules: {
4290
toolbar: [
43-
[{'font': Font.whitelist}, {'header': '1'}, {'header': '2'}, 'bold', 'italic', 'underline', 'strike', 'blockquote', 'code', 'link', 'image', 'divider', {'list': 'ordered'}, {'list': 'bullet'}, {'align': []}, {'color': []}, {'background': []}, 'clean'],
91+
[{'font': Font.whitelist}, {'header': '1'}, {'header': '2'}, 'bold', 'italic', 'underline', 'strike', 'blockquote', 'code', 'code-block', 'link', 'image', 'divider', {'list': 'ordered'}, {'list': 'bullet'}, {'align': []}, {'color': []}, {'background': []}, 'clean'],
4492
],
4593
keyboard: {
4694
bindings: {
@@ -57,10 +105,10 @@ const QuillEditor = () => {
57105
}
58106
},
59107
markdown: {},
108+
syntax: enableSyntax ? { hljs, languages: CODE_LANGUAGES } : false,
60109
},
61110
theme: 'snow',
62111
});
63-
const initialText = snApi.text;
64112
if (initialText) {
65113
try {
66114
const data = JSON.parse(initialText);

src/demo/test-data.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,29 @@ export const RICH = {
147147
},
148148
'insert': 'Red Background'
149149
},
150+
{
151+
'insert': '\n\n'
152+
},
153+
{
154+
'attributes': {
155+
},
156+
'insert': 'const language = "JavaScript";'
157+
},
158+
{
159+
'attributes': {
160+
'code-block': 'javascript'
161+
},
162+
'insert': '\n'
163+
},
164+
{
165+
'insert': 'console.log("I love " + language + "!");'
166+
},
167+
{
168+
'attributes': {
169+
'code-block': true
170+
},
171+
'insert': '\n'
172+
},
150173
{
151174
'insert': '\n'
152175
}

src/hljs.css

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
pre code.hljs {
2+
display: block;
3+
overflow-x: auto;
4+
padding: 1em
5+
}
6+
7+
code.hljs {
8+
padding: 3px 5px
9+
}
10+
11+
.hljs {
12+
color: var(--sn-stylekit-foreground-color);
13+
background: var(--sn-stylekit-background-color)
14+
}
15+
16+
.hljs-comment,
17+
.hljs-quote {
18+
color: var(--sn-stylekit-passive-color-1);
19+
font-style: italic
20+
}
21+
22+
.hljs-doctag,
23+
.hljs-formula,
24+
.hljs-keyword {
25+
color: var(--sn-stylekit-accessory-tint-color-4)
26+
}
27+
28+
.hljs-deletion,
29+
.hljs-name,
30+
.hljs-section,
31+
.hljs-selector-tag,
32+
.hljs-subst {
33+
color: var(--sn-stylekit-accessory-tint-color-2)
34+
}
35+
36+
.hljs-literal {
37+
color: var(--sn-stylekit-accessory-tint-color-1)
38+
}
39+
40+
.hljs-addition,
41+
.hljs-attribute,
42+
.hljs-meta .hljs-string,
43+
.hljs-regexp,
44+
.hljs-string {
45+
color: var(--sn-stylekit-accessory-tint-color-5)
46+
}
47+
48+
.hljs-attr,
49+
.hljs-number,
50+
.hljs-selector-attr,
51+
.hljs-selector-class,
52+
.hljs-selector-pseudo,
53+
.hljs-template-variable,
54+
.hljs-type,
55+
.hljs-variable {
56+
color: var(--sn-stylekit-accessory-tint-color-6)
57+
}
58+
59+
.hljs-bullet,
60+
.hljs-link,
61+
.hljs-meta,
62+
.hljs-selector-id,
63+
.hljs-symbol,
64+
.hljs-title {
65+
color: var(--sn-stylekit-accessory-tint-color-1)
66+
}
67+
68+
.hljs-built_in,
69+
.hljs-class .hljs-title,
70+
.hljs-title.class_ {
71+
color: var(--sn-stylekit-accessory-tint-color-3)
72+
}
73+
74+
.hljs-emphasis {
75+
font-style: italic
76+
}
77+
78+
.hljs-strong {
79+
font-weight: 700
80+
}
81+
82+
.hljs-link {
83+
text-decoration: underline
84+
}

0 commit comments

Comments
 (0)