11import tinycolor from 'tinycolor2' ;
22import { basename , extname , isObject , isDarkTheme } from '../utils.ts' ;
33import { onInputDebounce } from '../utils/dom.ts' ;
4+ import type MonacoNamespace from 'monaco-editor' ;
5+
6+ type Monaco = typeof MonacoNamespace ;
7+ type IStandaloneCodeEditor = MonacoNamespace . editor . IStandaloneCodeEditor ;
8+ type IEditorOptions = MonacoNamespace . editor . IEditorOptions ;
9+ type IGlobalEditorOptions = MonacoNamespace . editor . IGlobalEditorOptions ;
10+ type ITextModelUpdateOptions = MonacoNamespace . editor . ITextModelUpdateOptions ;
11+ type MonacoOpts = IEditorOptions & IGlobalEditorOptions & ITextModelUpdateOptions ;
12+
13+ type EditorConfig = {
14+ indent_style ?: 'tab' | 'space' ,
15+ indent_size ?: number ,
16+ tab_width ?: number ,
17+ end_of_line ?: 'lf' | 'cr' | 'crlf' ,
18+ charset ?: 'latin1' | 'utf-8' | 'utf-8-bom' | 'utf-16be' | 'utf-16le' ,
19+ trim_trailing_whitespace ?: boolean ,
20+ insert_final_newline ?: boolean ,
21+ root ?: boolean ,
22+ }
423
5- const languagesByFilename = { } ;
6- const languagesByExt = { } ;
24+ const languagesByFilename : Record < string , string > = { } ;
25+ const languagesByExt : Record < string , string > = { } ;
726
8- const baseOptions = {
27+ const baseOptions : MonacoOpts = {
928 fontFamily : 'var(--fonts-monospace)' ,
1029 fontSize : 14 , // https://github.com/microsoft/monaco-editor/issues/2242
1130 guides : { bracketPairs : false , indentation : false } ,
@@ -15,21 +34,21 @@ const baseOptions = {
1534 overviewRulerLanes : 0 ,
1635 renderLineHighlight : 'all' ,
1736 renderLineHighlightOnlyWhenFocus : true ,
18- rulers : false ,
37+ rulers : [ ] ,
1938 scrollbar : { horizontalScrollbarSize : 6 , verticalScrollbarSize : 6 } ,
2039 scrollBeyondLastLine : false ,
2140 automaticLayout : true ,
2241} ;
2342
24- function getEditorconfig ( input : HTMLInputElement ) {
43+ function getEditorconfig ( input : HTMLInputElement ) : EditorConfig | null {
2544 try {
26- return JSON . parse ( input . getAttribute ( 'data-editorconfig' ) ) ;
45+ return JSON . parse ( input . getAttribute ( 'data-editorconfig' ) ?? '' ) ;
2746 } catch {
2847 return null ;
2948 }
3049}
3150
32- function initLanguages ( monaco ) {
51+ function initLanguages ( monaco : Monaco ) : void {
3352 for ( const { filenames, extensions, id} of monaco . languages . getLanguages ( ) ) {
3453 for ( const filename of filenames || [ ] ) {
3554 languagesByFilename [ filename ] = id ;
@@ -40,35 +59,26 @@ function initLanguages(monaco) {
4059 }
4160}
4261
43- function getLanguage ( filename ) {
62+ function getLanguage ( filename : string ) : string {
4463 return languagesByFilename [ filename ] || languagesByExt [ extname ( filename ) ] || 'plaintext' ;
4564}
4665
47- function updateEditor ( monaco , editor , filename , lineWrapExts ) {
66+ function updateEditor ( monaco : Monaco , editor : IStandaloneCodeEditor , filename : string , lineWrapExts : string [ ] ) {
4867 editor . updateOptions ( getFileBasedOptions ( filename , lineWrapExts ) ) ;
4968 const model = editor . getModel ( ) ;
69+ if ( ! model ) return ;
5070 const language = model . getLanguageId ( ) ;
5171 const newLanguage = getLanguage ( filename ) ;
5272 if ( language !== newLanguage ) monaco . editor . setModelLanguage ( model , newLanguage ) ;
5373}
5474
5575// export editor for customization - https://github.com/go-gitea/gitea/issues/10409
56- function exportEditor ( editor ) {
76+ function exportEditor ( editor : IStandaloneCodeEditor ) : void {
5777 if ( ! window . codeEditors ) window . codeEditors = [ ] ;
5878 if ( ! window . codeEditors . includes ( editor ) ) window . codeEditors . push ( editor ) ;
5979}
6080
61- export async function createMonaco ( textarea : HTMLTextAreaElement , filename : string , editorOpts : Record < string , any > ) {
62- const monaco = await import ( /* webpackChunkName: "monaco" */ 'monaco-editor' ) ;
63-
64- initLanguages ( monaco ) ;
65- let { language, ...other } = editorOpts ;
66- if ( ! language ) language = getLanguage ( filename ) ;
67-
68- const container = document . createElement ( 'div' ) ;
69- container . className = 'monaco-editor-container' ;
70- textarea . parentNode . append ( container ) ;
71-
81+ function updateTheme ( monaco : Monaco ) : void {
7282 // https://github.com/microsoft/monaco-editor/issues/2427
7383 // also, monaco can only parse 6-digit hex colors, so we convert the colors to that format
7484 const styles = window . getComputedStyle ( document . documentElement ) ;
@@ -80,6 +90,7 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri
8090 rules : [
8191 {
8292 background : getColor ( '--color-code-bg' ) ,
93+ token : '' ,
8394 } ,
8495 ] ,
8596 colors : {
@@ -101,6 +112,22 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri
101112 'focusBorder' : '#0000' , // prevent blue border
102113 } ,
103114 } ) ;
115+ }
116+
117+ type CreateMonacoOpts = MonacoOpts & { language ?: string } ;
118+
119+ export async function createMonaco ( textarea : HTMLTextAreaElement , filename : string , opts : CreateMonacoOpts ) {
120+ const monaco = await import ( /* webpackChunkName: "monaco" */ 'monaco-editor' ) ;
121+
122+ initLanguages ( monaco ) ;
123+ let { language, ...other } = opts ;
124+ if ( ! language ) language = getLanguage ( filename ) ;
125+
126+ const container = document . createElement ( 'div' ) ;
127+ container . className = 'monaco-editor-container' ;
128+ textarea . parentNode ?. append ( container ) ;
129+
130+ updateTheme ( monaco ) ;
104131
105132 const editor = monaco . editor . create ( container , {
106133 value : textarea . value ,
@@ -109,13 +136,20 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri
109136 ...other ,
110137 } ) ;
111138
139+ window . matchMedia ( '(prefers-color-scheme: dark)' ) . addEventListener ( 'change' , ( ) => {
140+ updateTheme ( monaco ) ;
141+ } ) ;
142+
112143 monaco . editor . addKeybindingRules ( [
113144 { keybinding : monaco . KeyCode . Enter , command : null } , // disable enter from accepting code completion
114145 ] ) ;
115146
116147 const model = editor . getModel ( ) ;
117- model . onDidChangeContent ( ( ) => {
118- textarea . value = editor . getValue ( { preserveBOM : true } ) ;
148+ model ?. onDidChangeContent ( ( ) => {
149+ textarea . value = editor . getValue ( {
150+ preserveBOM : true ,
151+ lineEnding : '' ,
152+ } ) ;
119153 textarea . dispatchEvent ( new Event ( 'change' ) ) ; // seems to be needed for jquery-are-you-sure
120154 } ) ;
121155
@@ -127,13 +161,13 @@ export async function createMonaco(textarea: HTMLTextAreaElement, filename: stri
127161 return { monaco, editor} ;
128162}
129163
130- function getFileBasedOptions ( filename : string , lineWrapExts : string [ ] ) {
164+ function getFileBasedOptions ( filename : string , lineWrapExts : string [ ] ) : IEditorOptions & IGlobalEditorOptions {
131165 return {
132166 wordWrap : ( lineWrapExts || [ ] ) . includes ( extname ( filename ) ) ? 'on' : 'off' ,
133167 } ;
134168}
135169
136- function togglePreviewDisplay ( previewable : boolean ) {
170+ function togglePreviewDisplay ( previewable : boolean ) : void {
137171 const previewTab = document . querySelector < HTMLElement > ( 'a[data-tab="preview"]' ) ;
138172 if ( ! previewTab ) return ;
139173
@@ -145,19 +179,19 @@ function togglePreviewDisplay(previewable: boolean) {
145179 // then the "preview" tab becomes inactive (hidden), so the "write" tab should become active
146180 if ( previewTab . classList . contains ( 'active' ) ) {
147181 const writeTab = document . querySelector < HTMLElement > ( 'a[data-tab="write"]' ) ;
148- writeTab . click ( ) ;
182+ writeTab ? .click ( ) ;
149183 }
150184 }
151185}
152186
153- export async function createCodeEditor ( textarea : HTMLTextAreaElement , filenameInput : HTMLInputElement ) {
187+ export async function createCodeEditor ( textarea : HTMLTextAreaElement , filenameInput : HTMLInputElement ) : Promise < IStandaloneCodeEditor > {
154188 const filename = basename ( filenameInput . value ) ;
155189 const previewableExts = new Set ( ( textarea . getAttribute ( 'data-previewable-extensions' ) || '' ) . split ( ',' ) ) ;
156190 const lineWrapExts = ( textarea . getAttribute ( 'data-line-wrap-extensions' ) || '' ) . split ( ',' ) ;
157- const previewable = previewableExts . has ( extname ( filename ) ) ;
191+ const isPreviewable = previewableExts . has ( extname ( filename ) ) ;
158192 const editorConfig = getEditorconfig ( filenameInput ) ;
159193
160- togglePreviewDisplay ( previewable ) ;
194+ togglePreviewDisplay ( isPreviewable ) ;
161195
162196 const { monaco, editor} = await createMonaco ( textarea , filename , {
163197 ...baseOptions ,
@@ -175,13 +209,13 @@ export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameIn
175209 return editor ;
176210}
177211
178- function getEditorConfigOptions ( ec : Record < string , any > ) : Record < string , any > {
179- if ( ! isObject ( ec ) ) return { } ;
212+ function getEditorConfigOptions ( ec : EditorConfig | null ) : MonacoOpts {
213+ if ( ! ec || ! isObject ( ec ) ) return { } ;
180214
181- const opts : Record < string , any > = { } ;
215+ const opts : MonacoOpts = { } ;
182216 opts . detectIndentation = ! ( 'indent_style' in ec ) || ! ( 'indent_size' in ec ) ;
183217 if ( 'indent_size' in ec ) opts . indentSize = Number ( ec . indent_size ) ;
184- if ( 'tab_width' in ec ) opts . tabSize = Number ( ec . tab_width ) || opts . indentSize ;
218+ if ( 'tab_width' in ec ) opts . tabSize = Number ( ec . tab_width ) || ( opts . indentSize as number ) ;
185219 if ( 'max_line_length' in ec ) opts . rulers = [ Number ( ec . max_line_length ) ] ;
186220 opts . trimAutoWhitespace = ec . trim_trailing_whitespace === true ;
187221 opts . insertSpaces = ec . indent_style === 'space' ;
0 commit comments