Skip to content

Commit 0d726c0

Browse files
committed
feat(repl): formatting codeblocks
1 parent fb800fb commit 0d726c0

File tree

3 files changed

+113
-13
lines changed

3 files changed

+113
-13
lines changed

packages/docs/src/components/code-block/code-block.css

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -278,3 +278,33 @@ pre[class*='language-'] > code[class*='language-'] {
278278
box-shadow: inset 5px 0 0 #f7d87c;
279279
z-index: 0;
280280
}
281+
282+
/* Prettier Toggle Styles */
283+
.prettier-toggle {
284+
cursor: pointer;
285+
display: inline-block;
286+
position: absolute;
287+
top: 30px;
288+
right: 9px;
289+
z-index: 10;
290+
}
291+
292+
.prettier-toggle span {
293+
display: inline-block;
294+
padding: 0px 4px;
295+
background: #1e1e1e;
296+
border: 1px solid #d4d4d4;
297+
color: #d4d4d4;
298+
font-weight: normal;
299+
font-size: 14px;
300+
border-radius: 4px;
301+
}
302+
303+
.prettier-toggle span.checked {
304+
background: #264f78;
305+
color: #ffffff;
306+
font-weight: bold;
307+
}
308+
.prettier-toggle span.checked.error {
309+
background: #db4c69;
310+
}
Lines changed: 82 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,76 @@
1-
import { component$, useSignal, useStyles$, useVisibleTask$, type QRL } from '@builder.io/qwik';
1+
import {
2+
component$,
3+
useSignal,
4+
useStyles$,
5+
useTask$,
6+
useVisibleTask$,
7+
type QRL,
8+
type Signal,
9+
} from '@builder.io/qwik';
210
import { CopyCode } from '../copy-code/copy-code-block';
311
import styles from './code-block.css?inline';
412
import { shikiInstance, SHIKI_THEME, type ShikiLangs } from './shiki-config';
13+
import { format } from 'prettier/standalone';
14+
import parserHtml from 'prettier/plugins/html';
15+
import parserTs from 'prettier/plugins/typescript';
16+
import parserEstree from 'prettier/plugins/estree';
517

618
interface CodeBlockProps {
719
path?: string;
820
language?: ShikiLangs;
921
code: string;
22+
format?: boolean;
1023
pathInView$?: QRL<(name: string) => void>;
1124
observerRootId?: string;
1225
}
1326

1427
export const CodeBlock = component$((props: CodeBlockProps) => {
1528
const listSig = useSignal<Element>();
29+
const codeSig = useSignal<string | null>(props.format ? null : props.code);
30+
const formatSig = useSignal(!!props.format);
31+
const formatError = useSignal<string>();
32+
33+
const language =
34+
props.language ||
35+
(props.path
36+
? /\.([cm]?[jt]sx?|json)$/.test(props.path)
37+
? 'javascript'
38+
: props.path.endsWith('.html')
39+
? 'html'
40+
: null
41+
: null);
42+
1643
useStyles$(styles);
1744

45+
useTask$(async ({ track }) => {
46+
track(() => props.code);
47+
track(formatSig);
48+
49+
if (formatSig.value) {
50+
try {
51+
// simple formatting for html and js
52+
if (language === 'html') {
53+
codeSig.value = await format(props.code, {
54+
parser: 'html',
55+
plugins: [parserHtml],
56+
htmlWhitespaceSensitivity: 'ignore',
57+
});
58+
} else if (language === 'javascript') {
59+
codeSig.value = await format(props.code, {
60+
parser: 'typescript',
61+
plugins: [parserTs, parserEstree],
62+
});
63+
}
64+
formatError.value = undefined;
65+
} catch (e: any) {
66+
formatError.value = e.message;
67+
codeSig.value = props.code;
68+
}
69+
} else {
70+
codeSig.value = props.code;
71+
}
72+
});
73+
1874
useVisibleTask$(async () => {
1975
const { pathInView$, path, observerRootId } = props;
2076
if (pathInView$ && path && listSig.value !== undefined) {
@@ -33,23 +89,37 @@ export const CodeBlock = component$((props: CodeBlockProps) => {
3389
}
3490
});
3591

36-
let language = props.language;
37-
if (!language && props.path && props.code) {
38-
const ext = props.path.split('.').pop();
39-
language = ext === 'js' || ext === 'json' ? 'javascript' : ext === 'css' ? 'css' : 'html';
40-
}
41-
42-
const highlighted = shikiInstance.codeToHtml(props.code, {
43-
lang: language!,
44-
theme: SHIKI_THEME,
45-
});
92+
const highlighted =
93+
codeSig.value != null &&
94+
shikiInstance.codeToHtml(codeSig.value, {
95+
lang: language!,
96+
theme: SHIKI_THEME,
97+
});
4698
const className = `language-${language}`;
4799
return (
48100
<div class="relative">
49101
<pre class={className} ref={listSig}>
50-
<code class={className} dangerouslySetInnerHTML={highlighted} />
102+
{highlighted && <code class={className} dangerouslySetInnerHTML={highlighted} />}
51103
</pre>
104+
{(language === 'html' || language === 'javascript') && (
105+
<PrettierToggle bind:value={formatSig} />
106+
)}
52107
<CopyCode code={props.code} />
53108
</div>
54109
);
55110
});
111+
112+
const PrettierToggle = component$((props: { 'bind:value': Signal<boolean>; error?: string }) => {
113+
return (
114+
<label
115+
class="prettier-toggle"
116+
title={`Toggle Prettier ${props.error ? `\n${props.error}` : ''}`}
117+
aria-label="Toggle Prettier"
118+
>
119+
<input type="checkbox" bind:checked={props['bind:value']} style="display: none;" />
120+
<span class={[props['bind:value'].value ? 'checked' : '', props.error ? 'error' : '']}>
121+
P
122+
</span>
123+
</label>
124+
);
125+
});

packages/docs/src/repl/ui/repl-output-panel.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ export const ReplOutputPanel = component$(({ input, store }: ReplOutputPanelProp
111111

112112
{store.selectedOutputPanel === 'html' ? (
113113
<div class="output-result output-html">
114-
<CodeBlock language="html" code={store.html} />
114+
<CodeBlock language="html" format code={store.html} />
115115
</div>
116116
) : null}
117117

0 commit comments

Comments
 (0)