Skip to content

Commit 3e61df6

Browse files
authored
Merge pull request #187 from code-hike/more-playground
Better playground
2 parents 39b6a82 + 93f52c6 commit 3e61df6

File tree

13 files changed

+470
-55
lines changed

13 files changed

+470
-55
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"scripts": {
1212
"build": "lerna run --stream build",
1313
"build:lib": "lerna run --stream --scope @*/mdx build",
14+
"build:playground": "lerna run --stream --scope playground build",
1415
"test": "lerna run --stream test",
1516
"dev": "lerna run --stream --scope @*/mdx dev",
1617
"release": "auto shipit"

packages/mdx/dev/content/markdown.mdx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
Test nested syntax highlighting
2+
3+
````md hi.md
4+
# Hi
5+
6+
```js
7+
export default {
8+
data() {
9+
return {
10+
greeting: "Hello World!",
11+
}
12+
},
13+
}
14+
```
15+
16+
[lorem](https://loremipsum.com)
17+
````
18+
19+
```js hi.js
20+
export default {
21+
data() {
22+
return {
23+
greeting: "Hello World!",
24+
}
25+
},
26+
}
27+
```

packages/mdx/package.json

+1
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"scripts": {
3535
"dev": "next",
3636
"build": "rollup -c rollup.config.js",
37+
"watch": "rollup --watch -c rollup.config.js",
3738
"test": "vitest",
3839
"coverage": "vitest run --coverage"
3940
},

packages/mdx/src/highlighter/index.tsx

+7-1
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,17 @@ export async function highlight({
3333
}
3434
}
3535
if (highlighterPromise === null) {
36+
const isBrowser = typeof window !== "undefined"
37+
// if we are on the server we load all the languages
38+
// if we are on the browser just load the first language
39+
// subsequent calls with different languages will lazy load
40+
const langs = isBrowser ? [lang as Lang] : undefined
41+
3642
// TODO add version
3743
setCDN("https://unpkg.com/shiki/")
3844
highlighterPromise = getHighlighter({
3945
theme: theme as IShikiTheme,
40-
// langs: [lang as Lang], // TODO change lang from string to Lang
46+
langs,
4147
})
4248
}
4349

playground/package.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
"@code-hike/mdx": "^0.5.1",
1212
"@mdx-js/mdx": "^2.1.1",
1313
"@monaco-editor/react": "^4.4.5",
14+
"lz-string": "^1.4.4",
1415
"react": "^17.0.2",
1516
"react-dom": "^17.0.2",
16-
"react-error-boundary": "^3.1.4"
17+
"react-error-boundary": "^3.1.4",
18+
"react-split-pane": "^0.1.92"
1719
},
1820
"devDependencies": {
1921
"@types/react": "^17.0.2",

playground/src/app.jsx

+46-7
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,53 @@
1-
import { useState } from "react";
1+
import React, { useState } from "react";
22
import { Editor } from "./editor";
3+
import { readHash, writeHash } from "./hash";
34
import { Preview } from "./preview";
5+
import SplitPane from "react-split-pane";
6+
import "./resizer.css";
47

58
const defaultCode = `
69
# Hello
710
811
Edit me.
912
10-
~~~python hello.py
13+
\`\`\`python hello.py
1114
# mark[16:24]
1215
print("This is Code Hike")
13-
~~~
16+
\`\`\`
1417
1518
`;
1619

20+
const defaultCss = `.preview-container {
21+
margin: 8px;
22+
}`;
23+
1724
function App() {
18-
const [code, setCode] = useState(defaultCode);
25+
const data = React.useMemo(() => {
26+
const input = readHash() || {
27+
mdx: defaultCode,
28+
css: defaultCss,
29+
config: {
30+
lineNumbers: false,
31+
showCopyButton: false,
32+
theme: "material-darker",
33+
},
34+
};
35+
const params = new URL(location).searchParams;
36+
const standalonePreview = !!params.get("preview");
37+
return { input, standalonePreview };
38+
}, []);
39+
const [input, setInput] = useState(data.input);
1940

20-
return (
41+
React.useEffect(() => {
42+
writeHash(input);
43+
}, [input]);
44+
45+
// when the width changes we want to re-render the preview
46+
const [refreshKey, setRefreshKey] = useState(0);
47+
48+
return data.standalonePreview ? (
49+
<Preview input={input} standalone={true} />
50+
) : (
2151
<div className="app">
2252
<header>
2353
<a className="code-hike" href="https://codehike.org">
@@ -27,10 +57,19 @@ function App() {
2757
<span>v0.5.1</span>
2858
</h1>
2959
</a>
60+
<a href="https://codehike.org/docs">Docs</a>
61+
<a href="https://codehike.org/#demos">Demos</a>
3062
</header>
3163
<main>
32-
<Editor setCode={setCode} defaultCode={defaultCode} />
33-
<Preview code={code} />
64+
<SplitPane
65+
split="vertical"
66+
minSize={200}
67+
defaultSize="50%"
68+
onDragFinished={(e) => setRefreshKey(e)}
69+
>
70+
<Editor setInput={setInput} input={input} />
71+
<Preview input={input} refreshKey={refreshKey} />
72+
</SplitPane>
3473
</main>
3574
</div>
3675
);

playground/src/editor.jsx

+88-25
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,30 @@
11
import MonacoEditor from "@monaco-editor/react";
22
import { useState } from "react";
3+
import { themeList } from "./themes";
34

4-
export function Editor({ setCode, defaultCode }) {
5-
function handleEditorChange(value, event) {
6-
setCode(value);
7-
}
5+
const tabs = {
6+
mdx: {
7+
lang: "markdown",
8+
code: (input) => input.mdx,
9+
},
10+
css: {
11+
lang: "css",
12+
code: (input) => input.css,
13+
},
14+
config: {
15+
lang: "json",
16+
code: (input) => JSON.stringify(input.config, null, 2),
17+
},
18+
};
819

20+
export function Editor({ setInput, input }) {
921
const [tab, setTab] = useState("mdx");
22+
23+
function handleEditorChange(code) {
24+
let value = code;
25+
setInput((prev) => ({ ...prev, [tab]: value }));
26+
}
27+
1028
return (
1129
<div className="editor-side">
1230
<nav>
@@ -32,27 +50,72 @@ export function Editor({ setCode, defaultCode }) {
3250
Config
3351
</span>
3452
</nav>
35-
<MonacoEditor
36-
className="editor"
37-
onChange={handleEditorChange}
38-
defaultLanguage="markdown"
39-
theme="vs-dark"
40-
defaultValue={defaultCode}
41-
options={{
42-
// https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IEditorConstructionOptions.html
43-
minimap: {
44-
enabled: false,
45-
},
46-
lineNumbers: "off",
47-
scrollBeyondLastLine: false,
48-
hideCursorInOverviewRuler: true,
49-
matchBrackets: false,
50-
overviewRulerBorder: false,
51-
renderLineHighlight: "none",
52-
wordWrap: "on",
53-
tabSize: 2,
54-
}}
55-
/>
53+
{tab === "config" ? (
54+
<ConfigEditor config={input.config} onChange={handleEditorChange} />
55+
) : (
56+
<div className="editor">
57+
<MonacoEditor
58+
onChange={handleEditorChange}
59+
theme="vs-dark"
60+
path={tab}
61+
defaultLanguage={tabs[tab].lang}
62+
defaultValue={tabs[tab].code(input)}
63+
options={{
64+
// https://microsoft.github.io/monaco-editor/api/interfaces/monaco.editor.IEditorConstructionOptions.html
65+
minimap: {
66+
enabled: false,
67+
},
68+
lineNumbers: "off",
69+
scrollBeyondLastLine: false,
70+
hideCursorInOverviewRuler: true,
71+
matchBrackets: false,
72+
overviewRulerBorder: false,
73+
renderLineHighlight: "none",
74+
wordWrap: "on",
75+
tabSize: 2,
76+
}}
77+
/>
78+
</div>
79+
)}
5680
</div>
5781
);
5882
}
83+
84+
function ConfigEditor({ config, onChange }) {
85+
return (
86+
<form className="editor config-editor">
87+
<label>
88+
<input
89+
type="checkbox"
90+
checked={config.lineNumbers}
91+
onChange={(e) =>
92+
onChange({ ...config, lineNumbers: e.target.checked })
93+
}
94+
/>
95+
Line Numbers
96+
</label>
97+
<label>
98+
<input
99+
type="checkbox"
100+
checked={config.showCopyButton}
101+
onChange={(e) =>
102+
onChange({ ...config, showCopyButton: e.target.checked })
103+
}
104+
/>
105+
Copy Button
106+
</label>
107+
<label>
108+
Theme:
109+
<br />
110+
<select
111+
value={config.theme}
112+
onChange={(e) => onChange({ ...config, theme: e.target.value })}
113+
>
114+
{themeList().map((theme) => (
115+
<option key={theme}>{theme}</option>
116+
))}
117+
</select>
118+
</label>
119+
</form>
120+
);
121+
}

playground/src/hash.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import LZString from "lz-string";
2+
3+
export function toHash(input) {
4+
return LZString.compressToEncodedURIComponent(JSON.stringify(input));
5+
}
6+
7+
export function fromHash(hash) {
8+
return JSON.parse(LZString.decompressFromEncodedURIComponent(hash));
9+
}
10+
11+
export function writeHash(input) {
12+
const hash = toHash(input);
13+
window.location.hash = hash;
14+
}
15+
16+
export function readHash() {
17+
const hash = document.location.hash.slice(1);
18+
if (!hash) {
19+
return null;
20+
}
21+
22+
try {
23+
return fromHash(hash);
24+
} catch {
25+
return {};
26+
}
27+
}

0 commit comments

Comments
 (0)