Repository Public
1/**2 * Monaco is bundled and self-hosted: no CDN loader, workers are emitted by the bundler3 * (`new Worker(new URL(...), import.meta.url)`), which the CSP allows via `worker-src 'self'`.4 */5import * as monaco from 'monaco-editor';6import { loader } from '@monaco-editor/react';7import { getBrand } from '@/stores/branding';89declare global {10 interface Window {11 MonacoEnvironment?: { getWorker(workerId: string, label: string): Worker };12 }13}1415let configured = false;16export function setupMonaco(): typeof monaco {17 if (configured) return monaco;18 configured = true;19 self.MonacoEnvironment = {20 getWorker(_id, label) {21 if (label === 'json')22 return new Worker(new URL('monaco-editor/language/json/json.worker.js', import.meta.url), { type: 'module' });23 if (label === 'css' || label === 'scss' || label === 'less')24 return new Worker(new URL('monaco-editor/language/css/css.worker.js', import.meta.url), { type: 'module' });25 if (label === 'html' || label === 'handlebars' || label === 'razor')26 return new Worker(new URL('monaco-editor/language/html/html.worker.js', import.meta.url), { type: 'module' });27 if (label === 'typescript' || label === 'javascript')28 return new Worker(new URL('monaco-editor/language/typescript/ts.worker.js', import.meta.url), { type: 'module' });29 return new Worker(new URL('monaco-editor/editor/editor.worker.js', import.meta.url), { type: 'module' });30 },31 };32 loader.config({ monaco });33 // Accent of the instance's agent (globals.css tokens). Monaco loads after sign-in: the branding is known.34 const accent = getBrand().agent === 'codex' ? { dark: '#2DD4BF', light: '#0B7268' } : { dark: '#8F8FFF', light: '#5151E8' };35 monaco.editor.defineTheme('cr-dark', {36 base: 'vs-dark',37 inherit: true,38 rules: [39 { token: 'comment', foreground: '7F8CA0' },40 { token: 'string', foreground: '42C995' },41 { token: 'number', foreground: 'E7B86B' },42 { token: 'keyword', foreground: '9DBCFF' },43 ],44 colors: {45 'editor.background': '#080B10', 'editor.foreground': '#F4F7FB', 'editorLineNumber.foreground': '#5D6A7D', 'editorLineNumber.activeForeground': '#A1ADBD',46 'editor.lineHighlightBackground': '#10151D', 'editor.selectionBackground': `${accent.dark}44`, 'editorCursor.foreground': accent.dark,47 'editorWidget.background': '#161D27', 'editorWidget.border': '#ffffff14', 'editorGutter.background': '#080B10',48 'diffEditor.insertedTextBackground': '#42C99522', 'diffEditor.removedTextBackground': '#ED777722',49 'scrollbarSlider.background': '#ffffff14', 'scrollbarSlider.hoverBackground': '#ffffff24',50 },51 });52 monaco.editor.defineTheme('cr-light', {53 base: 'vs',54 inherit: true,55 rules: [56 { token: 'comment', foreground: '6B7280' },57 { token: 'string', foreground: '116329' },58 { token: 'number', foreground: '8F5F00' },59 { token: 'keyword', foreground: accent.light.slice(1) },60 ],61 colors: {62 'editor.background': '#FFFFFF', 'editor.foreground': '#17181C', 'editorLineNumber.foreground': '#9AA0AD', 'editorLineNumber.activeForeground': '#555B69',63 'editor.lineHighlightBackground': '#F7F6F3', 'editor.selectionBackground': `${accent.light}30`, 'editorCursor.foreground': accent.light,64 'editorWidget.background': '#FFFFFF', 'editorWidget.border': '#00000017', 'editorGutter.background': '#FFFFFF',65 'diffEditor.insertedTextBackground': '#1FA97126', 'diffEditor.removedTextBackground': '#E5484D22',66 'scrollbarSlider.background': '#00000017', 'scrollbarSlider.hoverBackground': '#0000002e',67 },68 });69 return monaco;70}71