Skip to content
This repository was archived by the owner on Dec 5, 2024. It is now read-only.

Commit d87f3b9

Browse files
committed
feat(mdx): add executable-code syntax
1 parent f28d3f7 commit d87f3b9

File tree

12 files changed

+201
-10
lines changed

12 files changed

+201
-10
lines changed

package-lock.json

+5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,8 @@
3939
"lerna": "^3.22.1",
4040
"lint-staged": "^10.5.3",
4141
"prettier": "^2.2.1"
42+
},
43+
"dependencies": {
44+
"vm-browserify": "^1.1.2"
4245
}
4346
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
.executable-code-container {
2+
display: flex;
3+
order: 1; /* TODO: fix */
4+
flex-direction: column;
5+
margin: 0 auto;
6+
min-width: 50%;
7+
max-width: 90%;
8+
}
9+
10+
.executable-code-button {
11+
border: 1px solid rgba(0, 20, 80, 0.1);
12+
box-shadow: 0 8px 16px rgba(0, 20, 80, 0.04), 0 4px 16px rgba(0, 0, 0, 0.08);
13+
border-radius: 4px;
14+
color: var(--color-black);
15+
cursor: pointer;
16+
font-size: 1.8rem;
17+
padding: 0.5rem;
18+
width: 100px;
19+
}
20+
21+
.executable-code-result {
22+
max-height: 200px;
23+
overflow-x: auto;
24+
text-align: left;
25+
padding: 1.2rem 0.2rem;
26+
border: 1px solid rgba(0, 20, 80, 0.1);
27+
box-shadow: 0 8px 16px rgba(0, 20, 80, 0.04), 0 4px 16px rgba(0, 0, 0, 0.08);
28+
margin-top: 0.4rem;
29+
30+
& > p {
31+
margin-left: 12px;
32+
font-size: 2rem;
33+
white-space: nowrap;
34+
}
35+
}

packages/client/src/components/ContentView/Base.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useEffect, memo } from 'react';
22
import Prism from 'prismjs';
33
import classnames from 'classnames';
44
import { setup as setupWebSlides } from '../../setup/webSlides';
5+
import { createVMEnv } from '../../utils/createVMEnv';
56

67
const articleClass = process.env.IS_VERTICAL ? 'vertical' : undefined;
78
let mermaid = null;
@@ -46,6 +47,14 @@ export const Base = memo(
4647
Prism.highlightAll();
4748
}, 0);
4849
}
50+
51+
const hasExecutableCode = slides.some(
52+
({ fusumaProps }) => fusumaProps.hasExecutableCode === 'true'
53+
);
54+
55+
if (hasExecutableCode) {
56+
createVMEnv();
57+
}
4958
}
5059

