@@ -2,6 +2,7 @@ import React, { useRef, useEffect, forwardRef, useImperativeHandle, useCallback,
22import { useTheme } from '../hooks/useTheme' ;
33import { MONACO_KEYBINDING_DEFINITIONS } from '../services/editor/monacoKeybindings' ;
44import { DEFAULT_SETTINGS } from '../constants' ;
5+ import { ensureMonaco } from '../services/editor/monacoLoader' ;
56
67// Let TypeScript know monaco is available on the window
78declare const monaco : any ;
@@ -114,6 +115,7 @@ const toMonacoKeybinding = (monacoApi: any, keys: string[]): number | null => {
114115const CodeEditor = forwardRef < CodeEditorHandle , CodeEditorProps > ( ( { content, language, onChange, onScroll, customShortcuts = { } , fontFamily, fontSize } , ref ) => {
115116 const editorRef = useRef < HTMLDivElement > ( null ) ;
116117 const monacoInstanceRef = useRef < any > ( null ) ;
118+ const monacoApiRef = useRef < any > ( null ) ;
117119 const { theme } = useTheme ( ) ;
118120 const contentRef = useRef ( content ) ;
119121 const customShortcutsRef = useRef < Record < string , string [ ] > > ( { } ) ;
@@ -135,7 +137,12 @@ const CodeEditor = forwardRef<CodeEditorHandle, CodeEditorProps>(({ content, lan
135137 monacoInstanceRef . current ?. getAction ( 'editor.action.formatDocument' ) ?. run ( ) ;
136138 } ,
137139 setScrollTop ( scrollTop : number ) {
138- monacoInstanceRef . current ?. setScrollTop ( scrollTop , monaco . editor . ScrollType . Immediate ) ;
140+ const scrollType = monacoApiRef . current ?. editor ?. ScrollType ?. Immediate ;
141+ if ( scrollType ) {
142+ monacoInstanceRef . current ?. setScrollTop ( scrollTop , scrollType ) ;
143+ } else {
144+ monacoInstanceRef . current ?. setScrollTop ( scrollTop ) ;
145+ }
139146 } ,
140147 getScrollInfo ( ) {
141148 return new Promise ( resolve => {
@@ -167,7 +174,8 @@ const CodeEditor = forwardRef<CodeEditorHandle, CodeEditorProps>(({ content, lan
167174 } , [ ] ) ;
168175
169176 const applyEditorShortcuts = useCallback ( ( ) => {
170- if ( ! monacoInstanceRef . current || typeof monaco === 'undefined' ) {
177+ const monacoApi = monacoApiRef . current ;
178+ if ( ! monacoInstanceRef . current || ! monacoApi ) {
171179 return ;
172180 }
173181
@@ -180,7 +188,7 @@ const CodeEditor = forwardRef<CodeEditorHandle, CodeEditorProps>(({ content, lan
180188 return ;
181189 }
182190
183- const keybinding = toMonacoKeybinding ( monaco , keys ) ;
191+ const keybinding = toMonacoKeybinding ( monacoApi , keys ) ;
184192 if ( keybinding === null ) {
185193 return ;
186194 }
@@ -211,77 +219,79 @@ const CodeEditor = forwardRef<CodeEditorHandle, CodeEditorProps>(({ content, lan
211219 } , [ content ] ) ;
212220
213221 useEffect ( ( ) => {
214- if ( editorRef . current && typeof ( ( window as any ) . require ) !== 'undefined' ) {
215- // Configure Monaco Environment to load workers from CDN. This is crucial for syntax highlighting.
216- if ( ! ( window as any ) . MonacoEnvironment ) {
217- ( window as any ) . MonacoEnvironment = {
218- getWorkerUrl : function ( _moduleId : any , label : string ) {
219- const CDN_PATH = 'https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs' ;
220- if ( label === 'json' ) return `${ CDN_PATH } /language/json/json.worker.js` ;
221- if ( label === 'css' || label === 'scss' || label === 'less' ) return `${ CDN_PATH } /language/css/css.worker.js` ;
222- if ( label === 'html' || label === 'handlebars' || label === 'razor' ) return `${ CDN_PATH } /language/html/html.worker.js` ;
223- if ( label === 'typescript' || label === 'javascript' ) return `${ CDN_PATH } /language/typescript/ts.worker.js` ;
224- return `${ CDN_PATH } /editor/editor.worker.js` ;
225- } ,
226- } ;
222+ let isCancelled = false ;
223+
224+ const initializeEditor = async ( ) => {
225+ if ( ! editorRef . current ) {
226+ return ;
227227 }
228228
229- ( window as any ) . require . config ( { paths : { 'vs' : 'https://cdn.jsdelivr.net/npm/monaco-editor@0.44.0/min/vs' } } ) ;
230- ( window as any ) . require ( [ 'vs/editor/editor.main' ] , ( ) => {
231- if ( editorRef . current ) {
232- // Ensure any previous instance is disposed
233- if ( monacoInstanceRef . current ) {
234- disposeEditorShortcuts ( ) ;
235- monacoInstanceRef . current . dispose ( ) ;
236- }
229+ try {
230+ const monacoApi = await ensureMonaco ( ) ;
231+ if ( ! monacoApi || isCancelled || ! editorRef . current ) {
232+ return ;
233+ }
237234
238- const editorInstance = monaco . editor . create ( editorRef . current , {
239- value : content ,
240- language : language || 'plaintext' ,
241- theme : theme === 'dark' ? 'vs-dark' : 'vs' ,
242- automaticLayout : true ,
243- fontSize : computedFontSize ,
244- fontFamily : computedFontFamily ,
245- minimap : {
246- enabled : true ,
247- } ,
248- wordWrap : 'on' ,
249- folding : true ,
250- showFoldingControls : 'always' ,
251- bracketPairColorization : {
252- enabled : true ,
253- } ,
254- } ) ;
235+ monacoApiRef . current = monacoApi ;
255236
256- editorInstance . onDidChangeModelContent ( ( ) => {
257- const currentValue = editorInstance . getValue ( ) ;
258- if ( currentValue !== contentRef . current ) {
259- onChange ( currentValue ) ;
260- }
261- } ) ;
237+ if ( monacoInstanceRef . current ) {
238+ disposeEditorShortcuts ( ) ;
239+ monacoInstanceRef . current . dispose ( ) ;
240+ }
262241
263- editorInstance . onDidScrollChange ( ( e : any ) => {
264- if ( e . scrollTopChanged ) {
265- onScroll ?.( {
266- scrollTop : e . scrollTop ,
267- scrollHeight : e . scrollHeight ,
268- clientHeight : editorInstance . getLayoutInfo ( ) . height
269- } ) ;
270- }
271- } ) ;
242+ const editorInstance = monacoApi . editor . create ( editorRef . current , {
243+ value : content ,
244+ language : language || 'plaintext' ,
245+ theme : theme === 'dark' ? 'vs-dark' : 'vs' ,
246+ automaticLayout : true ,
247+ fontSize : computedFontSize ,
248+ fontFamily : computedFontFamily ,
249+ minimap : {
250+ enabled : true ,
251+ } ,
252+ wordWrap : 'on' ,
253+ folding : true ,
254+ showFoldingControls : 'always' ,
255+ bracketPairColorization : {
256+ enabled : true ,
257+ } ,
258+ } ) ;
272259
273- monacoInstanceRef . current = editorInstance ;
274- applyEditorShortcuts ( ) ;
275- }
276- } ) ;
277- }
260+ editorInstance . onDidChangeModelContent ( ( ) => {
261+ const currentValue = editorInstance . getValue ( ) ;
262+ if ( currentValue !== contentRef . current ) {
263+ onChange ( currentValue ) ;
264+ }
265+ } ) ;
266+
267+ editorInstance . onDidScrollChange ( ( e : any ) => {
268+ if ( e . scrollTopChanged ) {
269+ onScroll ?.( {
270+ scrollTop : e . scrollTop ,
271+ scrollHeight : e . scrollHeight ,
272+ clientHeight : editorInstance . getLayoutInfo ( ) . height
273+ } ) ;
274+ }
275+ } ) ;
276+
277+ monacoInstanceRef . current = editorInstance ;
278+ applyEditorShortcuts ( ) ;
279+ } catch ( error ) {
280+ // eslint-disable-next-line no-console
281+ console . error ( 'Failed to initialize Monaco editor' , error ) ;
282+ }
283+ } ;
284+
285+ initializeEditor ( ) ;
278286
279287 return ( ) => {
288+ isCancelled = true ;
280289 disposeEditorShortcuts ( ) ;
281290 if ( monacoInstanceRef . current ) {
282291 monacoInstanceRef . current . dispose ( ) ;
283292 monacoInstanceRef . current = null ;
284293 }
294+ monacoApiRef . current = null ;
285295 } ;
286296 } , [ onChange , onScroll , applyEditorShortcuts , disposeEditorShortcuts , computedFontFamily , computedFontSize ] ) ;
287297
@@ -299,8 +309,8 @@ const CodeEditor = forwardRef<CodeEditorHandle, CodeEditorProps>(({ content, lan
299309
300310 // Effect to update theme
301311 useEffect ( ( ) => {
302- if ( monacoInstanceRef . current ) {
303- monaco . editor . setTheme ( theme === 'dark' ? 'vs-dark' : 'vs' ) ;
312+ if ( monacoInstanceRef . current && monacoApiRef . current ) {
313+ monacoApiRef . current . editor . setTheme ( theme === 'dark' ? 'vs-dark' : 'vs' ) ;
304314 }
305315 } , [ theme ] ) ;
306316
@@ -309,11 +319,11 @@ const CodeEditor = forwardRef<CodeEditorHandle, CodeEditorProps>(({ content, lan
309319 monacoInstanceRef . current . updateOptions ( { fontFamily : computedFontFamily , fontSize : computedFontSize } ) ;
310320 }
311321 } , [ computedFontFamily , computedFontSize ] ) ;
312-
322+
313323 // Effect to update language
314324 useEffect ( ( ) => {
315- if ( monacoInstanceRef . current && monacoInstanceRef . current . getModel ( ) ) {
316- monaco . editor . setModelLanguage ( monacoInstanceRef . current . getModel ( ) , language || 'plaintext' ) ;
325+ if ( monacoInstanceRef . current && monacoInstanceRef . current . getModel ( ) && monacoApiRef . current ) {
326+ monacoApiRef . current . editor . setModelLanguage ( monacoInstanceRef . current . getModel ( ) , language || 'plaintext' ) ;
317327 }
318328 } , [ language ] ) ;
319329
0 commit comments