From 00c6a2685a611a563867a8c7466cbd30b12ebe63 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Sat, 9 Mar 2024 12:30:23 +0000 Subject: [PATCH 1/8] Added Editor component --- apps/total-typescript/package.json | 1 + .../components/code-editor/code-editor.tsx | 103 +++++++++++++++++ .../code-editor/lazy-loaded-editor.tsx | 22 ++++ .../src/components/code-editor/load-script.ts | 16 +++ .../components/code-editor/prettier-loader.ts | 56 ++++++++++ .../src/components/mdx/index.tsx | 36 +++--- .../src/pages/editor-test.tsx | 105 ++++++++++++++++++ 7 files changed, 322 insertions(+), 17 deletions(-) create mode 100644 apps/total-typescript/src/components/code-editor/code-editor.tsx create mode 100644 apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx create mode 100644 apps/total-typescript/src/components/code-editor/load-script.ts create mode 100644 apps/total-typescript/src/components/code-editor/prettier-loader.ts create mode 100644 apps/total-typescript/src/pages/editor-test.tsx diff --git a/apps/total-typescript/package.json b/apps/total-typescript/package.json index b875621011..da1f833c2a 100644 --- a/apps/total-typescript/package.json +++ b/apps/total-typescript/package.json @@ -52,6 +52,7 @@ "@mdx-js/loader": "^2.3.0", "@mdx-js/mdx": "^2.3.0", "@mdx-js/react": "^2.3.0", + "@monaco-editor/react": "^4.6.0", "@mux/mux-node": "^7.3.1", "@mux/mux-player-react": "2.3.2", "@next/mdx": "^14.1.3", diff --git a/apps/total-typescript/src/components/code-editor/code-editor.tsx b/apps/total-typescript/src/components/code-editor/code-editor.tsx new file mode 100644 index 0000000000..b73b7aec22 --- /dev/null +++ b/apps/total-typescript/src/components/code-editor/code-editor.tsx @@ -0,0 +1,103 @@ +import Editor from '@monaco-editor/react' +import {prettierLoader} from './prettier-loader' + +const resolveLanguage = (language: Language | undefined): Language => { + switch (language) { + case 'ts': + case undefined: + return 'typescript' + default: + return language + } +} + +export type Language = 'ts' | 'tsx' | 'typescript' | 'json' + +const THEME_NAME = 'total-typescript' + +const EDITOR_THEME = { + base: 'vs-dark' as const, + inherit: true, + rules: [], + name: THEME_NAME, + colors: {'editor.background': '#1e2632'}, +} + +export type CodeEditorProps = { + language?: Language + code: string +} + +/** + * Eagerly loaded code editor + * + * This should not be used directly, instead use ./lazy-loaded-editor.tsx + */ +export const EagerlyLoadedEditor = (props: CodeEditorProps) => { + return ( +
+ Loading Code Editor...
} + defaultValue={props.code} + language={resolveLanguage(props.language)} + options={{ + minimap: {enabled: false, showSlider: 'mouseover'}, + fontSize: 14, + glyphMargin: false, + tabSize: 2, + lineNumbers: 'off', + scrollbar: { + vertical: 'auto', + horizontal: 'auto', + }, + scrollBeyondLastLine: false, + }} + onMount={(editor, monaco) => { + // Enable prettier + monaco.languages.registerDocumentFormattingEditProvider( + 'typescript', + { + provideDocumentFormattingEdits: async (model) => { + try { + return [ + { + text: await prettierLoader.format(editor.getValue()), + range: model.getFullModelRange(), + }, + ] + } catch (err) { + console.error(err) + } finally { + } + }, + }, + ) + + // Adjust the compiler options + monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ + ...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(), + module: monaco.languages.typescript.ModuleKind.ESNext, + moduleResolution: + monaco.languages.typescript.ModuleResolutionKind.NodeJs, + strict: true, + }) + + // Define the custom theme + monaco.editor.defineTheme(THEME_NAME, EDITOR_THEME) + monaco.editor.setTheme(THEME_NAME) + + // Enable auto formatting on save + editor.addAction({ + id: 'save', + label: 'Save', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS], + run: () => { + editor.getAction('editor.action.formatDocument')?.run() + }, + }) + }} + /> + + ) +} diff --git a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx new file mode 100644 index 0000000000..66d602328f --- /dev/null +++ b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx @@ -0,0 +1,22 @@ +import React from 'react' +import type {Language} from './code-editor' + +export const LazyLoadedEditor = React.lazy(() => + import('./code-editor').then((res) => { + return { + default: res.EagerlyLoadedEditor, + } + }), +) + +export const MDXEditor = (props: { + language?: Language + code: string + children?: React.ReactNode +}) => { + return +} + +export const EditorTest = () => { + return {}`} /> +} diff --git a/apps/total-typescript/src/components/code-editor/load-script.ts b/apps/total-typescript/src/components/code-editor/load-script.ts new file mode 100644 index 0000000000..3d8caeb4ac --- /dev/null +++ b/apps/total-typescript/src/components/code-editor/load-script.ts @@ -0,0 +1,16 @@ +export const loadScript = (src: string) => { + return new Promise((resolve, reject) => { + const scriptElement = document.createElement('script') + scriptElement.src = src + + const timeout = setTimeout(() => { + reject('Timeout') + }, 15000) + + scriptElement.onload = () => { + resolve() + clearTimeout(timeout) + } + document.body.appendChild(scriptElement) + }) +} diff --git a/apps/total-typescript/src/components/code-editor/prettier-loader.ts b/apps/total-typescript/src/components/code-editor/prettier-loader.ts new file mode 100644 index 0000000000..d398caefa9 --- /dev/null +++ b/apps/total-typescript/src/components/code-editor/prettier-loader.ts @@ -0,0 +1,56 @@ +import {loadScript} from './load-script' + +declare global { + interface Window { + define: any + } +} + +const Prettier = () => { + let hasLoaded = false + + const loadPrettier = async () => { + if (hasLoaded) return + + /** + * Helps Monaco editor get over a too-sensitive error + * + * https://stackoverflow.com/questions/55057425/can-only-have-one-anonymous-define-call-per-script-file + */ + const define = window.define + window.define = () => {} + + await Promise.all([ + loadScript('https://unpkg.com/prettier@2.3.2/standalone.js'), + loadScript('https://unpkg.com/prettier@2.3.2/parser-typescript.js'), + ]) + + window.define = define + + hasLoaded = true + } + + const format = async (code: string) => { + try { + await loadPrettier() + return (window as any).prettier.format(code, { + parser: 'typescript', + plugins: (window as any).prettierPlugins, + }) + } catch (e) { + console.error(e) + } + /** + * If loading prettier fails, just + * load the code + */ + return code + } + + return { + load: loadPrettier, + format, + } +} + +export const prettierLoader = Prettier() diff --git a/apps/total-typescript/src/components/mdx/index.tsx b/apps/total-typescript/src/components/mdx/index.tsx index 054a3b3ccd..db5fecb8dd 100644 --- a/apps/total-typescript/src/components/mdx/index.tsx +++ b/apps/total-typescript/src/components/mdx/index.tsx @@ -1,26 +1,28 @@ -import React from 'react' -import Image, {ImageProps} from 'next/image' +import {isBrowser} from '@/utils/is-browser' +import {CopyToClipboard, Twitter} from '@skillrecordings/react' import cx from 'classnames' -import Balancer from 'react-wrap-balancer' -import {Twitter, CopyToClipboard} from '@skillrecordings/react' +import {type MDXComponents as MDXComponentsType} from 'mdx/types' +import Image, {ImageProps} from 'next/image' import Link from 'next/link' -import toast from 'react-hot-toast' import {useRouter} from 'next/router' -import {twMerge} from 'tailwind-merge' +import React from 'react' +import toast from 'react-hot-toast' import {useCopyToClipboard} from 'react-use' -import {isBrowser} from '@/utils/is-browser' -import {type MDXComponents as MDXComponentsType} from 'mdx/types' +import Balancer from 'react-wrap-balancer' +import {twMerge} from 'tailwind-merge' +import {MDXEditor} from '../code-editor/lazy-loaded-editor' export const MDXComponents = { - TypeError: (props: TypeErrorProps) => , - Topics: (props: TopicsProps) => , - Image: (props: any) => , - Section: (props: any) =>
, - SectionHeading: (props: any) => , - ErrorFromHell: (props: any) => , - Testimonial: (props: any) => , - Module: (props: any) => , -} + TypeError: (props) => , + Topics: (props) => , + Image: (props) => , + Section: (props) =>
, + SectionHeading: (props) => , + ErrorFromHell: (props) => , + Testimonial: (props) => , + Module: (props) => , + Editor: (props) => , +} satisfies Record> type TypeErrorProps = { children: React.ReactNode diff --git a/apps/total-typescript/src/pages/editor-test.tsx b/apps/total-typescript/src/pages/editor-test.tsx new file mode 100644 index 0000000000..fab3d1484c --- /dev/null +++ b/apps/total-typescript/src/pages/editor-test.tsx @@ -0,0 +1,105 @@ +import Layout from '@/components/app/layout' +import {EditorTest} from '@/components/code-editor/lazy-loaded-editor' + +import {cn} from '@skillrecordings/ui/utils/cn' +import Image from 'next/image' + +const title = 'How to use TypeScript with React' + +const config = { + author: 'Matt Pocock', + authorBio: 'Frontend Engineer, TypeScript Enthusiast', +} + +const isBookTeaser = true + +const ArticleTemplate: React.FC = () => { + return ( + +
+
+

+ {title} +

+
+
+
+ Matt Pocock +
+
+ {config.author} + + {config.authorBio} + +
+
+
+
+ {isBookTeaser && ( +
+ +
+ )} + {/* {image && !isBookTeaser && ( +
+ + +
+ )} */} +
+
+
+ +
+ Matt's signature +
+
+
+
+ ) +} + +export default ArticleTemplate From dbc15c6f1948fd32957d955fca9100117531657b Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Sat, 9 Mar 2024 12:33:26 +0000 Subject: [PATCH 2/8] Fixed lockfile --- pnpm-lock.yaml | 160 +++++++++++++++++++++++++++---------------------- 1 file changed, 87 insertions(+), 73 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c5b7428a55..d57a1bf26e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -3509,6 +3509,9 @@ importers: '@mdx-js/react': specifier: ^2.3.0 version: 2.3.0(react@18.2.0) + '@monaco-editor/react': + specifier: ^4.6.0 + version: 4.6.0(monaco-editor@0.47.0)(react-dom@18.2.0)(react@18.2.0) '@mux/mux-node': specifier: ^7.3.1 version: 7.3.5 @@ -7178,10 +7181,10 @@ packages: '@babel/helpers': 7.23.9 '@babel/parser': 7.23.9 '@babel/template': 7.23.9 - '@babel/traverse': 7.23.9 + '@babel/traverse': 7.23.9(supports-color@5.5.0) '@babel/types': 7.23.9 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -7203,7 +7206,7 @@ packages: '@babel/traverse': 7.24.0 '@babel/types': 7.24.0 convert-source-map: 2.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -7322,8 +7325,8 @@ packages: '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-module-imports': 7.22.15 '@babel/helper-plugin-utils': 7.22.5 - '@babel/traverse': 7.23.9 - debug: 4.3.4(supports-color@8.1.1) + '@babel/traverse': 7.23.9(supports-color@5.5.0) + debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 semver: 6.3.1 @@ -7339,7 +7342,7 @@ packages: '@babel/core': 7.23.9 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -7353,7 +7356,7 @@ packages: '@babel/core': 7.24.0 '@babel/helper-compilation-targets': 7.23.6 '@babel/helper-plugin-utils': 7.22.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 resolve: 1.22.8 transitivePeerDependencies: @@ -7514,7 +7517,7 @@ packages: engines: {node: '>=6.9.0'} dependencies: '@babel/template': 7.23.9 - '@babel/traverse': 7.23.9 + '@babel/traverse': 7.23.9(supports-color@5.5.0) '@babel/types': 7.23.9 transitivePeerDependencies: - supports-color @@ -9611,23 +9614,6 @@ packages: '@babel/parser': 7.24.0 '@babel/types': 7.24.0 - /@babel/traverse@7.23.9: - resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==} - engines: {node: '>=6.9.0'} - dependencies: - '@babel/code-frame': 7.23.5 - '@babel/generator': 7.23.6 - '@babel/helper-environment-visitor': 7.22.20 - '@babel/helper-function-name': 7.23.0 - '@babel/helper-hoist-variables': 7.22.5 - '@babel/helper-split-export-declaration': 7.22.6 - '@babel/parser': 7.23.9 - '@babel/types': 7.23.9 - debug: 4.3.4(supports-color@8.1.1) - globals: 11.12.0 - transitivePeerDependencies: - - supports-color - /@babel/traverse@7.23.9(supports-color@5.5.0): resolution: {integrity: sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==} engines: {node: '>=6.9.0'} @@ -9644,7 +9630,6 @@ packages: globals: 11.12.0 transitivePeerDependencies: - supports-color - dev: false /@babel/traverse@7.24.0: resolution: {integrity: sha512-HfuJlI8qq3dEDmNU5ChzzpZRWq+oxCZQyMzIMEqLho+AQnhMnKQUzH6ydo3RBl/YjPCuk68Y6s0Gx0AeyULiWw==} @@ -9658,7 +9643,7 @@ packages: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.24.0 '@babel/types': 7.24.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -11419,7 +11404,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.0 @@ -11436,7 +11421,7 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.0 @@ -11563,7 +11548,7 @@ packages: engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 2.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -12631,6 +12616,28 @@ packages: resolution: {integrity: sha512-Rp7ll8BHrKB3wXaRFKhrltwZl1CiXGdibPxuWXvqGnKTnv8fqa/nvftYNuSbf+pbJWKYCXdBtYTITdAUTGGh0Q==} dev: false + /@monaco-editor/loader@1.4.0(monaco-editor@0.47.0): + resolution: {integrity: sha512-00ioBig0x642hytVspPl7DbQyaSWRaolYie/UFNjoTdvoKPzo6xrXLhTk9ixgIKcLH5b5vDOjVNiGyY+uDCUlg==} + peerDependencies: + monaco-editor: '>= 0.21.0 < 1' + dependencies: + monaco-editor: 0.47.0 + state-local: 1.0.7 + dev: false + + /@monaco-editor/react@4.6.0(monaco-editor@0.47.0)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-RFkU9/i7cN2bsq/iTkurMWOEErmYcY6JiQI3Jn+WeR/FGISH8JbHERjpS9oRuSOPvDMJI0Z8nJeKkbOs9sBYQw==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 + dependencies: + '@monaco-editor/loader': 1.4.0(monaco-editor@0.47.0) + monaco-editor: 0.47.0 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@motionone/animation@10.17.0: resolution: {integrity: sha512-ANfIN9+iq1kGgsZxs+Nz96uiNcPLGTXwfNo2Xz/fcJXniPYpaz/Uyrfa+7I5BPLxCP82sh7quVDudf1GABqHbg==} dependencies: @@ -12698,7 +12705,7 @@ packages: dependencies: '@open-draft/until': 1.0.3 '@xmldom/xmldom': 0.7.13 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) headers-utils: 3.0.2 outvariant: 1.4.2 strict-event-emitter: 0.2.8 @@ -15393,7 +15400,7 @@ packages: engines: {node: '>=18'} hasBin: true dependencies: - '@babel/traverse': 7.23.9 + '@babel/traverse': 7.23.9(supports-color@5.5.0) '@sanity/telemetry': 0.7.7 chalk: 4.1.2 esbuild: 0.19.12 @@ -15411,7 +15418,7 @@ packages: engines: {node: '>=18'} hasBin: true dependencies: - '@babel/traverse': 7.23.9 + '@babel/traverse': 7.23.9(supports-color@5.5.0) '@sanity/telemetry': 0.7.7 chalk: 4.1.2 esbuild: 0.19.12 @@ -15490,7 +15497,7 @@ packages: sanity: ^3 styled-components: ^5.2 || ^6 dependencies: - '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.1)(@lezer/common@1.2.1) + '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.1)(@codemirror/view@6.23.1)(@lezer/common@1.2.1) '@codemirror/commands': 6.3.3 '@codemirror/lang-html': 6.4.8 '@codemirror/lang-java': 6.0.1 @@ -15532,7 +15539,7 @@ packages: sanity: ^3 styled-components: ^5.2 || ^6 dependencies: - '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.1)(@codemirror/view@6.23.1)(@lezer/common@1.2.1) + '@codemirror/autocomplete': 6.12.0(@codemirror/language@6.10.0)(@codemirror/state@6.4.0)(@codemirror/view@6.23.1)(@lezer/common@1.2.1) '@codemirror/commands': 6.3.3 '@codemirror/lang-html': 6.4.8 '@codemirror/lang-java': 6.0.1 @@ -15841,7 +15848,7 @@ packages: '@sanity/types': 3.27.0 '@sanity/util': 3.27.0 arrify: 2.0.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) fast-fifo: 1.3.2 groq-js: 1.4.1 p-map: 7.0.1 @@ -18986,7 +18993,7 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/type-utils': 5.62.0(eslint@8.56.0)(typescript@5.1.6) '@typescript-eslint/utils': 5.62.0(eslint@8.56.0)(typescript@5.1.6) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 graphemer: 1.4.0 ignore: 5.3.0 @@ -19058,7 +19065,7 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.26.0 typescript: 5.1.6 transitivePeerDependencies: @@ -19078,7 +19085,7 @@ packages: '@typescript-eslint/scope-manager': 5.62.0 '@typescript-eslint/types': 5.62.0 '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 typescript: 5.1.6 transitivePeerDependencies: @@ -19099,7 +19106,7 @@ packages: '@typescript-eslint/types': 6.19.1 '@typescript-eslint/typescript-estree': 6.19.1(typescript@5.1.6) '@typescript-eslint/visitor-keys': 6.19.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 typescript: 5.1.6 transitivePeerDependencies: @@ -19133,7 +19140,7 @@ packages: dependencies: '@typescript-eslint/typescript-estree': 5.62.0(typescript@5.1.6) '@typescript-eslint/utils': 5.62.0(eslint@8.56.0)(typescript@5.1.6) - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.56.0 tsutils: 3.21.0(typescript@5.1.6) typescript: 5.1.6 @@ -19159,7 +19166,7 @@ packages: typescript: optional: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint-visitor-keys: 1.3.0 glob: 7.2.3 is-glob: 4.0.3 @@ -19181,7 +19188,7 @@ packages: dependencies: '@typescript-eslint/types': 5.62.0 '@typescript-eslint/visitor-keys': 5.62.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 semver: 7.5.4 @@ -19201,7 +19208,7 @@ packages: dependencies: '@typescript-eslint/types': 6.19.1 '@typescript-eslint/visitor-keys': 6.19.1 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -19250,7 +19257,7 @@ packages: resolution: {integrity: sha512-kTwMUQ8xtAZaC4wb2XuLkPqFVBj2dNBueMQ89NWEuw87k2nLBbuafeG5cob/QEr6YduxIdTVUjix0MtC7mPlmg==} dependencies: '@typescript/vfs': 1.3.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) lz-string: 1.5.0 transitivePeerDependencies: - supports-color @@ -19259,7 +19266,7 @@ packages: /@typescript/vfs@1.3.4: resolution: {integrity: sha512-RbyJiaAGQPIcAGWFa3jAXSuAexU4BFiDRF1g3hy7LmRqfNpYlTQWGXjcrOaVZjJ8YkkpuwG0FcsYvtWQpd9igQ==} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -19267,7 +19274,7 @@ packages: /@typescript/vfs@1.3.5: resolution: {integrity: sha512-pI8Saqjupf9MfLw7w2+og+fmb0fZS0J6vsKXXrp4/PDXEFvntgzXmChCXC/KefZZS0YGS6AT8e0hGAJcTsdJlg==} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -19686,7 +19693,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -19694,7 +19701,7 @@ packages: resolution: {integrity: sha512-o/zjMZRhJxny7OyEF+Op8X+efiELC7k7yOjMzgfzVqOzXqkBkWI79YoTdOtsuWd5BWhAGAuOY/Xa6xpiaWXiNg==} engines: {node: '>= 14'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -20272,7 +20279,7 @@ packages: dependencies: '@babel/code-frame': 7.23.5 '@babel/parser': 7.23.9 - '@babel/traverse': 7.23.9 + '@babel/traverse': 7.23.9(supports-color@5.5.0) '@babel/types': 7.23.9 eslint: 8.56.0 eslint-visitor-keys: 1.3.0 @@ -22313,7 +22320,6 @@ packages: dependencies: ms: 2.1.2 supports-color: 5.5.0 - dev: false /debug@4.3.4(supports-color@8.1.1): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} @@ -23231,7 +23237,7 @@ packages: peerDependencies: esbuild: '>=0.12 <1' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) esbuild: 0.19.12 transitivePeerDependencies: - supports-color @@ -23653,7 +23659,7 @@ packages: eslint: '*' eslint-plugin-import: '*' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) eslint: 8.26.0 eslint-plugin-import: 2.29.1(@typescript-eslint/parser@2.34.0)(eslint@8.26.0) glob: 7.2.3 @@ -23671,7 +23677,7 @@ packages: eslint: '*' eslint-plugin-import: '*' dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) enhanced-resolve: 5.15.0 eslint: 8.56.0 eslint-module-utils: 2.8.0(@typescript-eslint/parser@6.19.1)(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1)(eslint@8.56.0) @@ -24190,7 +24196,7 @@ packages: ajv: 6.12.6 chalk: 2.4.2 cross-spawn: 6.0.5 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 eslint-scope: 5.1.1 eslint-utils: 1.4.3 @@ -24239,7 +24245,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -24291,7 +24297,7 @@ packages: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -24934,7 +24940,7 @@ packages: debug: optional: true dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} @@ -25293,7 +25299,7 @@ packages: resolution: {integrity: sha512-omefjdbyRb2rRt0tnrZlbeWx9oZJm66o88K8JlYn13xELn+0+d6mJZOQHrJAdC3vxeJ4t/NHa4wh7Wlh+nvEJA==} engines: {node: '>=14.0.0'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) decompress-response: 7.0.0 follow-redirects: 1.15.5(debug@4.3.4) into-stream: 6.0.0 @@ -26335,7 +26341,7 @@ packages: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -26346,7 +26352,7 @@ packages: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -26356,7 +26362,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -26374,7 +26380,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -26383,7 +26389,7 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: false @@ -26557,7 +26563,7 @@ packages: canonicalize: 1.0.8 chalk: 4.1.2 cross-fetch: 4.0.0 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) h3: 1.10.1 hash.js: 1.1.7 json-stringify-safe: 5.0.1 @@ -27197,7 +27203,7 @@ packages: resolution: {integrity: sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==} engines: {node: '>=10'} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -27764,7 +27770,7 @@ packages: resolution: {integrity: sha512-9acbWEfbmS8UpdcfqnDO+uBUgKa/9hcRh983IHdM+pKmJPL77G0sWAAK0V0kr5LK3a8cSBfkFSoncXwQlRZfkQ==} engines: {node: '>= 8.3'} dependencies: - '@babel/traverse': 7.23.9 + '@babel/traverse': 7.23.9(supports-color@5.5.0) '@jest/environment': 25.5.0 '@jest/source-map': 25.5.0 '@jest/test-result': 25.5.0 @@ -28298,7 +28304,7 @@ packages: '@babel/core': 7.23.9 '@babel/generator': 7.23.6 '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.9) - '@babel/traverse': 7.23.9 + '@babel/traverse': 7.23.9(supports-color@5.5.0) '@babel/types': 7.23.9 '@jest/transform': 27.5.1 '@jest/types': 27.5.1 @@ -30580,7 +30586,7 @@ packages: /micromark@2.11.4: resolution: {integrity: sha512-+WoovN/ppKolQOFIAajxi7Lu9kInbPxFuTBVEavFcL8eAfVstoc5MocPmqBeAdBOJV00uaVjegzH4+MA0DN/uA==} dependencies: - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) parse-entities: 2.0.0 transitivePeerDependencies: - supports-color @@ -30590,7 +30596,7 @@ packages: resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} dependencies: '@types/debug': 4.1.12 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) decode-named-character-reference: 1.0.2 micromark-core-commonmark: 1.1.0 micromark-factory-space: 1.1.0 @@ -30613,7 +30619,7 @@ packages: resolution: {integrity: sha512-o/sd0nMof8kYff+TqcDx3VSrgBTcZpSvYcAHIfHhv5VAuNmisCxjhx6YmxS8PFEpb9z5WKWKPdzf0jM23ro3RQ==} dependencies: '@types/debug': 4.1.12 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -31250,6 +31256,10 @@ packages: resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==} dev: false + /monaco-editor@0.47.0: + resolution: {integrity: sha512-VabVvHvQ9QmMwXu4du008ZDuyLnHs9j7ThVFsiJoXSOQk18+LF89N4ADzPbFenm0W4V2bGHnFBztIRQTgBfxzw==} + dev: false + /moo@0.5.2: resolution: {integrity: sha512-iSAJLHYKnX41mKcJKjqvnAN9sf0LMDTXDEvFv+ffuRR9a1MIuXLjMNL6EsnDHSkKLTWNqQQ5uo61P4EbU4NU+Q==} dev: false @@ -37006,6 +37016,10 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + dev: false + /static-browser-server@1.0.3: resolution: {integrity: sha512-ZUyfgGDdFRbZGGJQ1YhiM930Yczz5VlbJObrQLlk24+qNHVQx4OlLcYswEUo3bIyNAbQUIUR9Yr5/Hqjzqb4zA==} dependencies: @@ -38201,7 +38215,7 @@ packages: '@babel/parser': 7.23.9 '@babel/plugin-proposal-class-properties': 7.18.6(@babel/core@7.23.9) '@babel/preset-env': 7.23.9(@babel/core@7.23.9) - '@babel/traverse': 7.23.9 + '@babel/traverse': 7.23.9(supports-color@5.5.0) '@rollup/plugin-babel': 5.3.1(@babel/core@7.23.9)(rollup@1.32.1) '@rollup/plugin-commonjs': 11.1.0(rollup@1.32.1) '@rollup/plugin-json': 4.1.0(rollup@1.32.1) @@ -38650,7 +38664,7 @@ packages: bundle-require: 3.1.2(esbuild@0.14.54) cac: 6.7.14 chokidar: 3.5.3 - debug: 4.3.4(supports-color@8.1.1) + debug: 4.3.4(supports-color@5.5.0) esbuild: 0.14.54 execa: 5.1.1 globby: 11.1.0 From c692b59041337dc6063f4f20f8e095e6b2f024ad Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Sat, 9 Mar 2024 15:18:30 +0000 Subject: [PATCH 3/8] Made it so you can wrap a
 element with Editor to
 turn it into an editor

