Skip to content

Instantly share code, notes, and snippets.

@sandros94
Last active January 16, 2025 17:22
Show Gist options
  • Select an option

  • Save sandros94/6eb313fa95cecc19c37f66f45474a889 to your computer and use it in GitHub Desktop.

Select an option

Save sandros94/6eb313fa95cecc19c37f66f45474a889 to your computer and use it in GitHub Desktop.

Revisions

  1. sandros94 revised this gist Jan 16, 2025. 1 changed file with 214 additions and 66 deletions.
    280 changes: 214 additions & 66 deletions monaco-editor.vue
    Original file line number Diff line number Diff line change
    @@ -1,86 +1,234 @@
    <script setup lang="ts">
    const md = ref('')
    import { useScript } from '@unhead/vue'
    import { computed, ref, createError, onMounted, watch, useTemplateRef } from '#imports'
    const MONACO_CDN_BASE = 'https://unpkg.com/[email protected]/min/'
    const MDC_CDN_BASE = 'https://cdn.jsdelivr.net/npm/@nuxtlabs/[email protected]/'
    declare global {
    interface Window {
    require: any
    MonacoEnvironment: any
    }
    }
    const editorEl = useTemplateRef('editor')
    const code = defineModel<string>({ required: true })
    const props = withDefaults(defineProps<{
    fitContent?: boolean
    language?: string
    minimap?: boolean
    readOnly?: boolean
    theme?: string
    wordWrap?: 'on' | 'off'
    tabSize?: number
    }>(), {
    language: 'mdc',
    minimap: true,
    readOnly: false,
    theme: 'vs-dark',
    wordWrap: 'on',
    tabSize: 2,
    })
    const monaco = ref()
    const editor = ref()
    const styling = computed(() => {
    if (props.fitContent) {
    return {}
    }
    else {
    return { height: '100%' }
    }
    })
    const MONACO_CDN_BASE = 'https://unpkg.com/[email protected]/dev/'
    const { onLoaded, status } = useScriptNpm({
    packageName: 'monaco-editor',
    file: 'dev/vs/loader.js',
    version: '0.52.0',
    scriptOptions: {
    trigger: 'onNuxtReady',
    async use() {
    // @ts-expect-error: in-browser event
    window.require.config({ paths: { vs: `${MONACO_CDN_BASE}vs` } })
    // @ts-expect-error: in-browser event
    const monaco = await new Promise<any>(resolve => window.require(['vs/editor/editor.main'], resolve))
    // @ts-expect-error: in-browser event
    window.MonacoEnvironment = {
    getWorkerUrl: function () {
    return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
    self.MonacoEnvironment = {
    baseUrl: '${MONACO_CDN_BASE}'
    };
    importScripts('${MONACO_CDN_BASE}vs/base/worker/workerMain.js');`,
    )}`
    },
    // Cave's man approach to wait for window.require to be available
    const waitForRequire = async (attempt = 1): Promise<void> => {
    if (window.require) return
    if (attempt > 5) throw new Error('Monaco loader.js failed to initialize')
    const delays = [0, 100, 250, 500, 2000]
    await new Promise(resolve => setTimeout(resolve, delays[attempt - 1]))
    return waitForRequire(attempt + 1)
    }
    const { status, load } = useScript({
    src: `${MONACO_CDN_BASE}vs/loader.js`,
    }, {
    trigger: 'manual',
    async use() {
    if (editorEl.value) return
    try {
    await waitForRequire()
    }
    catch {
    throw new Error('Failed to load Monaco: loader.js did not initialize')
    }
    window.require.config({
    paths: {
    vs: `${MONACO_CDN_BASE}vs`,
    },
    // 'vs/nls': {
    // availableLanguages: {},
    // },
    })
    const _monaco = await new Promise<any>((resolve, reject) => {
    try {
    window.require(['vs/editor/editor.main'], resolve)
    }
    catch (e) {
    reject(new Error('Failed to load Monaco editor: ' + e))
    }
    })
    window.MonacoEnvironment = {
    getWorkerUrl: function () {
    return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
    self.MonacoEnvironment = {
    baseUrl: '${MONACO_CDN_BASE}'
    };
    importScripts('${MONACO_CDN_BASE}vs/base/worker/workerMain.js');`,
    )}`
    },
    }
    // @ts-expect-error: in-browser event
    const { language: monarchMdc } = await import('https://cdn.jsdelivr.net/npm/@nuxtlabs/[email protected]/dist/index.mjs')
    monaco.languages.register({ id: 'mdc' })
    monaco.languages.setMonarchTokensProvider('mdc', monarchMdc)
    monaco.languages.setLanguageConfiguration('mdc', {
    surroundingPairs: [
    { open: '{', close: '}' },
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: '<', close: '>' },
    { open: "'", close: "'" },
    { open: '"', close: '"' },
    ],
    autoClosingPairs: [
    { open: '{', close: '}' },
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: "'", close: "'" },
    { open: '"', close: '"' },
    ],
    })
    const {
    language: mdc,
    formatter: mdcFormatter,
    foldingProvider: mdcFoldingProvider,
    } = await import(/* @vite-ignore */`${MDC_CDN_BASE}dist/index.mjs`)
    _monaco.languages.register({ id: 'mdc' })
    _monaco.languages.setMonarchTokensProvider('mdc', mdc)
    _monaco.languages.registerDocumentFormattingEditProvider('mdc', {
    provideDocumentFormattingEdits: (model: any) => [{
    range: model.getFullModelRange(),
    text: mdcFormatter(model.getValue(), {
    tabSize: props.tabSize,
    }),
    }],
    })
    _monaco.languages.registerOnTypeFormattingEditProvider('mdc', {
    autoFormatTriggerCharacters: ['\n'],
    provideOnTypeFormattingEdits: (model: any) => [{
    range: model.getFullModelRange(),
    text: mdcFormatter(model.getValue(), {
    tabSize: props.tabSize,
    isFormatOnType: true,
    }),
    }],
    })
    _monaco.languages.registerFoldingRangeProvider('mdc', {
    provideFoldingRanges: (model: any) => mdcFoldingProvider(model),
    })
    _monaco.languages.setLanguageConfiguration('mdc', {
    surroundingPairs: [
    { open: '{', close: '}' },
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: '\'', close: '\'' },
    { open: '"', close: '"' },
    { open: '`', close: '`' },
    ],
    autoClosingPairs: [
    { open: '{', close: '}' },
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: '"', close: '"' },
    ],
    })
    return { monaco }
    if (!editorEl.value) {
    createError('MonacoEditor must be called in the browser')
    return
    }
    },
    })
    onMounted(() => {
    onLoaded(({ monaco }) => {
    const editor = new monaco.editor.create(unref(editorEl), {
    value: md.value,
    language: 'mdc',
    tabSize: 2,
    wordWrap: 'on',
    const _editor = _monaco.editor.create(editorEl.value, {
    value: code.value,
    language: props.language,
    tabSize: props.tabSize,
    wordWrap: props.wordWrap,
    wrappingStrategy: 'advanced',
    insertSpaces: true,
    theme: 'vs-dark',
    autoIndent: true,
    theme: props.theme,
    autoIndent: 'full',
    folding: true,
    detectIndentation: false,
    formatOnType: true,
    formatOnPaste: true,
    formatOnType: true
    automaticLayout: true,
    readOnly: props.readOnly,
    minimap: {
    enabled: props.minimap,
    },
    lineNumbers: 'on',
    scrollBeyondLastLine: false,
    bracketPairColorization: {
    enabled: true,
    },
    roundedSelection: false,
    fontSize: 14,
    padding: {
    top: 8,
    },
    })
    setTimeout(() => {
    editor.getAction('editor.action.formatDocument').run();
    }, 1000)
    })
    _editor.onDidChangeModelContent(() => {
    code.value = _editor.getValue()
    })
    const updateHeight = () => {
    if (!props.fitContent) return
    const contentHeight = _editor.getContentHeight()
    editorEl.value!.style.height = `${contentHeight}px`
    _editor.layout({
    width: editorEl.value!.offsetWidth,
    height: Math.min(contentHeight, editorEl.value!.offsetHeight),
    })
    }
    _editor.onDidContentSizeChange(updateHeight)
    return {
    editor: _editor,
    monaco: _monaco,
    }
    },
    })
    onMounted(async () => {
    try {
    const l = await load()
    if (!l) return
    monaco.value = l.monaco
    editor.value = l.editor
    }
    catch (error) {
    console.error('Failed to initialize Monaco:', error)
    }
    })
    // watch(code, (newCode) => {
    // if (editor.value && editor.value.getValue() !== newCode) {
    // editor.value.setValue(newCode)
    // }
    // })
    watch(() => props.theme, (newTheme) => {
    if (monaco.value) {
    monaco.value.editor.setTheme(newTheme)
    }
    })
    </script>

    <template>
    <div class="h-[100svh]">
    <span v-if="status !== 'loaded'">
    <div v-if="status !== 'loaded'">
    <slot :status="status">
    {{ status }}
    </span>
    <div v-else class="h-full" ref="editor" />
    </slot>
    </div>
    <div v-else ref="editor" :style="styling" />
    </template>
  2. sandros94 revised this gist Nov 18, 2024. No changes.
  3. sandros94 created this gist Nov 18, 2024.
    86 changes: 86 additions & 0 deletions monaco-editor.vue
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,86 @@
    <script setup lang="ts">
    const md = ref('')
    const editorEl = useTemplateRef('editor')
    const MONACO_CDN_BASE = 'https://unpkg.com/[email protected]/dev/'
    const { onLoaded, status } = useScriptNpm({
    packageName: 'monaco-editor',
    file: 'dev/vs/loader.js',
    version: '0.52.0',
    scriptOptions: {
    trigger: 'onNuxtReady',
    async use() {
    // @ts-expect-error: in-browser event
    window.require.config({ paths: { vs: `${MONACO_CDN_BASE}vs` } })
    // @ts-expect-error: in-browser event
    const monaco = await new Promise<any>(resolve => window.require(['vs/editor/editor.main'], resolve))
    // @ts-expect-error: in-browser event
    window.MonacoEnvironment = {
    getWorkerUrl: function () {
    return `data:text/javascript;charset=utf-8,${encodeURIComponent(`
    self.MonacoEnvironment = {
    baseUrl: '${MONACO_CDN_BASE}'
    };
    importScripts('${MONACO_CDN_BASE}vs/base/worker/workerMain.js');`,
    )}`
    },
    }
    // @ts-expect-error: in-browser event
    const { language: monarchMdc } = await import('https://cdn.jsdelivr.net/npm/@nuxtlabs/[email protected]/dist/index.mjs')
    monaco.languages.register({ id: 'mdc' })
    monaco.languages.setMonarchTokensProvider('mdc', monarchMdc)
    monaco.languages.setLanguageConfiguration('mdc', {
    surroundingPairs: [
    { open: '{', close: '}' },
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: '<', close: '>' },
    { open: "'", close: "'" },
    { open: '"', close: '"' },
    ],
    autoClosingPairs: [
    { open: '{', close: '}' },
    { open: '[', close: ']' },
    { open: '(', close: ')' },
    { open: "'", close: "'" },
    { open: '"', close: '"' },
    ],
    })
    return { monaco }
    }
    },
    })
    onMounted(() => {
    onLoaded(({ monaco }) => {
    const editor = new monaco.editor.create(unref(editorEl), {
    value: md.value,
    language: 'mdc',
    tabSize: 2,
    wordWrap: 'on',
    insertSpaces: true,
    theme: 'vs-dark',
    autoIndent: true,
    formatOnPaste: true,
    formatOnType: true
    })
    setTimeout(() => {
    editor.getAction('editor.action.formatDocument').run();
    }, 1000)
    })
    })
    </script>

    <template>
    <div class="h-[100svh]">
    <span v-if="status !== 'loaded'">
    {{ status }}
    </span>
    <div v-else class="h-full" ref="editor" />
    </div>
    </template>