5160
useEffect(() => {
@@ -79,7 +88,7 @@ export const Base = memo(
7988
<article className={articleClass} id="webslides">
8089
{slides.map(({ slide: Slide, fusumaProps }, i) => (
8190
<section
82-
key={i}
91+
key={i /* mdx-loaderでhash作成 */}
8392
className={classnames(
8493
'aligncenter',
8594
fusumaProps.classes,

packages/client/src/hooks/useSlides.js

+1-8
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,5 @@ import { useMemo } from 'react';
22
import { createSlidesProps } from '../utils/createSlidesProps';
33

44
export function useSlidesProps({ originalSlides, hash, currentIndex }) {
5-
const createdProps = useMemo(() => createSlidesProps(originalSlides, currentIndex), [hash]);
6-
const slides = useMemo(() => createdProps.slides, [hash]);
7-
const contentsList = useMemo(() => createdProps.contentsList, [hash]);
8-
9-
return {
10-
slides,
11-
contentsList,
12-
};
5+
return useMemo(() => createSlidesProps(originalSlides, currentIndex), [hash]);
136
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
export async function createVMEnv() {
2+
await import('../../assets/style/executableCode.css');
3+
4+
const { createContext, runInNewContext } = await import('vm-browserify');
5+
6+
Array.from(document.querySelectorAll('.executable-code-button')).forEach((el) => {
7+
el.addEventListener('click', (e) => {
8+
const list = e.target.nextSibling;
9+
10+
if (list.style.display === 'none') {
11+
list.style.display = 'block';
12+
}
13+
const context = createContext({
14+
console: {
15+
log: (res) => {
16+
const p = document.createElement('p');
17+
18+
p.innerText = `- ${res}`;
19+
list.appendChild(p);
20+
},
21+
},
22+
});
23+
24+
runInNewContext(e.target.dataset.value, context);
25+
});
26+
});
27+
}

packages/mdx-loader/__tests__/__snapshots__/index.js.snap

+57
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,63 @@ in\`}</p>
177177
MDXContent.isMDXComponent = true;"
178178
`;
179179

180+
exports[`fusuma-loader should append executable code components 1`] = `
181+
"/* @jsxRuntime classic */
182+
/* @jsx mdx */
183+
184+
import React from 'react';
185+
import { mdx } from '@mdx-js/react';
186+
export const slides = [props => <>
187+
188+
{
189+
/* executable-code */
190+
}
191+
<div className=\\"executable-code-container\\">
192+
<a data-value=\\"const a = 1;
193+
const b = 2;
194+
console.log(a + b);\\" className=\\"executable-code-button\\">execute</a>
195+
<div className=\\"executable-code-result\\" style={{
196+
display: 'none'
197+
}}></div>
198+
</div>
199+
<pre><code parentName=\\"pre\\" {...{
200+
\\"className\\": \\"language-javascript\\"
201+
}}>{\`const a = 1;
202+
const b = 2;
203+
console.log(a + b);
204+
\`}</code></pre>
205+
206+
</>];
207+
export const fusumaProps = [{
208+
hasExecutableCode: 'true'
209+
}];
210+
211+
const layoutProps = {
212+
213+
};
214+
const MDXLayout = \\"wrapper\\"
215+
export default function MDXContent({
216+
components,
217+
...props
218+
}) {
219+
return <MDXLayout {...layoutProps} {...props} components={components} mdxType=\\"MDXLayout\\">
220+
{
221+
/* executable-code */
222+
}
223+
<pre><code parentName=\\"pre\\" {...{
224+
\\"className\\": \\"language-javascript\\"
225+
}}>{\`const a = 1;
226+
const b = 2;
227+
console.log(a + b);
228+
\`}</code></pre>
229+
230+
</MDXLayout>;
231+
}
232+
233+
;
234+
MDXContent.isMDXComponent = true;"
235+
`;
236+
180237
exports[`fusuma-loader should convert MathJax 1`] = `
181238
"/* @jsxRuntime classic */
182239
/* @jsx mdx */

packages/mdx-loader/__tests__/index.js

+13
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,19 @@ in
163163
<!-- block-end -->
164164
out
165165
<!-- block-end -->
166+
`;
167+
168+
expect(await transformToJS(src)).toMatchSnapshot();
169+
});
170+
171+
test('should append executable code components', async () => {
172+
const src = `
173+
<!-- executable-code -->
174+
\`\`\`javascript
175+
const a = 1;
176+
const b = 2;
177+
console.log(a + b);
178+
\`\`\`
166179
`;
167180

168181
expect(await transformToJS(src)).toMatchSnapshot();

packages/mdx-loader/src/createFusumaProps.js

+4
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ function createFusumaProps(nodes) {
1717
if (type === 'comment') {
1818
const v = value.trim();
1919

20+
if (v === 'executable-code') {
21+
property.hasExecutableCode = true;
22+
}
23+
2024
if (v === 'contents') {
2125
property.contents = true;
2226
}

packages/mdx-loader/src/mdxPlugin.js

+20-1
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const transformQrToJSX = require('./transformers/transformQrToJSX');
88
const transformScreenToJSX = require('./transformers/transformScreenToJSX');
99
const transformChartToJSX = require('./transformers/transformChartToJSX');
1010
const transformMarkdownImageNodeToJSX = require('./transformers/transformMarkdownImageNodeToJSX');
11+
const transformExecJSCodeButtonToJSX = require('./transformers/transformExecJSCodeButtonToJSX');
1112

1213
function mdxPlugin() {
1314
return (tree) => {
@@ -21,7 +22,7 @@ function mdxPlugin() {
2122
};
2223

2324
// TODO: refactor using visit
24-
tree.children.forEach((n) => {
25+
tree.children.forEach((n, i) => {
2526
const { type, value, lang, meta } = n;
2627

2728
// move to a new slide
@@ -62,6 +63,24 @@ function mdxPlugin() {
6263
++videoId;
6364
return;
6465
}
66+
67+
if (prefix === 'executable-code') {
68+
const nextNode = tree.children[i + 1];
69+
70+
if (nextNode.type === 'code' && ['js', 'javascript'].includes(nextNode.lang)) {
71+
slide.push(
72+
...[
73+
n,
74+
{
75+
...n,
76+
...transformExecJSCodeButtonToJSX(nextNode.value),
77+
},
78+
]
79+
);
80+
}
81+
82+
return;
83+
}
6584
}
6685

6786
if (type === 'code') {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
'use strict';
2+
3+
// add display:none to here, not css
4+
function transformExecJSCodeButtonToJSX(value) {
5+
return {
6+
type: 'jsx',
7+
value: `<div className="executable-code-container">
8+
<a data-value="${value}" className="executable-code-button">execute</a>
9+
<div className="executable-code-result" style={{ display: 'none' }}></div>
10+
</div>`,
11+
};
12+
}
13+
14+
module.exports = transformExecJSCodeButtonToJSX;

samples/debug/slides/07-run-js.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
## Execute JavaScript
2+
3+
<!-- executable-code -->
4+
5+
```js
6+
console.log('yay');
7+
8+
const a = 1;
9+
const b = 2;
10+
11+
console.log(a + b);
12+
```

0 commit comments

Comments
 (0)