---
 .../components/code-editor/code-editor.tsx    |  2 +-
 .../code-editor/lazy-loaded-editor.tsx        | 22 ++++++++++++++-----
 .../markdown/shiki-remote-plugin.ts           |  8 +++++++
 3 files changed, 25 insertions(+), 7 deletions(-)

diff --git a/apps/total-typescript/src/components/code-editor/code-editor.tsx b/apps/total-typescript/src/components/code-editor/code-editor.tsx
index b73b7aec22..0e7cd459ca 100644
--- a/apps/total-typescript/src/components/code-editor/code-editor.tsx
+++ b/apps/total-typescript/src/components/code-editor/code-editor.tsx
@@ -43,7 +43,7 @@ export const EagerlyLoadedEditor = (props: CodeEditorProps) => {
         language={resolveLanguage(props.language)}
         options={{
           minimap: {enabled: false, showSlider: 'mouseover'},
-          fontSize: 14,
+          fontSize: 16,
           glyphMargin: false,
           tabSize: 2,
           lineNumbers: 'off',
diff --git a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx
index 66d602328f..939c98ace3 100644
--- a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx
+++ b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx
@@ -9,12 +9,22 @@ export const LazyLoadedEditor = React.lazy(() =>
   }),
 )
 
-export const MDXEditor = (props: {
-  language?: Language
-  code: string
-  children?: React.ReactNode
-}) => {
-  return 
+export const MDXEditor = (props: {children?: any}) => {
+  // Yes, we're diving into React's internals to grab the
+  // code from the 
 element
+  const code = props.children?.props?.children?.props?.children
+
+  if (!code) {
+    return null
+  }
+
+  let language = props.children?.props?.children?.props?.className
+
+  if (language) {
+    language = language.replace('language-', '') as Language
+  }
+
+  return 
 }
 
 export const EditorTest = () => {
diff --git a/packages/skill-lesson/markdown/shiki-remote-plugin.ts b/packages/skill-lesson/markdown/shiki-remote-plugin.ts
index c6bde41f27..b2cebc8f6e 100644
--- a/packages/skill-lesson/markdown/shiki-remote-plugin.ts
+++ b/packages/skill-lesson/markdown/shiki-remote-plugin.ts
@@ -4,6 +4,7 @@ import defaultTheme from 'shiki/themes/github-dark.json'
 
 interface MarkdownNode {
   type: string
+  name?: string
   children?: MarkdownNode[]
 }
 
@@ -17,6 +18,13 @@ const visitCodeNodes = async (
   node: MarkdownNode,
   transformer: (node: CodeNode) => Promise,
 ) => {
+  /**
+   * Abort if the wrapping node is an Editor component
+   */
+  if (node.type === 'mdxJsxFlowElement' && node.name === 'Editor') {
+    return
+  }
+
   if (node.type === 'code') {
     await transformer(node as CodeNode)
   }

From 16e589ab5c51f0d865822082901ff33fe4792a21 Mon Sep 17 00:00:00 2001
From: Matt Pocock 
Date: Sat, 9 Mar 2024 16:05:51 +0000
Subject: [PATCH 4/8] Finished code editor work

---
 .../components/code-editor/code-editor.tsx    | 27 ++++++++++++++-----
 1 file changed, 20 insertions(+), 7 deletions(-)

diff --git a/apps/total-typescript/src/components/code-editor/code-editor.tsx b/apps/total-typescript/src/components/code-editor/code-editor.tsx
index 0e7cd459ca..3f94561014 100644
--- a/apps/total-typescript/src/components/code-editor/code-editor.tsx
+++ b/apps/total-typescript/src/components/code-editor/code-editor.tsx
@@ -1,4 +1,5 @@
 import Editor from '@monaco-editor/react'
+import {useState} from 'react'
 import {prettierLoader} from './prettier-loader'
 
 const resolveLanguage = (language: Language | undefined): Language => {
@@ -28,17 +29,30 @@ export type CodeEditorProps = {
   code: string
 }
 
+const getHeight = (code: string) => {
+  return code.split('\n').length * 24
+}
+
+let incrementable = 0
+
 /**
  * Eagerly loaded code editor
  *
  * This should not be used directly, instead use ./lazy-loaded-editor.tsx
  */
 export const EagerlyLoadedEditor = (props: CodeEditorProps) => {
+  // Needs to be a different file name for each editor on-screen
+  const [path] = useState(() => {
+    incrementable++
+    return `main-${incrementable}.ts`
+  })
+
   return (
-    
+
Loading Code Editor...
} + height={getHeight(props.code)} defaultValue={props.code} language={resolveLanguage(props.language)} options={{ @@ -48,10 +62,10 @@ export const EagerlyLoadedEditor = (props: CodeEditorProps) => { tabSize: 2, lineNumbers: 'off', scrollbar: { - vertical: 'auto', + vertical: 'hidden', horizontal: 'auto', }, - scrollBeyondLastLine: false, + scrollBeyondLastLine: true, }} onMount={(editor, monaco) => { // Enable prettier @@ -62,13 +76,12 @@ export const EagerlyLoadedEditor = (props: CodeEditorProps) => { try { return [ { - text: await prettierLoader.format(editor.getValue()), + text: await prettierLoader.format(model.getValue()), range: model.getFullModelRange(), }, ] } catch (err) { console.error(err) - } finally { } }, }, @@ -93,7 +106,7 @@ export const EagerlyLoadedEditor = (props: CodeEditorProps) => { label: 'Save', keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS], run: () => { - editor.getAction('editor.action.formatDocument')?.run() + editor.getAction('editor.action.formatDocument')?.run(path) }, }) }} From 4a1ad911e5875ce2adcdcc2245873542b220b181 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Sun, 10 Mar 2024 11:19:30 +0000 Subject: [PATCH 5/8] Got a basic version of transpile preview working --- .../components/code-editor/code-editor.tsx | 225 +++++++++++++----- .../code-editor/lazy-loaded-editor.tsx | 18 +- 2 files changed, 175 insertions(+), 68 deletions(-) diff --git a/apps/total-typescript/src/components/code-editor/code-editor.tsx b/apps/total-typescript/src/components/code-editor/code-editor.tsx index 3f94561014..bd64e8fd1e 100644 --- a/apps/total-typescript/src/components/code-editor/code-editor.tsx +++ b/apps/total-typescript/src/components/code-editor/code-editor.tsx @@ -1,18 +1,34 @@ import Editor from '@monaco-editor/react' -import {useState} from 'react' +import {ComponentProps, useRef, useState} from 'react' import {prettierLoader} from './prettier-loader' +type Editor = Parameters< + NonNullable['onMount']> +>[0] + +type Monaco = Parameters< + NonNullable['onMount']> +>[1] + const resolveLanguage = (language: Language | undefined): Language => { switch (language) { case 'ts': case undefined: return 'typescript' + case 'js': + return 'javascript' default: return language } } -export type Language = 'ts' | 'tsx' | 'typescript' | 'json' +export type Language = + | 'ts' + | 'tsx' + | 'typescript' + | 'json' + | 'js' + | 'javascript' const THEME_NAME = 'total-typescript' @@ -27,6 +43,11 @@ const EDITOR_THEME = { export type CodeEditorProps = { language?: Language code: string + className?: string + fontSize?: number + readonly?: boolean + onChange?: (code: string | undefined) => void + onEmittedJavaScript?: (code: string) => void } const getHeight = (code: string) => { @@ -47,70 +68,146 @@ export const EagerlyLoadedEditor = (props: CodeEditorProps) => { return `main-${incrementable}.ts` }) + const monacoRef = useRef(undefined) + const editorRef = useRef(undefined) + + const reportEmittedJavaScript = async (): Promise => { + if (!props.onEmittedJavaScript) { + return + } + + if (!editorRef.current || !monacoRef.current) { + return + } + + const uri = editorRef.current?.getModel()?.uri + + if (!uri) { + return + } + + const worker = + await monacoRef.current.languages.typescript.getTypeScriptWorker() + + const client = await worker(uri) + + const emitOutput = await client.getEmitOutput(uri.toString()) + + props.onEmittedJavaScript(emitOutput.outputFiles[0].text) + } + return ( -
- Loading Code Editor...
} - height={getHeight(props.code)} - defaultValue={props.code} - language={resolveLanguage(props.language)} - options={{ - minimap: {enabled: false, showSlider: 'mouseover'}, - fontSize: 16, - glyphMargin: false, - tabSize: 2, - lineNumbers: 'off', - scrollbar: { - vertical: 'hidden', - horizontal: 'auto', + Loading Code Editor...
} + // height={getHeight(props.code)} + {...(props.readonly + ? { + value: props.code, + } + : { + defaultValue: props.code, + })} + onChange={(code) => { + props.onChange?.(code) + + reportEmittedJavaScript() + }} + language={resolveLanguage(props.language)} + options={{ + minimap: {enabled: false, showSlider: 'mouseover'}, + fontSize: props.fontSize ?? 16, + glyphMargin: false, + tabSize: 2, + lineNumbers: 'off', + scrollbar: { + vertical: 'hidden', + horizontal: 'auto', + }, + automaticLayout: true, + readOnly: props.readonly, + scrollBeyondLastLine: true, + }} + onMount={(editor, monaco) => { + monacoRef.current = monaco + editorRef.current = editor + + // Enable prettier + monaco.languages.registerDocumentFormattingEditProvider('typescript', { + provideDocumentFormattingEdits: async (model) => { + try { + return [ + { + text: await prettierLoader.format(model.getValue()), + range: model.getFullModelRange(), + }, + ] + } catch (err) { + console.error(err) + } }, - scrollBeyondLastLine: true, - }} - onMount={(editor, monaco) => { - // Enable prettier - monaco.languages.registerDocumentFormattingEditProvider( - 'typescript', - { - provideDocumentFormattingEdits: async (model) => { - try { - return [ - { - text: await prettierLoader.format(model.getValue()), - range: model.getFullModelRange(), - }, - ] - } catch (err) { - console.error(err) - } - }, - }, - ) - - // Adjust the compiler options - monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ - ...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(), - module: monaco.languages.typescript.ModuleKind.ESNext, - moduleResolution: - monaco.languages.typescript.ModuleResolutionKind.NodeJs, - strict: true, - }) - - // Define the custom theme - monaco.editor.defineTheme(THEME_NAME, EDITOR_THEME) - monaco.editor.setTheme(THEME_NAME) - - // Enable auto formatting on save - editor.addAction({ - id: 'save', - label: 'Save', - keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS], - run: () => { - editor.getAction('editor.action.formatDocument')?.run(path) - }, - }) - }} - /> + }) + + // Adjust the compiler options + monaco.languages.typescript.typescriptDefaults.setCompilerOptions({ + ...monaco.languages.typescript.typescriptDefaults.getCompilerOptions(), + module: monaco.languages.typescript.ModuleKind.ESNext, + moduleResolution: + monaco.languages.typescript.ModuleResolutionKind.NodeJs, + strict: true, + }) + + // Define the custom theme + monaco.editor.defineTheme(THEME_NAME, EDITOR_THEME) + monaco.editor.setTheme(THEME_NAME) + + // Enable auto formatting on save + editor.addAction({ + id: 'save', + label: 'Save', + keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS], + run: () => { + editor.getAction('editor.action.formatDocument')?.run(path) + }, + }) + + reportEmittedJavaScript() + }} + /> + ) +} + +export const EagerlyLoadedFullWidthEditor = (props: CodeEditorProps) => { + return ( +
+ +
+ ) +} + +export const EagerlyLoadedTranspilePreview = ( + props: Omit, +) => { + const [code, setCode] = useState(undefined) + + return ( +
+
+ +
+
+ +
) } diff --git a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx index 939c98ace3..b2de088d6a 100644 --- a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx +++ b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx @@ -1,10 +1,18 @@ import React from 'react' import type {Language} from './code-editor' -export const LazyLoadedEditor = React.lazy(() => +export const LazyLoadedFullWidthEditor = React.lazy(() => import('./code-editor').then((res) => { return { - default: res.EagerlyLoadedEditor, + default: res.EagerlyLoadedFullWidthEditor, + } + }), +) + +export const LazyLoadedTranspilePreview = React.lazy(() => + import('./code-editor').then((res) => { + return { + default: res.EagerlyLoadedTranspilePreview, } }), ) @@ -24,9 +32,11 @@ export const MDXEditor = (props: {children?: any}) => { language = language.replace('language-', '') as Language } - return + return } export const EditorTest = () => { - return {}`} /> + return ( + {}`} /> + ) } From 2c27785b5283dda7c2c11ac97a1c9857e0593768 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Sun, 10 Mar 2024 11:44:13 +0000 Subject: [PATCH 6/8] Completed work on the transpile preview --- .../components/code-editor/code-editor.tsx | 26 ------------- .../src/components/code-editor/js-logo.svg | 4 ++ .../code-editor/lazy-loaded-editor.tsx | 20 +++++++++- .../code-editor/transpile-preview.tsx | 39 +++++++++++++++++++ .../src/components/code-editor/ts-logo.svg | 1 + .../src/components/mdx/index.tsx | 3 +- .../markdown/shiki-remote-plugin.ts | 8 +++- 7 files changed, 72 insertions(+), 29 deletions(-) create mode 100644 apps/total-typescript/src/components/code-editor/js-logo.svg create mode 100644 apps/total-typescript/src/components/code-editor/transpile-preview.tsx create mode 100644 apps/total-typescript/src/components/code-editor/ts-logo.svg diff --git a/apps/total-typescript/src/components/code-editor/code-editor.tsx b/apps/total-typescript/src/components/code-editor/code-editor.tsx index bd64e8fd1e..40f31a83c6 100644 --- a/apps/total-typescript/src/components/code-editor/code-editor.tsx +++ b/apps/total-typescript/src/components/code-editor/code-editor.tsx @@ -185,29 +185,3 @@ export const EagerlyLoadedFullWidthEditor = (props: CodeEditorProps) => { ) } - -export const EagerlyLoadedTranspilePreview = ( - props: Omit, -) => { - const [code, setCode] = useState(undefined) - - return ( -
-
- -
-
- -
-
- ) -} diff --git a/apps/total-typescript/src/components/code-editor/js-logo.svg b/apps/total-typescript/src/components/code-editor/js-logo.svg new file mode 100644 index 0000000000..9650ca78ef --- /dev/null +++ b/apps/total-typescript/src/components/code-editor/js-logo.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx index b2de088d6a..105ba1821a 100644 --- a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx +++ b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx @@ -10,7 +10,7 @@ export const LazyLoadedFullWidthEditor = React.lazy(() => ) export const LazyLoadedTranspilePreview = React.lazy(() => - import('./code-editor').then((res) => { + import('./transpile-preview').then((res) => { return { default: res.EagerlyLoadedTranspilePreview, } @@ -35,6 +35,24 @@ export const MDXEditor = (props: {children?: any}) => { return } +export const MDXTranspilePreview = (props: {children?: any}) => { + // Yes, we're diving into React's internals to grab the + // code from the
 element
+  const code = props.children?.props?.children?.props?.children
+
+  if (!code) {
+    return null
+  }
+
+  let language = props.children?.props?.children?.props?.className
+
+  if (language) {
+    language = language.replace('language-', '') as Language
+  }
+
+  return 
+}
+
 export const EditorTest = () => {
   return (
      {}`} />
diff --git a/apps/total-typescript/src/components/code-editor/transpile-preview.tsx b/apps/total-typescript/src/components/code-editor/transpile-preview.tsx
new file mode 100644
index 0000000000..0fbd23b811
--- /dev/null
+++ b/apps/total-typescript/src/components/code-editor/transpile-preview.tsx
@@ -0,0 +1,39 @@
+import {useState} from 'react'
+import {CodeEditorProps, EagerlyLoadedEditor} from './code-editor'
+import tsSvg from './ts-logo.svg'
+import jsSvg from './js-logo.svg'
+import Image from 'next/image'
+
+export const EagerlyLoadedTranspilePreview = (
+  props: Omit,
+) => {
+  const [code, setCode] = useState(undefined)
+
+  return (
+    
+
+ +
+ TypeScript Logo + TypeScript Code +
+
+
+ +
+ JavaScript Logo + Emitted JavaScript +
+
+
+ ) +} diff --git a/apps/total-typescript/src/components/code-editor/ts-logo.svg b/apps/total-typescript/src/components/code-editor/ts-logo.svg new file mode 100644 index 0000000000..a46d53d49f --- /dev/null +++ b/apps/total-typescript/src/components/code-editor/ts-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/total-typescript/src/components/mdx/index.tsx b/apps/total-typescript/src/components/mdx/index.tsx index db5fecb8dd..f592949d3d 100644 --- a/apps/total-typescript/src/components/mdx/index.tsx +++ b/apps/total-typescript/src/components/mdx/index.tsx @@ -10,7 +10,7 @@ import toast from 'react-hot-toast' import {useCopyToClipboard} from 'react-use' import Balancer from 'react-wrap-balancer' import {twMerge} from 'tailwind-merge' -import {MDXEditor} from '../code-editor/lazy-loaded-editor' +import {MDXEditor, MDXTranspilePreview} from '../code-editor/lazy-loaded-editor' export const MDXComponents = { TypeError: (props) => , @@ -22,6 +22,7 @@ export const MDXComponents = { Testimonial: (props) => , Module: (props) => , Editor: (props) => , + TranspilePreview: (props) => , } satisfies Record> type TypeErrorProps = { diff --git a/packages/skill-lesson/markdown/shiki-remote-plugin.ts b/packages/skill-lesson/markdown/shiki-remote-plugin.ts index b2cebc8f6e..db39b9f902 100644 --- a/packages/skill-lesson/markdown/shiki-remote-plugin.ts +++ b/packages/skill-lesson/markdown/shiki-remote-plugin.ts @@ -14,6 +14,8 @@ interface CodeNode extends MarkdownNode { meta: string | null } +const NODES_TO_ABORT = ['Editor', 'TranspilePreview'] + const visitCodeNodes = async ( node: MarkdownNode, transformer: (node: CodeNode) => Promise, @@ -21,7 +23,11 @@ const visitCodeNodes = async ( /** * Abort if the wrapping node is an Editor component */ - if (node.type === 'mdxJsxFlowElement' && node.name === 'Editor') { + if ( + node.type === 'mdxJsxFlowElement' && + node.name && + NODES_TO_ABORT.includes(node.name) + ) { return } From 00232de4a7e8b01478fce241766d929595544dbb Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 11 Mar 2024 14:08:57 +0000 Subject: [PATCH 7/8] Made the editor load only when visible on the page --- .../components/code-editor/code-editor.tsx | 26 ++--- .../code-editor/lazy-loaded-editor.tsx | 68 +++++------- .../src/components/code-editor/mdx-editor.tsx | 86 ++++++++++++++ .../code-editor/transpile-preview.tsx | 39 ------- .../src/components/mdx/index.tsx | 2 +- .../src/pages/editor-test.tsx | 105 ------------------ 6 files changed, 121 insertions(+), 205 deletions(-) create mode 100644 apps/total-typescript/src/components/code-editor/mdx-editor.tsx delete mode 100644 apps/total-typescript/src/components/code-editor/transpile-preview.tsx delete mode 100644 apps/total-typescript/src/pages/editor-test.tsx diff --git a/apps/total-typescript/src/components/code-editor/code-editor.tsx b/apps/total-typescript/src/components/code-editor/code-editor.tsx index 40f31a83c6..0cf02b0e3f 100644 --- a/apps/total-typescript/src/components/code-editor/code-editor.tsx +++ b/apps/total-typescript/src/components/code-editor/code-editor.tsx @@ -50,10 +50,6 @@ export type CodeEditorProps = { onEmittedJavaScript?: (code: string) => void } -const getHeight = (code: string) => { - return code.split('\n').length * 24 -} - let incrementable = 0 /** @@ -86,14 +82,18 @@ export const EagerlyLoadedEditor = (props: CodeEditorProps) => { return } - const worker = - await monacoRef.current.languages.typescript.getTypeScriptWorker() + try { + const worker = + await monacoRef.current.languages.typescript.getTypeScriptWorker() - const client = await worker(uri) + const client = await worker(uri) - const emitOutput = await client.getEmitOutput(uri.toString()) + const emitOutput = await client.getEmitOutput(uri.toString()) - props.onEmittedJavaScript(emitOutput.outputFiles[0].text) + props.onEmittedJavaScript(emitOutput.outputFiles[0].text) + } catch (e) { + console.error('Getting emitted JavaScript failed', e) + } } return ( @@ -177,11 +177,3 @@ export const EagerlyLoadedEditor = (props: CodeEditorProps) => { /> ) } - -export const EagerlyLoadedFullWidthEditor = (props: CodeEditorProps) => { - return ( -
- -
- ) -} diff --git a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx index 105ba1821a..73c1b7777a 100644 --- a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx +++ b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx @@ -1,60 +1,42 @@ -import React from 'react' -import type {Language} from './code-editor' +import React, {useLayoutEffect} from 'react' +import type {CodeEditorProps} from './code-editor' -export const LazyLoadedFullWidthEditor = React.lazy(() => +const _LazyLoadedEditor = React.lazy(() => import('./code-editor').then((res) => { return { - default: res.EagerlyLoadedFullWidthEditor, + default: res.EagerlyLoadedEditor, } }), ) -export const LazyLoadedTranspilePreview = React.lazy(() => - import('./transpile-preview').then((res) => { - return { - default: res.EagerlyLoadedTranspilePreview, - } - }), -) +const LoadWhenVisible = (props: {children: React.ReactNode}) => { + const [isVisible, setIsVisible] = React.useState(false) -export const MDXEditor = (props: {children?: any}) => { - // Yes, we're diving into React's internals to grab the - // code from the
 element
-  const code = props.children?.props?.children?.props?.children
+  const ref = React.useRef(null)
 
-  if (!code) {
-    return null
-  }
+  React.useEffect(() => {
+    const observer = new IntersectionObserver(([entry]) => {
+      setIsVisible(entry.isIntersecting)
+    })
 
-  let language = props.children?.props?.children?.props?.className
-
-  if (language) {
-    language = language.replace('language-', '') as Language
-  }
-
-  return 
-}
+    observer.observe(ref.current!)
 
-export const MDXTranspilePreview = (props: {children?: any}) => {
-  // Yes, we're diving into React's internals to grab the
-  // code from the 
 element
-  const code = props.children?.props?.children?.props?.children
-
-  if (!code) {
-    return null
-  }
-
-  let language = props.children?.props?.children?.props?.className
-
-  if (language) {
-    language = language.replace('language-', '') as Language
-  }
+    return () => {
+      observer.disconnect()
+    }
+  }, [])
 
-  return 
+  return (
+    
+ {isVisible ? props.children : null} +
+ ) } -export const EditorTest = () => { +export const LazyLoadedEditor = (props: CodeEditorProps) => { return ( - {}`} /> + + <_LazyLoadedEditor {...props} /> + ) } diff --git a/apps/total-typescript/src/components/code-editor/mdx-editor.tsx b/apps/total-typescript/src/components/code-editor/mdx-editor.tsx new file mode 100644 index 0000000000..54fa641f1a --- /dev/null +++ b/apps/total-typescript/src/components/code-editor/mdx-editor.tsx @@ -0,0 +1,86 @@ +import Image from 'next/image' +import {useState} from 'react' +import type {CodeEditorProps, Language} from './code-editor' +import {LazyLoadedEditor} from './lazy-loaded-editor' +import tsSvg from './ts-logo.svg' +import jsSvg from './js-logo.svg' + +const getHeight = (code: string) => { + return (code.split('\n').length + 3) * 24 +} + +const extractCodeAndLanguage = (props: {children?: any}) => { + const code = props.children?.props?.children?.props?.children + let language = props.children?.props?.children?.props?.className + + if (!code || !language) { + return null + } + + language = language.replace('language-', '') as Language + + return {code, language} +} + +export const MDXEditor = (props: {children?: any}) => { + // Yes, we're diving into React's internals to grab the + // code from the
 element
+  const extracted = extractCodeAndLanguage(props)
+
+  if (!extracted) {
+    return null
+  }
+
+  const {code, language} = extracted
+
+  return (
+    
+ +
+ ) +} + +export const MDXTranspilePreview = (props: {children?: any}) => { + const extracted = extractCodeAndLanguage(props) + const [jsCode, setJsCode] = useState(undefined) + + if (!extracted) { + return null + } + + const {code, language} = extracted + + return ( +
+
+ +
+ TypeScript Logo + TypeScript Code +
+
+
+ +
+ JavaScript Logo + Emitted JavaScript +
+
+
+ ) +} diff --git a/apps/total-typescript/src/components/code-editor/transpile-preview.tsx b/apps/total-typescript/src/components/code-editor/transpile-preview.tsx deleted file mode 100644 index 0fbd23b811..0000000000 --- a/apps/total-typescript/src/components/code-editor/transpile-preview.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import {useState} from 'react' -import {CodeEditorProps, EagerlyLoadedEditor} from './code-editor' -import tsSvg from './ts-logo.svg' -import jsSvg from './js-logo.svg' -import Image from 'next/image' - -export const EagerlyLoadedTranspilePreview = ( - props: Omit, -) => { - const [code, setCode] = useState(undefined) - - return ( -
-
- -
- TypeScript Logo - TypeScript Code -
-
-
- -
- JavaScript Logo - Emitted JavaScript -
-
-
- ) -} diff --git a/apps/total-typescript/src/components/mdx/index.tsx b/apps/total-typescript/src/components/mdx/index.tsx index f592949d3d..a4e10bafd7 100644 --- a/apps/total-typescript/src/components/mdx/index.tsx +++ b/apps/total-typescript/src/components/mdx/index.tsx @@ -10,7 +10,7 @@ import toast from 'react-hot-toast' import {useCopyToClipboard} from 'react-use' import Balancer from 'react-wrap-balancer' import {twMerge} from 'tailwind-merge' -import {MDXEditor, MDXTranspilePreview} from '../code-editor/lazy-loaded-editor' +import {MDXEditor, MDXTranspilePreview} from '../code-editor/mdx-editor' export const MDXComponents = { TypeError: (props) => , diff --git a/apps/total-typescript/src/pages/editor-test.tsx b/apps/total-typescript/src/pages/editor-test.tsx deleted file mode 100644 index fab3d1484c..0000000000 --- a/apps/total-typescript/src/pages/editor-test.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import Layout from '@/components/app/layout' -import {EditorTest} from '@/components/code-editor/lazy-loaded-editor' - -import {cn} from '@skillrecordings/ui/utils/cn' -import Image from 'next/image' - -const title = 'How to use TypeScript with React' - -const config = { - author: 'Matt Pocock', - authorBio: 'Frontend Engineer, TypeScript Enthusiast', -} - -const isBookTeaser = true - -const ArticleTemplate: React.FC = () => { - return ( - -
-
-

- {title} -

-
-
-
- Matt Pocock -
-
- {config.author} - - {config.authorBio} - -
-
-
-
- {isBookTeaser && ( -
- -
- )} - {/* {image && !isBookTeaser && ( -
- - -
- )} */} -
-
-
- -
- Matt's signature -
-
-
-
- ) -} - -export default ArticleTemplate From 194142e0535417c8f23b61c221e32bd3616f5457 Mon Sep 17 00:00:00 2001 From: Matt Pocock Date: Mon, 11 Mar 2024 14:15:06 +0000 Subject: [PATCH 8/8] Removed an import --- .../src/components/code-editor/lazy-loaded-editor.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx index 73c1b7777a..019ab6d2d8 100644 --- a/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx +++ b/apps/total-typescript/src/components/code-editor/lazy-loaded-editor.tsx @@ -1,4 +1,4 @@ -import React, {useLayoutEffect} from 'react' +import React from 'react' import type {CodeEditorProps} from './code-editor' const _LazyLoadedEditor = React.lazy(() =>