Skip to content

Commit

Permalink
wip
Browse files Browse the repository at this point in the history
  • Loading branch information
nag5000 committed Sep 27, 2024
1 parent b1d6143 commit 4636eff
Show file tree
Hide file tree
Showing 36 changed files with 1,839 additions and 110 deletions.
1 change: 1 addition & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"importOrder": [
"^node:",
"^react",
"^next",
"<THIRD_PARTY_MODULES>",
"^\\~icons/",
"^\\@/",
Expand Down
10 changes: 5 additions & 5 deletions packages/jsrepl-nuxt/components/CodeEditor.vue
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
<script setup lang="ts">
import { useCodeEditorRepl } from '@/composables/useCodeEditorRepl'
import { useCodeEditorTypescript } from '@/composables/useCodeEditorTypescript'
import { loadMonacoTheme } from '@/utils/monaco-themes'
import { Themes } from '@/utils/themes'
import * as monaco from 'monaco-editor'
// @ts-expect-error: no types for this
import { IQuickInputService } from 'monaco-editor/esm/vs/platform/quickinput/common/quickInput'
import { type ModelDef, type Theme, type ThemeDef } from '~/types/repl.types'
import type { ModelDef, Theme, ThemeDef } from '~/types/repl.types'
import { createCodeEditorModel } from '~/utils/code-editor-model-factory'
import { PrettierFormattingProvider } from '~/utils/prettier-formatting-provider'
import { useCodeEditorRepl } from '@/composables/useCodeEditorRepl'
import { useCodeEditorTypescript } from '@/composables/useCodeEditorTypescript'
import { loadMonacoTheme } from '@/utils/monaco-themes'
import { Themes } from '@/utils/themes'
const emit = defineEmits(['model-change', 'repl', 'replBodyMutation'])
Expand Down
2 changes: 2 additions & 0 deletions packages/jsrepl/next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import Icons from 'unplugin-icons/webpack'

/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: false,

// Configure `pageExtensions` to include markdown and MDX files
pageExtensions: ['js', 'jsx', 'md', 'mdx', 'ts', 'tsx'],

Expand Down
5 changes: 5 additions & 0 deletions packages/jsrepl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,23 @@
"dependencies": {
"@mdx-js/loader": "^3.0.1",
"@mdx-js/react": "^3.0.1",
"@nag5000/monaco-tailwindcss": "0.6.1-fork.0",
"@next/mdx": "^14.2.13",
"@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-slot": "^1.1.0",
"@shikijs/monaco": "^1.11.1",
"@types/mdx": "^2.0.13",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"debounce": "^2.1.0",
"fflate": "^0.8.2",
"lucide-react": "^0.445.0",
"next": "14.2.13",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"shiki": "^1.11.1",
"sonner": "^1.5.0",
"tailwind-merge": "^2.4.0",
"tailwindcss-animate": "^1.0.7"
Expand Down
138 changes: 138 additions & 0 deletions packages/jsrepl/src/app/repl/components/code-editor.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, { useEffect, useMemo, useRef, useState } from 'react'
import { useTheme } from 'next-themes'
import * as monaco from 'monaco-editor'
import { loadMonacoTheme } from '@/lib/monaco-themes'
import { PrettierFormattingProvider } from '@/lib/prettier-formatting-provider'
import { Themes } from '@/lib/themes'
import { cn } from '@/lib/utils'
import { Theme } from '@/types'

setupMonaco()
setupTailwindCSS()

export default function CodeEditor() {
const containerRef = useRef<HTMLDivElement>(null)
const editorRef = useRef<monaco.editor.IStandaloneCodeEditor | null>(null)
const [isThemeLoaded, setIsThemeLoaded] = useState(false)
const { resolvedTheme } = useTheme()
const themeId = resolvedTheme as Theme

const theme = useMemo(() => Themes.find((theme) => theme.id === themeId) ?? Themes[0], [themeId])

const editorInitialOptions = useRef<monaco.editor.IStandaloneEditorConstructionOptions>({
value: '<div class="bg-red-500 ml-4">Hello World</div>',
language: 'html',

automaticLayout: true,
padding: { top: 20, bottom: 20 },
// TODO: make it configurable
fontSize: 16,
minimap: { enabled: false },
theme: theme.id,
quickSuggestions: {
other: true,
comments: true,

// Tailwind CSS intellisense autosuggestion in TSX (otherwise it works only by pressing Ctrl+Space manually, unlike in html/css).
strings: true,
},
tabSize: 2,
// TODO: make it configurable
renderLineHighlight: 'none',
scrollBeyondLastLine: false,
})

useEffect(() => {
loadMonacoTheme(theme).then(() => {
editorRef.current?.updateOptions({ theme: theme.id })
setIsThemeLoaded(true)
})
}, [theme])

useEffect(() => {
const editor = monaco.editor.create(containerRef.current!, editorInitialOptions.current)
editorRef.current = editor

return () => {
editor.dispose()
}
}, [])

return (
<>
<div className={cn('h-96 w-full', { 'opacity-0': !isThemeLoaded })} ref={containerRef} />
</>
)
}

function setupMonaco() {
self.MonacoEnvironment = {
getWorker(workerId, label) {
switch (label) {
case 'json':
return new Worker(
new URL('monaco-editor/esm/vs/language/json/json.worker', import.meta.url)
)
case 'css':
case 'scss':
case 'less':
return new Worker(
new URL('monaco-editor/esm/vs/language/css/css.worker', import.meta.url)
)
case 'html':
case 'handlebars':
case 'razor':
return new Worker(
new URL('monaco-editor/esm/vs/language/html/html.worker', import.meta.url)
)
case 'typescript':
case 'javascript':
return new Worker(
new URL('monaco-editor/esm/vs/language/typescript/ts.worker', import.meta.url)
)
case 'tailwindcss':
return new Worker(new URL('@/lib/monaco-tailwindcss.worker', import.meta.url))
default:
return new Worker(new URL('monaco-editor/esm/vs/editor/editor.worker', import.meta.url))
}
},
}

monaco.editor.addKeybindingRules([
{
keybinding: monaco.KeyMod.CtrlCmd | monaco.KeyCode.KeyS,
command: 'editor.action.formatDocument',
when: 'editorHasDocumentFormattingProvider && editorTextFocus && !editorReadonly',
},
])

const prettierFormattingProvider = new PrettierFormattingProvider()
monaco.languages.registerDocumentFormattingEditProvider(
[
{ language: 'typescript', exclusive: true },
{ language: 'javascript', exclusive: true },
{ language: 'html', exclusive: true },
{ language: 'css', exclusive: true },
],
prettierFormattingProvider
)

monaco.languages.json.jsonDefaults.setDiagnosticsOptions({
validate: true,
allowComments: true,
trailingCommas: 'ignore',
// schemas: [],
})
}

async function setupTailwindCSS() {
const { tailwindcssData } = await import('@nag5000/monaco-tailwindcss')

monaco.languages.css.cssDefaults.setOptions({
data: {
dataProviders: {
tailwindcssData,
},
},
})
}
18 changes: 18 additions & 0 deletions packages/jsrepl/src/app/repl/components/header-base.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Link from 'next/link'
import Logo from '@/components/logo'

export default function HeaderBase({ children }: { children?: React.ReactNode }) {
return (
<header className="h-repl-header flex items-stretch gap-2 border-b px-2 leading-[calc(theme(height.repl-header)-1px)]">
<Link
href="/"
className="mr-24 inline-flex items-center gap-2 text-lg font-light tracking-wide max-[840px]:mr-6"
>
<Logo width="1.25rem" height="1.25rem" />
<span className="text-foreground/80">JSREPL</span>
</Link>

{children}
</header>
)
}
Loading

0 comments on commit 4636eff

Please sign in to comment.