2020-05-14 19:06:01 +03:00
import { basename , extname , isObject , isDarkTheme } from '../utils.js' ;
2023-05-10 18:50:58 +03:00
import { onInputDebounce } from '../utils/dom.js' ;
2020-05-14 19:06:01 +03:00
const languagesByFilename = { } ;
const languagesByExt = { } ;
2021-04-08 12:53:00 +03:00
const baseOptions = {
fontFamily : 'var(--fonts-monospace)' ,
fontSize : 14 , // https://github.com/microsoft/monaco-editor/issues/2242
2021-10-19 10:23:58 +03:00
guides : { bracketPairs : false , indentation : false } ,
2021-04-08 12:53:00 +03:00
links : false ,
minimap : { enabled : false } ,
occurrencesHighlight : false ,
overviewRulerLanes : 0 ,
renderLineHighlight : 'all' ,
renderLineHighlightOnlyWhenFocus : true ,
rulers : false ,
scrollbar : { horizontalScrollbarSize : 6 , verticalScrollbarSize : 6 } ,
scrollBeyondLastLine : false ,
2022-10-20 04:54:18 +03:00
automaticLayout : true ,
2021-04-08 12:53:00 +03:00
} ;
2020-05-14 19:06:01 +03:00
function getEditorconfig ( input ) {
try {
2021-11-22 11:19:01 +03:00
return JSON . parse ( input . getAttribute ( 'data-editorconfig' ) ) ;
2020-06-10 00:31:15 +03:00
} catch {
2020-05-14 19:06:01 +03:00
return null ;
}
}
function initLanguages ( monaco ) {
for ( const { filenames , extensions , id } of monaco . languages . getLanguages ( ) ) {
for ( const filename of filenames || [ ] ) {
languagesByFilename [ filename ] = id ;
}
for ( const extension of extensions || [ ] ) {
languagesByExt [ extension ] = id ;
}
}
}
function getLanguage ( filename ) {
return languagesByFilename [ filename ] || languagesByExt [ extname ( filename ) ] || 'plaintext' ;
}
2020-11-14 06:57:34 +03:00
function updateEditor ( monaco , editor , filename , lineWrapExts ) {
2021-04-08 12:53:00 +03:00
editor . updateOptions ( getFileBasedOptions ( filename , lineWrapExts ) ) ;
2020-05-14 19:06:01 +03:00
const model = editor . getModel ( ) ;
2021-11-11 04:52:16 +03:00
const language = model . getLanguageId ( ) ;
2020-11-14 06:57:34 +03:00
const newLanguage = getLanguage ( filename ) ;
2020-05-14 19:06:01 +03:00
if ( language !== newLanguage ) monaco . editor . setModelLanguage ( model , newLanguage ) ;
}
2020-06-03 14:19:32 +03:00
// export editor for customization - https://github.com/go-gitea/gitea/issues/10409
function exportEditor ( editor ) {
if ( ! window . codeEditors ) window . codeEditors = [ ] ;
if ( ! window . codeEditors . includes ( editor ) ) window . codeEditors . push ( editor ) ;
}
2020-11-14 06:57:34 +03:00
export async function createMonaco ( textarea , filename , editorOpts ) {
2020-05-14 19:06:01 +03:00
const monaco = await import ( /* webpackChunkName: "monaco" */ 'monaco-editor' ) ;
2020-11-14 06:57:34 +03:00
2020-05-14 19:06:01 +03:00
initLanguages ( monaco ) ;
2020-11-14 06:57:34 +03:00
let { language , ... other } = editorOpts ;
if ( ! language ) language = getLanguage ( filename ) ;
2020-05-14 19:06:01 +03:00
const container = document . createElement ( 'div' ) ;
container . className = 'monaco-editor-container' ;
2023-05-09 05:35:49 +03:00
textarea . parentNode . append ( container ) ;
2020-05-14 19:06:01 +03:00
2021-04-08 12:53:00 +03:00
// https://github.com/microsoft/monaco-editor/issues/2427
const styles = window . getComputedStyle ( document . documentElement ) ;
const getProp = ( name ) => styles . getPropertyValue ( name ) . trim ( ) ;
monaco . editor . defineTheme ( 'gitea' , {
base : isDarkTheme ( ) ? 'vs-dark' : 'vs' ,
inherit : true ,
rules : [
{
background : getProp ( '--color-code-bg' ) ,
}
] ,
colors : {
'editor.background' : getProp ( '--color-code-bg' ) ,
'editor.foreground' : getProp ( '--color-text' ) ,
'editor.inactiveSelectionBackground' : getProp ( '--color-primary-light-4' ) ,
'editor.lineHighlightBackground' : getProp ( '--color-editor-line-highlight' ) ,
'editor.selectionBackground' : getProp ( '--color-primary-light-3' ) ,
'editor.selectionForeground' : getProp ( '--color-primary-light-3' ) ,
'editorLineNumber.background' : getProp ( '--color-code-bg' ) ,
'editorLineNumber.foreground' : getProp ( '--color-secondary-dark-6' ) ,
'editorWidget.background' : getProp ( '--color-body' ) ,
'editorWidget.border' : getProp ( '--color-secondary' ) ,
'input.background' : getProp ( '--color-input-background' ) ,
'input.border' : getProp ( '--color-input-border' ) ,
'input.foreground' : getProp ( '--color-input-text' ) ,
'scrollbar.shadow' : getProp ( '--color-shadow' ) ,
'progressBar.background' : getProp ( '--color-primary' ) ,
}
} ) ;
2022-11-09 13:02:19 +03:00
// Quick fix: https://github.com/microsoft/monaco-editor/issues/2962
monaco . languages . register ( { id : 'vs.editor.nullLanguage' } ) ;
monaco . languages . setLanguageConfiguration ( 'vs.editor.nullLanguage' , { } ) ;
2020-05-14 19:06:01 +03:00
const editor = monaco . editor . create ( container , {
value : textarea . value ,
2021-04-08 12:53:00 +03:00
theme : 'gitea' ,
2020-11-14 06:57:34 +03:00
language ,
... other ,
2020-05-14 19:06:01 +03:00
} ) ;
const model = editor . getModel ( ) ;
model . onDidChangeContent ( ( ) => {
textarea . value = editor . getValue ( ) ;
textarea . dispatchEvent ( new Event ( 'change' ) ) ; // seems to be needed for jquery-are-you-sure
} ) ;
2020-11-14 06:57:34 +03:00
exportEditor ( editor ) ;
2020-05-14 19:06:01 +03:00
const loading = document . querySelector ( '.editor-loading' ) ;
if ( loading ) loading . remove ( ) ;
2020-11-14 06:57:34 +03:00
return { monaco , editor } ;
}
2020-06-03 14:19:32 +03:00
2020-11-14 06:57:34 +03:00
function getFileBasedOptions ( filename , lineWrapExts ) {
return {
wordWrap : ( lineWrapExts || [ ] ) . includes ( extname ( filename ) ) ? 'on' : 'off' ,
} ;
2020-05-14 19:06:01 +03:00
}
2023-03-26 08:25:41 +03:00
function togglePreviewDisplay ( previewable ) {
const previewTab = document . querySelector ( 'a[data-tab="preview"]' ) ;
if ( ! previewTab ) return ;
if ( previewable ) {
2023-04-27 05:08:16 +03:00
const newUrl = ( previewTab . getAttribute ( 'data-url' ) || '' ) . replace ( /(.*)\/.*/ , ` $ 1/markup ` ) ;
2023-03-26 08:25:41 +03:00
previewTab . setAttribute ( 'data-url' , newUrl ) ;
previewTab . style . display = '' ;
} else {
previewTab . style . display = 'none' ;
// If the "preview" tab was active, user changes the filename to a non-previewable one,
// then the "preview" tab becomes inactive (hidden), so the "write" tab should become active
if ( previewTab . classList . contains ( 'active' ) ) {
const writeTab = document . querySelector ( 'a[data-tab="write"]' ) ;
writeTab . click ( ) ;
}
}
}
2023-03-24 09:12:23 +03:00
export async function createCodeEditor ( textarea , filenameInput ) {
2020-11-14 06:57:34 +03:00
const filename = basename ( filenameInput . value ) ;
2023-03-26 08:25:41 +03:00
const previewableExts = new Set ( ( textarea . getAttribute ( 'data-previewable-extensions' ) || '' ) . split ( ',' ) ) ;
2021-11-22 11:19:01 +03:00
const lineWrapExts = ( textarea . getAttribute ( 'data-line-wrap-extensions' ) || '' ) . split ( ',' ) ;
2023-03-26 08:25:41 +03:00
const previewable = previewableExts . has ( extname ( filename ) ) ;
2020-11-14 06:57:34 +03:00
const editorConfig = getEditorconfig ( filenameInput ) ;
2023-03-26 08:25:41 +03:00
togglePreviewDisplay ( previewable ) ;
2020-05-14 19:06:01 +03:00
2020-11-14 06:57:34 +03:00
const { monaco , editor } = await createMonaco ( textarea , filename , {
2021-04-08 12:53:00 +03:00
... baseOptions ,
2020-11-14 06:57:34 +03:00
... getFileBasedOptions ( filenameInput . value , lineWrapExts ) ,
... getEditorConfigOptions ( editorConfig ) ,
} ) ;
2023-05-10 18:50:58 +03:00
filenameInput . addEventListener ( 'input' , onInputDebounce ( ( ) => {
2020-11-14 06:57:34 +03:00
const filename = filenameInput . value ;
2023-03-26 08:25:41 +03:00
const previewable = previewableExts . has ( extname ( filename ) ) ;
togglePreviewDisplay ( previewable ) ;
2020-11-14 06:57:34 +03:00
updateEditor ( monaco , editor , filename , lineWrapExts ) ;
2023-03-26 08:25:41 +03:00
} ) ) ;
2020-11-14 06:57:34 +03:00
return editor ;
}
function getEditorConfigOptions ( ec ) {
if ( ! isObject ( ec ) ) return { } ;
const opts = { } ;
opts . detectIndentation = ! ( 'indent_style' in ec ) || ! ( 'indent_size' in ec ) ;
if ( 'indent_size' in ec ) opts . indentSize = Number ( ec . indent _size ) ;
if ( 'tab_width' in ec ) opts . tabSize = Number ( ec . tab _width ) || opts . indentSize ;
if ( 'max_line_length' in ec ) opts . rulers = [ Number ( ec . max _line _length ) ] ;
opts . trimAutoWhitespace = ec . trim _trailing _whitespace === true ;
opts . insertSpaces = ec . indent _style === 'space' ;
opts . useTabStops = ec . indent _style === 'tab' ;
2020-05-14 19:06:01 +03:00
return opts ;
}