From 0d006290a7b8c96e544d4d73b8bf7a5b2047bbf4 Mon Sep 17 00:00:00 2001 From: silverwind Date: Thu, 26 Feb 2026 11:50:44 +0100 Subject: [PATCH] Inline and lazy-load EasyMDE CSS, fix border colors (#36714) Replace the external easymde.min.css import with an inlined and lazy-loaded CSS file that uses proper theme variables for border colors. All EasyMDE/CodeMirror rules are scoped under `.EasyMDEContainer`, removing the need for !important overrides. - Fixes easymde borders, these were broken since a while now - Scope all easymde styles to .EasyMDEContainer - Inline easymde.min.css and codemirror.css into web_src/css/easymde.css - Lazy-load the CSS alongside the JS in switchToEasyMDE() - Fix .editor-toolbar and .CodeMirror border colors to use --color-input-border matching textarea inputs - Remove unused gutter, line number, and other unconfigured styles - Move .editor-loading to codeeditor.css where it belongs image --------- Signed-off-by: silverwind Co-authored-by: Claude Opus 4.6 Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- web_src/css/codemirror/base.css | 49 -- web_src/css/codemirror/dark.css | 88 ++-- web_src/css/easymde.css | 442 ++++++++++++++++++ web_src/css/editor/fileeditor.css | 56 --- web_src/css/features/codeeditor.css | 5 + web_src/css/index.css | 2 - .../js/features/comp/ComboMarkdownEditor.ts | 6 +- web_src/js/index-domready.ts | 1 - 8 files changed, 495 insertions(+), 154 deletions(-) delete mode 100644 web_src/css/codemirror/base.css create mode 100644 web_src/css/easymde.css delete mode 100644 web_src/css/editor/fileeditor.css diff --git a/web_src/css/codemirror/base.css b/web_src/css/codemirror/base.css deleted file mode 100644 index aedf7d8560..0000000000 --- a/web_src/css/codemirror/base.css +++ /dev/null @@ -1,49 +0,0 @@ -.ui .field:not(:last-child) .EasyMDEContainer .editor-statusbar { - margin-bottom: -1em; /* when there is a statusbar, the "margin-bottom: 1em" of the "field" is not needed, because the statusbar is likely a blank line */ -} - -.EasyMDEContainer .CodeMirror { - color: var(--color-input-text); - background-color: var(--color-input-background); - border-color: var(--color-secondary); - font: 14px var(--fonts-monospace); -} - -.EasyMDEContainer .CodeMirror.cm-s-default { - border-radius: var(--border-radius); - padding: 0 !important; -} - -.EasyMDEContainer .CodeMirror.CodeMirror-fullscreen.CodeMirror-focused { - border-right: 1px solid var(--color-primary) !important; -} - -.CodeMirror-cursor { - border-color: var(--color-caret) !important; -} - -.CodeMirror .cm-comment { - background: inherit !important; -} - -.CodeMirror .CodeMirror-code { - font: 14px var(--fonts-monospace); -} - -.CodeMirror-selected { - background: var(--color-primary-light-1) !important; - color: var(--color-white) !important; -} - -.CodeMirror-placeholder { - color: var(--color-placeholder-text) !important; - opacity: 1 !important; -} - -.CodeMirror-focused { - border-color: var(--color-primary) !important; -} - -.CodeMirror :focus { - outline: none; -} diff --git a/web_src/css/codemirror/dark.css b/web_src/css/codemirror/dark.css index 8a20d1c004..0fcc13c076 100644 --- a/web_src/css/codemirror/dark.css +++ b/web_src/css/codemirror/dark.css @@ -1,106 +1,106 @@ -.CodeMirror.cm-s-default .cm-property, -.CodeMirror.cm-s-paper .cm-property { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-property, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-property { color: #a0cc75; } -.CodeMirror.cm-s-default .cm-header, -.CodeMirror.cm-s-paper .cm-header { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-header, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-header { color: #9daccc; } -.CodeMirror.cm-s-default .cm-quote, -.CodeMirror.cm-s-paper .cm-quote { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-quote, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-quote { color: #009900; } -.CodeMirror.cm-s-default .cm-keyword, -.CodeMirror.cm-s-paper .cm-keyword { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-keyword, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-keyword { color: #cc8a61; } -.CodeMirror.cm-s-default .cm-atom, -.CodeMirror.cm-s-paper .cm-atom { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-atom, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-atom { color: #ef5e77; } -.CodeMirror.cm-s-default .cm-number, -.CodeMirror.cm-s-paper .cm-number { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-number, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-number { color: #ff5656; } -.CodeMirror.cm-s-default .cm-def, -.CodeMirror.cm-s-paper .cm-def { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-def, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-def { color: #e4e4e4; } -.CodeMirror.cm-s-default .cm-variable-2, -.CodeMirror.cm-s-paper .cm-variable-2 { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-variable-2, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-variable-2 { color: #00bdbf; } -.CodeMirror.cm-s-default .cm-variable-3, -.CodeMirror.cm-s-paper .cm-variable-3 { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-variable-3, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-variable-3 { color: #008855; } -.CodeMirror.cm-s-default .cm-comment, -.CodeMirror.cm-s-paper .cm-comment { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-comment, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-comment { color: #8e9ab3; } -.CodeMirror.cm-s-default .cm-string, -.CodeMirror.cm-s-paper .cm-string { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-string, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-string { color: #a77272; } -.CodeMirror.cm-s-default .cm-string-2, -.CodeMirror.cm-s-paper .cm-string-2 { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-string-2, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-string-2 { color: #ff5500; } -.CodeMirror.cm-s-default .cm-meta, -.CodeMirror.cm-s-paper .cm-meta, -.CodeMirror.cm-s-default .cm-qualifier, -.CodeMirror.cm-s-paper .cm-qualifier { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-meta, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-meta, +.EasyMDEContainer .CodeMirror.cm-s-default .cm-qualifier, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-qualifier { color: #ffb176; } -.CodeMirror.cm-s-default .cm-builtin, -.CodeMirror.cm-s-paper .cm-builtin { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-builtin, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-builtin { color: #b7c951; } -.CodeMirror.cm-s-default .cm-bracket, -.CodeMirror.cm-s-paper .cm-bracket { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-bracket, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-bracket { color: #999977; } -.CodeMirror.cm-s-default .cm-tag, -.CodeMirror.cm-s-paper .cm-tag { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-tag, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-tag { color: #f1d273; } -.CodeMirror.cm-s-default .cm-attribute, -.CodeMirror.cm-s-paper .cm-attribute { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-attribute, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-attribute { color: #bfcc70; } -.CodeMirror.cm-s-default .cm-hr, -.CodeMirror.cm-s-paper .cm-hr { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-hr, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-hr { color: #999999; } -.CodeMirror.cm-s-default .cm-url, -.CodeMirror.cm-s-paper .cm-url { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-url, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-url { color: #c5cfd0; } -.CodeMirror.cm-s-default .cm-link, -.CodeMirror.cm-s-paper .cm-link { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-link, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-link { color: #d8c792; } -.CodeMirror.cm-s-default .cm-error, -.CodeMirror.cm-s-paper .cm-error { +.EasyMDEContainer .CodeMirror.cm-s-default .cm-error, +.EasyMDEContainer .CodeMirror.cm-s-paper .cm-error { color: #dbdbeb; } diff --git a/web_src/css/easymde.css b/web_src/css/easymde.css new file mode 100644 index 0000000000..a3193bcfbe --- /dev/null +++ b/web_src/css/easymde.css @@ -0,0 +1,442 @@ +/* Inlined styles from easymde.min.css (includes EasyMDE and CodeMirror base) */ +.EasyMDEContainer { + display: block; +} + +/* CodeMirror base layout (from codemirror.css) */ +.EasyMDEContainer .CodeMirror { + position: relative; + overflow: hidden; + box-sizing: border-box; + height: auto; + border: 1px solid var(--color-input-border); + border-bottom-left-radius: var(--border-radius); + border-bottom-right-radius: var(--border-radius); + padding: 10px; + font: 14px var(--fonts-monospace); + z-index: 0; + overflow-wrap: break-word; + color: var(--color-input-text); + background-color: var(--color-input-background); + direction: ltr; +} + +.EasyMDEContainer .CodeMirror.cm-s-default { + border-radius: var(--border-radius); + padding: 0; +} + +.EasyMDEContainer .CodeMirror-lines { + padding: 4px 0; + cursor: text; + min-height: 1px; +} + +.EasyMDEContainer .CodeMirror pre.CodeMirror-line, +.EasyMDEContainer .CodeMirror pre.CodeMirror-line-like { + padding: 0 4px; + border-radius: 0; + border-width: 0; + background: transparent; + font-family: inherit; + font-size: inherit; + margin: 0; + white-space: pre; + overflow-wrap: normal; + line-height: inherit; + color: inherit; + z-index: 2; + position: relative; + overflow: visible; + font-variant-ligatures: contextual; +} + +.EasyMDEContainer .CodeMirror-wrap pre.CodeMirror-line, +.EasyMDEContainer .CodeMirror-wrap pre.CodeMirror-line-like { + overflow-wrap: break-word; + white-space: pre-wrap; + word-break: normal; +} + +.EasyMDEContainer .CodeMirror-scroll { + overflow: scroll !important; /* things will break if this is overridden */ + margin-bottom: -50px; + margin-right: -50px; + padding-bottom: 50px; + height: 100%; + outline: none; + position: relative; + z-index: 0; + cursor: text; +} + +.EasyMDEContainer .CodeMirror-sizer { + position: relative; + border-right: 50px solid transparent; +} + +.EasyMDEContainer .CodeMirror-vscrollbar, +.EasyMDEContainer .CodeMirror-hscrollbar, +.EasyMDEContainer .CodeMirror-scrollbar-filler, +.EasyMDEContainer .CodeMirror-gutter-filler { + position: absolute; + z-index: 6; + display: none; + outline: none; +} + +.EasyMDEContainer .CodeMirror-vscrollbar { + right: 0; + top: 0; + overflow-x: hidden; + overflow-y: scroll; +} + +.EasyMDEContainer .CodeMirror-hscrollbar { + bottom: 0; + left: 0; + overflow-y: hidden; + overflow-x: scroll; +} + +.EasyMDEContainer .CodeMirror-scrollbar-filler { + right: 0; + bottom: 0; +} + +/* Cursor */ +.EasyMDEContainer .CodeMirror-cursor { + position: absolute; + pointer-events: none; + border-left: 1px solid var(--color-caret); + border-right: none; + width: 0; +} + +.EasyMDEContainer div.CodeMirror-cursors { + visibility: hidden; + position: relative; + z-index: 3; +} + +.EasyMDEContainer div.CodeMirror-dragcursors { + visibility: visible; +} + +.EasyMDEContainer .CodeMirror-focused div.CodeMirror-cursors { + visibility: visible; +} + +/* Selection */ +.EasyMDEContainer .CodeMirror-selected { + background: var(--color-primary-light-1); +} + +.EasyMDEContainer .CodeMirror-focused .CodeMirror-selected { + background: var(--color-primary-light-1); +} + +.EasyMDEContainer .CodeMirror-line::selection, +.EasyMDEContainer .CodeMirror-line > span::selection, +.EasyMDEContainer .CodeMirror-line > span > span::selection { + background: var(--color-primary-light-1); +} + +/* Misc */ +.EasyMDEContainer .cm-tab { + display: inline-block; + text-decoration: inherit; +} + +.EasyMDEContainer .CodeMirror-rtl pre { + direction: rtl; +} + +.EasyMDEContainer .CodeMirror-code { + font: 14px var(--fonts-monospace); + outline: none; +} + +.EasyMDEContainer .CodeMirror-scroll, +.EasyMDEContainer .CodeMirror-sizer { + box-sizing: content-box; +} + +.EasyMDEContainer .CodeMirror-measure { + position: absolute; + width: 100%; + height: 0; + overflow: hidden; + visibility: hidden; +} + +.EasyMDEContainer .CodeMirror-measure pre { + position: static; +} + +.EasyMDEContainer .CodeMirror-composing { + border-bottom: 2px solid; +} + +.EasyMDEContainer span.CodeMirror-selectedtext { + background: none; +} + +@media print { + .EasyMDEContainer .CodeMirror div.CodeMirror-cursors { + visibility: hidden; + } +} + +/* Default theme overrides */ +.EasyMDEContainer .cm-header, +.EasyMDEContainer .cm-strong { + font-weight: var(--font-weight-bold); +} + +.EasyMDEContainer .cm-em { + font-style: italic; +} + +.EasyMDEContainer .cm-link { + text-decoration: underline; +} + +.EasyMDEContainer .cm-strikethrough { + text-decoration: line-through; +} + +.EasyMDEContainer .cm-comment { + background: inherit; +} + +/* Placeholder */ +.EasyMDEContainer .CodeMirror-placeholder { + color: var(--color-placeholder-text); + opacity: 1; +} + +/* Focus */ +.EasyMDEContainer .CodeMirror-focused { + border-color: var(--color-primary); +} + +.EasyMDEContainer .CodeMirror :focus { + outline: none; +} + +/* Fullscreen */ +.EasyMDEContainer .CodeMirror-fullscreen { + background: var(--color-body); + position: fixed; + inset: 50px 0 0; + height: auto; + z-index: 8; + border-right: none; + border-bottom-right-radius: 0; +} + +.EasyMDEContainer .CodeMirror-fullscreen.CodeMirror-focused { + border-right: 1px solid var(--color-primary); +} + +/* Statusbar */ +.ui .field:not(:last-child) .EasyMDEContainer .editor-statusbar { + margin-bottom: -1em; /* when there is a statusbar, the "margin-bottom: 1em" of the "field" is not needed, because the statusbar is likely a blank line */ +} + +/* Toolbar */ +.EasyMDEContainer .editor-toolbar { + position: relative; + user-select: none; + padding: 9px 10px; + border-top: 1px solid var(--color-input-border); + border-left: 1px solid var(--color-input-border); + border-right: 1px solid var(--color-input-border); + border-top-left-radius: var(--border-radius); + border-top-right-radius: var(--border-radius); +} + +.EasyMDEContainer .editor-toolbar.fullscreen { + width: 100%; + height: 50px; + padding-top: 10px; + padding-bottom: 10px; + box-sizing: border-box; + background: var(--color-body); + border: 0; + position: fixed; + top: 0; + left: 0; + opacity: 1; + z-index: 9; +} + +.EasyMDEContainer .editor-toolbar button { + background: transparent; + display: inline-block; + text-align: center; + text-decoration: none; + height: 30px; + margin: 0; + padding: 0 6px; + border: none; + border-radius: 3px; + cursor: pointer; + font-weight: var(--font-weight-bold); + min-width: 30px; + white-space: nowrap; + color: var(--color-text-light); +} + +.EasyMDEContainer .editor-toolbar button:not(:hover) { + background-color: transparent; +} + +.EasyMDEContainer .editor-toolbar button:hover { + background: var(--color-hover); +} + +.EasyMDEContainer .editor-toolbar button.active { + background: var(--color-active); +} + +.EasyMDEContainer .editor-toolbar i.separator { + display: inline-block; + width: 0; + border-left: none; + border-right: 1px solid var(--color-input-border); + color: transparent; + text-indent: -10px; + margin: 0 6px; +} + +.EasyMDEContainer .editor-toolbar button::after { + font-family: Arial, "Helvetica Neue", Helvetica, sans-serif; + font-size: 65%; + vertical-align: text-bottom; + position: relative; + top: 2px; +} + +.EasyMDEContainer .editor-toolbar button.heading-1::after { + content: "1"; +} + +.EasyMDEContainer .editor-toolbar button.heading-2::after { + content: "2"; +} + +.EasyMDEContainer .editor-toolbar button.heading-3::after { + content: "3"; +} + +.EasyMDEContainer .editor-toolbar button.heading-bigger::after { + content: "\25B2"; +} + +.EasyMDEContainer .editor-toolbar button.heading-smaller::after { + content: "\25BC"; +} + +.EasyMDEContainer .editor-toolbar.disabled-for-preview button:not(.no-disable) { + opacity: 0.6; + pointer-events: none; +} + +/* hide preview button, we have the preview tab for this */ +.EasyMDEContainer .editor-toolbar:not(.fullscreen) .preview { + display: none; +} + +/* hide revert button in fullscreen, it breaks the page */ +.EasyMDEContainer .editor-toolbar.fullscreen .revert-to-textarea { + display: none; +} + +@media only screen and (max-width: 700px) { + .EasyMDEContainer .editor-toolbar i.no-mobile { + display: none; + } +} + +/* Statusbar */ +.EasyMDEContainer .editor-statusbar { + padding: 8px 10px; + font-size: 12px; + color: var(--color-text-light); + text-align: right; +} + +.EasyMDEContainer .editor-statusbar span { + display: inline-block; + min-width: 4em; + margin-left: 1em; +} + +.EasyMDEContainer .editor-statusbar .lines::before { + content: "lines: "; +} + +.EasyMDEContainer .editor-statusbar .words::before { + content: "words: "; +} + +.EasyMDEContainer .editor-statusbar .characters::before { + content: "characters: "; +} + +/* Preview */ +.EasyMDEContainer .editor-preview-full { + position: absolute; + width: 100%; + height: 100%; + top: 0; + left: 0; + z-index: 7; + overflow: auto; + display: none; + box-sizing: border-box; +} + +.EasyMDEContainer .editor-preview-side { + position: fixed; + bottom: 0; + width: 50%; + top: 50px; + right: 0; + z-index: 9; + overflow: auto; + display: none; + box-sizing: border-box; + border: 1px solid var(--color-secondary); + overflow-wrap: break-word; +} + +.EasyMDEContainer .editor-preview-active-side { + display: block; +} + +.EasyMDEContainer .editor-preview-active { + display: block; +} + +.EasyMDEContainer .editor-preview { + padding: 10px; + background-color: var(--color-body); +} + +.EasyMDEContainer .editor-preview > p { + margin-top: 0; +} + +.EasyMDEContainer .editor-preview pre { + background: var(--color-markup-code-block); + margin-bottom: 10px; +} + +.EasyMDEContainer .editor-preview table td, +.EasyMDEContainer .editor-preview table th { + border: 1px solid var(--color-secondary); + padding: 5px; +} diff --git a/web_src/css/editor/fileeditor.css b/web_src/css/editor/fileeditor.css deleted file mode 100644 index 12ae97a109..0000000000 --- a/web_src/css/editor/fileeditor.css +++ /dev/null @@ -1,56 +0,0 @@ -.editor-toolbar { - border-color: var(--color-secondary); -} - -.editor-toolbar.fullscreen { - background: var(--color-body); -} - -.editor-toolbar button { - border: none !important; - color: var(--color-text-light); -} - -.editor-toolbar button:not(:hover) { - background-color: transparent !important; -} - -.editor-toolbar i.separator { - border-left: none; - border-right-color: var(--color-secondary); -} - -.editor-toolbar button:hover { - background: var(--color-hover); -} - -.editor-toolbar button.active { - background: var(--color-active); -} - -/* hide preview button, we have the preview tab for this */ -.editor-toolbar:not(.fullscreen) .preview { - display: none; -} - -/* hide revert button in fullscreen, it breaks the page */ -.editor-toolbar.fullscreen .revert-to-textarea { - display: none; -} - -.editor-preview { - background-color: var(--color-body); -} - -.editor-preview-side { - border-color: var(--color-secondary); -} - -.editor-statusbar { - color: var(--color-text-light); -} - -.editor-loading { - padding: 1rem; - text-align: center; -} diff --git a/web_src/css/features/codeeditor.css b/web_src/css/features/codeeditor.css index 8df3429b09..33a9191f40 100644 --- a/web_src/css/features/codeeditor.css +++ b/web_src/css/features/codeeditor.css @@ -1,3 +1,8 @@ +.editor-loading { + padding: 1rem; + text-align: center; +} + .monaco-editor-container, .editor-loading.is-loading { width: 100%; diff --git a/web_src/css/index.css b/web_src/css/index.css index c02651d520..699ba221ca 100644 --- a/web_src/css/index.css +++ b/web_src/css/index.css @@ -52,7 +52,6 @@ @import "./markup/asciicast.css"; @import "./chroma/base.css"; -@import "./codemirror/base.css"; @import "./font_i18n.css"; @import "./base.css"; @import "./home.css"; @@ -74,7 +73,6 @@ @import "./repo/commit-sign.css"; @import "./repo/packages.css"; -@import "./editor/fileeditor.css"; @import "./editor/combomarkdowneditor.css"; @import "./org.css"; diff --git a/web_src/js/features/comp/ComboMarkdownEditor.ts b/web_src/js/features/comp/ComboMarkdownEditor.ts index fdc8a1d601..42104947df 100644 --- a/web_src/js/features/comp/ComboMarkdownEditor.ts +++ b/web_src/js/features/comp/ComboMarkdownEditor.ts @@ -318,8 +318,10 @@ export class ComboMarkdownEditor { async switchToEasyMDE() { if (this.easyMDE) return; - // EasyMDE's CSS should be loaded via webpack config, otherwise our own styles can not overwrite the default styles. - const {default: EasyMDE} = await import(/* webpackChunkName: "easymde" */'easymde'); + const [{default: EasyMDE}] = await Promise.all([ + import(/* webpackChunkName: "easymde" */'easymde'), + import(/* webpackChunkName: "easymde" */'../../../css/easymde.css'), + ]); const easyMDEOpt: EasyMDE.Options = { autoDownloadFontAwesome: false, element: this.textarea, diff --git a/web_src/js/index-domready.ts b/web_src/js/index-domready.ts index 187876df44..fb445b8df4 100644 --- a/web_src/js/index-domready.ts +++ b/web_src/js/index-domready.ts @@ -1,5 +1,4 @@ import '../fomantic/build/fomantic.js'; -import '../../node_modules/easymde/dist/easymde.min.css'; // TODO: lazy load in "switchToEasyMDE" import {initHtmx} from './htmx.ts'; import {initDashboardRepoList} from './features/dashboard.ts';