mirror of
https://github.com/go-gitea/gitea.git
synced 2026-04-02 00:18:35 +01:00
Replace Monaco with CodeMirror (#36764)
- Replace monaco-editor with CodeMirror 6 - Add `--color-syntax-*` CSS variables for all syntax token types, shared by CodeMirror, Chroma and EasyMDE - Consolidate chroma CSS into a single theme-independent file (`modules/chroma.css`) - Syntax colors in the code editor now match the code view and light/dark themes - Code editor is now 12px instead of 14px font size to match code view and GitHub - Use a global style for kbd elements - When editing existing files, focus will be on codemirror instead of filename input. - Keyboard shortcuts are roughtly the same as VSCode - Add a "Find" button, useful for mobile - Add context menu similar to Monaco - Add a command palette (Ctrl/Cmd+Shift+P or F1) or via button - Add clickable URLs via Ctrl/Cmd+click - Add e2e test for the code editor - Remove `window.codeEditors` global - The main missing Monaco features are hover types and semantic rename but these were not fully working because monaco operated only on single files and only for JS/TS/HTML/CSS/JSON. | | Monaco (main) | CodeMirror (cm) | Delta | |---|---|---|---| | **Build time** | 7.8s | 5.3s | **-32%** | | **JS output** | 25 MB | 14 MB | **-44%** | | **CSS output** | 1.2 MB | 1012 KB | **-17%** | | **Total (no maps)** | 23.3 MB | 12.1 MB | **-48%** | Fixes: #36311 Fixes: #14776 Fixes: #12171 <img width="1333" height="555" alt="image" src="https://github.com/user-attachments/assets/f0fe3a28-1ed9-4f22-bf25-2b161501d7ce" /> --------- Signed-off-by: silverwind <me@silverwind.io> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: wxiaoguang <wxiaoguang@gmail.com> Co-authored-by: Giteabot <teabot@gitea.io> Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
@@ -329,7 +329,7 @@ export default defineConfig([
|
||||
'github/no-innerText': [2],
|
||||
'github/no-then': [2],
|
||||
'github/no-useless-passive': [2],
|
||||
'github/prefer-observers': [2],
|
||||
'github/prefer-observers': [0],
|
||||
'github/require-passive-events': [2],
|
||||
'github/unescaped-html-literal': [2],
|
||||
'grouped-accessor-pairs': [2],
|
||||
@@ -1013,7 +1013,7 @@ export default defineConfig([
|
||||
},
|
||||
},
|
||||
{
|
||||
files: ['*', 'tools/**/*'],
|
||||
files: ['*', 'tools/**/*', 'tests/**/*'],
|
||||
languageOptions: {globals: globals.nodeBuiltin},
|
||||
},
|
||||
{
|
||||
|
||||
@@ -213,6 +213,9 @@
|
||||
"editor.buttons.switch_to_legacy.tooltip": "Use the legacy editor instead",
|
||||
"editor.buttons.enable_monospace_font": "Enable monospace font",
|
||||
"editor.buttons.disable_monospace_font": "Disable monospace font",
|
||||
"editor.code_editor.command_palette": "Command Palette",
|
||||
"editor.code_editor.find": "Find",
|
||||
"editor.code_editor.placeholder": "Enter file content here",
|
||||
"filter.string.asc": "A–Z",
|
||||
"filter.string.desc": "Z–A",
|
||||
"error.occurred": "An error occurred",
|
||||
@@ -2250,8 +2253,6 @@
|
||||
"repo.settings.webhook.delivery.success": "An event has been added to the delivery queue. It may take few seconds before it shows up in the delivery history.",
|
||||
"repo.settings.githooks_desc": "Git Hooks are powered by Git itself. You can edit hook files below to set up custom operations.",
|
||||
"repo.settings.githook_edit_desc": "If the hook is inactive, sample content will be presented. Leaving content to an empty value will disable this hook.",
|
||||
"repo.settings.githook_name": "Hook Name",
|
||||
"repo.settings.githook_content": "Hook Content",
|
||||
"repo.settings.update_githook": "Update Hook",
|
||||
"repo.settings.add_webhook_desc": "Gitea will send <code>POST</code> requests with a specified content type to the target URL. Read more in the <a target=\"_blank\" rel=\"noopener noreferrer\" href=\"%s\">webhooks guide</a>.",
|
||||
"repo.settings.payload_url": "Target URL",
|
||||
|
||||
18
package.json
18
package.json
@@ -10,12 +10,28 @@
|
||||
"@citation-js/plugin-bibtex": "0.7.21",
|
||||
"@citation-js/plugin-csl": "0.7.22",
|
||||
"@citation-js/plugin-software-formats": "0.6.2",
|
||||
"@codemirror/autocomplete": "6.20.1",
|
||||
"@codemirror/commands": "6.10.3",
|
||||
"@codemirror/lang-json": "6.0.2",
|
||||
"@codemirror/lang-markdown": "6.5.0",
|
||||
"@codemirror/language": "6.12.3",
|
||||
"@codemirror/language-data": "6.5.2",
|
||||
"@codemirror/legacy-modes": "6.5.2",
|
||||
"@codemirror/lint": "6.9.5",
|
||||
"@codemirror/search": "6.6.0",
|
||||
"@codemirror/state": "6.6.0",
|
||||
"@codemirror/view": "6.40.0",
|
||||
"@github/markdown-toolbar-element": "2.2.3",
|
||||
"@github/paste-markdown": "1.5.3",
|
||||
"@github/text-expander-element": "2.9.4",
|
||||
"@lezer/highlight": "1.2.3",
|
||||
"@mcaptcha/vanilla-glue": "0.1.0-alpha-3",
|
||||
"@mermaid-js/layout-elk": "0.2.1",
|
||||
"@primer/octicons": "19.23.1",
|
||||
"@replit/codemirror-indentation-markers": "6.5.3",
|
||||
"@replit/codemirror-lang-nix": "6.0.1",
|
||||
"@replit/codemirror-lang-svelte": "6.0.0",
|
||||
"@replit/codemirror-vscode-keymap": "6.0.2",
|
||||
"@resvg/resvg-wasm": "2.6.2",
|
||||
"@silverwind/vue3-calendar-heatmap": "2.1.1",
|
||||
"@vitejs/plugin-vue": "6.0.5",
|
||||
@@ -25,6 +41,7 @@
|
||||
"chartjs-adapter-dayjs-4": "1.0.4",
|
||||
"chartjs-plugin-zoom": "2.2.0",
|
||||
"clippie": "4.1.10",
|
||||
"codemirror-lang-elixir": "4.0.1",
|
||||
"colord": "2.9.3",
|
||||
"compare-versions": "6.1.1",
|
||||
"cropperjs": "1.6.2",
|
||||
@@ -38,7 +55,6 @@
|
||||
"js-yaml": "4.1.1",
|
||||
"katex": "0.16.43",
|
||||
"mermaid": "11.13.0",
|
||||
"monaco-editor": "0.55.1",
|
||||
"online-3d-viewer": "0.18.0",
|
||||
"pdfobject": "2.3.1",
|
||||
"perfect-debounce": "2.1.0",
|
||||
|
||||
@@ -14,7 +14,7 @@ export default defineConfig({
|
||||
timeout,
|
||||
},
|
||||
use: {
|
||||
baseURL: env.GITEA_TEST_E2E_URL?.replace?.(/\/$/g, ''),
|
||||
baseURL: env.GITEA_TEST_E2E_URL?.replace?.(/\/$/, ''),
|
||||
locale: 'en-US',
|
||||
actionTimeout: timeout,
|
||||
navigationTimeout: 2 * timeout,
|
||||
|
||||
678
pnpm-lock.yaml
generated
678
pnpm-lock.yaml
generated
@@ -38,6 +38,39 @@ importers:
|
||||
'@citation-js/plugin-software-formats':
|
||||
specifier: 0.6.2
|
||||
version: 0.6.2
|
||||
'@codemirror/autocomplete':
|
||||
specifier: 6.20.1
|
||||
version: 6.20.1
|
||||
'@codemirror/commands':
|
||||
specifier: 6.10.3
|
||||
version: 6.10.3
|
||||
'@codemirror/lang-json':
|
||||
specifier: 6.0.2
|
||||
version: 6.0.2
|
||||
'@codemirror/lang-markdown':
|
||||
specifier: 6.5.0
|
||||
version: 6.5.0
|
||||
'@codemirror/language':
|
||||
specifier: 6.12.3
|
||||
version: 6.12.3
|
||||
'@codemirror/language-data':
|
||||
specifier: 6.5.2
|
||||
version: 6.5.2
|
||||
'@codemirror/legacy-modes':
|
||||
specifier: 6.5.2
|
||||
version: 6.5.2
|
||||
'@codemirror/lint':
|
||||
specifier: 6.9.5
|
||||
version: 6.9.5
|
||||
'@codemirror/search':
|
||||
specifier: 6.6.0
|
||||
version: 6.6.0
|
||||
'@codemirror/state':
|
||||
specifier: 6.6.0
|
||||
version: 6.6.0
|
||||
'@codemirror/view':
|
||||
specifier: 6.40.0
|
||||
version: 6.40.0
|
||||
'@github/markdown-toolbar-element':
|
||||
specifier: 2.2.3
|
||||
version: 2.2.3
|
||||
@@ -47,6 +80,9 @@ importers:
|
||||
'@github/text-expander-element':
|
||||
specifier: 2.9.4
|
||||
version: 2.9.4
|
||||
'@lezer/highlight':
|
||||
specifier: 1.2.3
|
||||
version: 1.2.3
|
||||
'@mcaptcha/vanilla-glue':
|
||||
specifier: 0.1.0-alpha-3
|
||||
version: 0.1.0-alpha-3
|
||||
@@ -56,6 +92,18 @@ importers:
|
||||
'@primer/octicons':
|
||||
specifier: 19.23.1
|
||||
version: 19.23.1
|
||||
'@replit/codemirror-indentation-markers':
|
||||
specifier: 6.5.3
|
||||
version: 6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)
|
||||
'@replit/codemirror-lang-nix':
|
||||
specifier: 6.0.1
|
||||
version: 6.0.1(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8)
|
||||
'@replit/codemirror-lang-svelte':
|
||||
specifier: 6.0.0
|
||||
version: 6.0.0(@codemirror/autocomplete@6.20.1)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8)
|
||||
'@replit/codemirror-vscode-keymap':
|
||||
specifier: 6.0.2
|
||||
version: 6.0.2(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)
|
||||
'@resvg/resvg-wasm':
|
||||
specifier: 2.6.2
|
||||
version: 2.6.2
|
||||
@@ -83,6 +131,9 @@ importers:
|
||||
clippie:
|
||||
specifier: 4.1.10
|
||||
version: 4.1.10
|
||||
codemirror-lang-elixir:
|
||||
specifier: 4.0.1
|
||||
version: 4.0.1
|
||||
colord:
|
||||
specifier: 2.9.3
|
||||
version: 2.9.3
|
||||
@@ -122,9 +173,6 @@ importers:
|
||||
mermaid:
|
||||
specifier: 11.13.0
|
||||
version: 11.13.0
|
||||
monaco-editor:
|
||||
specifier: 0.55.1
|
||||
version: 0.55.1
|
||||
online-3d-viewer:
|
||||
specifier: 0.18.0
|
||||
version: 0.18.0
|
||||
@@ -443,6 +491,96 @@ packages:
|
||||
resolution: {integrity: sha512-3XQOO3u4WXY/7AWZyQ+9SuBzS8bYTlJ+NF1uCgrZO64g36nK5iIc5YV9cBl2TL2QhHF6S36nvAsXsj5fX9FeHw==}
|
||||
engines: {node: '>=14.0.0'}
|
||||
|
||||
'@codemirror/autocomplete@6.20.1':
|
||||
resolution: {integrity: sha512-1cvg3Vz1dSSToCNlJfRA2WSI4ht3K+WplO0UMOgmUYPivCyy2oueZY6Lx7M9wThm7SDUBViRmuT+OG/i8+ON9A==}
|
||||
|
||||
'@codemirror/commands@6.10.3':
|
||||
resolution: {integrity: sha512-JFRiqhKu+bvSkDLI+rUhJwSxQxYb759W5GBezE8Uc8mHLqC9aV/9aTC7yJSqCtB3F00pylrLCwnyS91Ap5ej4Q==}
|
||||
|
||||
'@codemirror/lang-angular@0.1.4':
|
||||
resolution: {integrity: sha512-oap+gsltb/fzdlTQWD6BFF4bSLKcDnlxDsLdePiJpCVNKWXSTAbiiQeYI3UmES+BLAdkmIC1WjyztC1pi/bX4g==}
|
||||
|
||||
'@codemirror/lang-cpp@6.0.3':
|
||||
resolution: {integrity: sha512-URM26M3vunFFn9/sm6rzqrBzDgfWuDixp85uTY49wKudToc2jTHUrKIGGKs+QWND+YLofNNZpxcNGRynFJfvgA==}
|
||||
|
||||
'@codemirror/lang-css@6.3.1':
|
||||
resolution: {integrity: sha512-kr5fwBGiGtmz6l0LSJIbno9QrifNMUusivHbnA1H6Dmqy4HZFte3UAICix1VuKo0lMPKQr2rqB+0BkKi/S3Ejg==}
|
||||
|
||||
'@codemirror/lang-go@6.0.1':
|
||||
resolution: {integrity: sha512-7fNvbyNylvqCphW9HD6WFnRpcDjr+KXX/FgqXy5H5ZS0eC5edDljukm/yNgYkwTsgp2busdod50AOTIy6Jikfg==}
|
||||
|
||||
'@codemirror/lang-html@6.4.11':
|
||||
resolution: {integrity: sha512-9NsXp7Nwp891pQchI7gPdTwBuSuT3K65NGTHWHNJ55HjYcHLllr0rbIZNdOzas9ztc1EUVBlHou85FFZS4BNnw==}
|
||||
|
||||
'@codemirror/lang-java@6.0.2':
|
||||
resolution: {integrity: sha512-m5Nt1mQ/cznJY7tMfQTJchmrjdjQ71IDs+55d1GAa8DGaB8JXWsVCkVT284C3RTASaY43YknrK2X3hPO/J3MOQ==}
|
||||
|
||||
'@codemirror/lang-javascript@6.2.5':
|
||||
resolution: {integrity: sha512-zD4e5mS+50htS7F+TYjBPsiIFGanfVqg4HyUz6WNFikgOPf2BgKlx+TQedI1w6n/IqRBVBbBWmGFdLB/7uxO4A==}
|
||||
|
||||
'@codemirror/lang-jinja@6.0.0':
|
||||
resolution: {integrity: sha512-47MFmRcR8UAxd8DReVgj7WJN1WSAMT7OJnewwugZM4XiHWkOjgJQqvEM1NpMj9ALMPyxmlziEI1opH9IaEvmaw==}
|
||||
|
||||
'@codemirror/lang-json@6.0.2':
|
||||
resolution: {integrity: sha512-x2OtO+AvwEHrEwR0FyyPtfDUiloG3rnVTSZV1W8UteaLL8/MajQd8DpvUb2YVzC+/T18aSDv0H9mu+xw0EStoQ==}
|
||||
|
||||
'@codemirror/lang-less@6.0.2':
|
||||
resolution: {integrity: sha512-EYdQTG22V+KUUk8Qq582g7FMnCZeEHsyuOJisHRft/mQ+ZSZ2w51NupvDUHiqtsOy7It5cHLPGfHQLpMh9bqpQ==}
|
||||
|
||||
'@codemirror/lang-liquid@6.3.2':
|
||||
resolution: {integrity: sha512-6PDVU3ZnfeYyz1at1E/ttorErZvZFXXt1OPhtfe1EZJ2V2iDFa0CwPqPgG5F7NXN0yONGoBogKmFAafKTqlwIw==}
|
||||
|
||||
'@codemirror/lang-markdown@6.5.0':
|
||||
resolution: {integrity: sha512-0K40bZ35jpHya6FriukbgaleaqzBLZfOh7HuzqbMxBXkbYMJDxfF39c23xOgxFezR+3G+tR2/Mup+Xk865OMvw==}
|
||||
|
||||
'@codemirror/lang-php@6.0.2':
|
||||
resolution: {integrity: sha512-ZKy2v1n8Fc8oEXj0Th0PUMXzQJ0AIR6TaZU+PbDHExFwdu+guzOA4jmCHS1Nz4vbFezwD7LyBdDnddSJeScMCA==}
|
||||
|
||||
'@codemirror/lang-python@6.2.1':
|
||||
resolution: {integrity: sha512-IRjC8RUBhn9mGR9ywecNhB51yePWCGgvHfY1lWN/Mrp3cKuHr0isDKia+9HnvhiWNnMpbGhWrkhuWOc09exRyw==}
|
||||
|
||||
'@codemirror/lang-rust@6.0.2':
|
||||
resolution: {integrity: sha512-EZaGjCUegtiU7kSMvOfEZpaCReowEf3yNidYu7+vfuGTm9ow4mthAparY5hisJqOHmJowVH3Upu+eJlUji6qqA==}
|
||||
|
||||
'@codemirror/lang-sass@6.0.2':
|
||||
resolution: {integrity: sha512-l/bdzIABvnTo1nzdY6U+kPAC51czYQcOErfzQ9zSm9D8GmNPD0WTW8st/CJwBTPLO8jlrbyvlSEcN20dc4iL0Q==}
|
||||
|
||||
'@codemirror/lang-sql@6.10.0':
|
||||
resolution: {integrity: sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==}
|
||||
|
||||
'@codemirror/lang-vue@0.1.3':
|
||||
resolution: {integrity: sha512-QSKdtYTDRhEHCfo5zOShzxCmqKJvgGrZwDQSdbvCRJ5pRLWBS7pD/8e/tH44aVQT6FKm0t6RVNoSUWHOI5vNug==}
|
||||
|
||||
'@codemirror/lang-wast@6.0.2':
|
||||
resolution: {integrity: sha512-Imi2KTpVGm7TKuUkqyJ5NRmeFWF7aMpNiwHnLQe0x9kmrxElndyH0K6H/gXtWwY6UshMRAhpENsgfpSwsgmC6Q==}
|
||||
|
||||
'@codemirror/lang-xml@6.1.0':
|
||||
resolution: {integrity: sha512-3z0blhicHLfwi2UgkZYRPioSgVTo9PV5GP5ducFH6FaHy0IAJRg+ixj5gTR1gnT/glAIC8xv4w2VL1LoZfs+Jg==}
|
||||
|
||||
'@codemirror/lang-yaml@6.1.3':
|
||||
resolution: {integrity: sha512-AZ8DJBuXGVHybpBQhmZtgew5//4hv3tdkXnr3vDmOUMJRuB6vn/uuwtmTOTlqEaQFg3hQSVeA90NmvIQyUV6FQ==}
|
||||
|
||||
'@codemirror/language-data@6.5.2':
|
||||
resolution: {integrity: sha512-CPkWBKrNS8stYbEU5kwBwTf3JB1kghlbh4FSAwzGW2TEscdeHHH4FGysREW86Mqnj3Qn09s0/6Ea/TutmoTobg==}
|
||||
|
||||
'@codemirror/language@6.12.3':
|
||||
resolution: {integrity: sha512-QwCZW6Tt1siP37Jet9Tb02Zs81TQt6qQrZR2H+eGMcFsL1zMrk2/b9CLC7/9ieP1fjIUMgviLWMmgiHoJrj+ZA==}
|
||||
|
||||
'@codemirror/legacy-modes@6.5.2':
|
||||
resolution: {integrity: sha512-/jJbwSTazlQEDOQw2FJ8LEEKVS72pU0lx6oM54kGpL8t/NJ2Jda3CZ4pcltiKTdqYSRk3ug1B3pil1gsjA6+8Q==}
|
||||
|
||||
'@codemirror/lint@6.9.5':
|
||||
resolution: {integrity: sha512-GElsbU9G7QT9xXhpUg1zWGmftA/7jamh+7+ydKRuT0ORpWS3wOSP0yT1FOlIZa7mIJjpVPipErsyvVqB9cfTFA==}
|
||||
|
||||
'@codemirror/search@6.6.0':
|
||||
resolution: {integrity: sha512-koFuNXcDvyyotWcgOnZGmY7LZqEOXZaaxD/j6n18TCLx2/9HieZJ5H6hs1g8FiRxBD0DNfs0nXn17g872RmYdw==}
|
||||
|
||||
'@codemirror/state@6.6.0':
|
||||
resolution: {integrity: sha512-4nbvra5R5EtiCzr9BTHiTLc+MLXK2QGiAVYMyi8PkQd3SR+6ixar/Q/01Fa21TBIDOZXgeWV4WppsQolSreAPQ==}
|
||||
|
||||
'@codemirror/view@6.40.0':
|
||||
resolution: {integrity: sha512-WA0zdU7xfF10+5I3HhUUq3kqOx3KjqmtQ9lqZjfK7jtYk4G72YW9rezcSywpaUMCWOMlq+6E0pO1IWg1TNIhtg==}
|
||||
|
||||
'@csstools/css-calc@3.1.1':
|
||||
resolution: {integrity: sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==}
|
||||
engines: {node: '>=20.19.0'}
|
||||
@@ -782,6 +920,60 @@ packages:
|
||||
'@kurkle/color@0.3.4':
|
||||
resolution: {integrity: sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==}
|
||||
|
||||
'@lezer/common@1.5.1':
|
||||
resolution: {integrity: sha512-6YRVG9vBkaY7p1IVxL4s44n5nUnaNnGM2/AckNgYOnxTG2kWh1vR8BMxPseWPjRNpb5VtXnMpeYAEAADoRV1Iw==}
|
||||
|
||||
'@lezer/cpp@1.1.5':
|
||||
resolution: {integrity: sha512-DIhSXmYtJKLehrjzDFN+2cPt547ySQ41nA8yqcDf/GxMc+YM736xqltFkvADL2M0VebU5I+3+4ks2Vv+Kyq3Aw==}
|
||||
|
||||
'@lezer/css@1.3.3':
|
||||
resolution: {integrity: sha512-RzBo8r+/6QJeow7aPHIpGVIH59xTcJXp399820gZoMo9noQDRVpJLheIBUicYwKcsbOYoBRoLZlf2720dG/4Tg==}
|
||||
|
||||
'@lezer/go@1.0.1':
|
||||
resolution: {integrity: sha512-xToRsYxwsgJNHTgNdStpcvmbVuKxTapV0dM0wey1geMMRc9aggoVyKgzYp41D2/vVOx+Ii4hmE206kvxIXBVXQ==}
|
||||
|
||||
'@lezer/highlight@1.2.3':
|
||||
resolution: {integrity: sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==}
|
||||
|
||||
'@lezer/html@1.3.13':
|
||||
resolution: {integrity: sha512-oI7n6NJml729m7pjm9lvLvmXbdoMoi2f+1pwSDJkl9d68zGr7a9Btz8NdHTGQZtW2DA25ybeuv/SyDb9D5tseg==}
|
||||
|
||||
'@lezer/java@1.1.3':
|
||||
resolution: {integrity: sha512-yHquUfujwg6Yu4Fd1GNHCvidIvJwi/1Xu2DaKl/pfWIA2c1oXkVvawH3NyXhCaFx4OdlYBVX5wvz2f7Aoa/4Xw==}
|
||||
|
||||
'@lezer/javascript@1.5.4':
|
||||
resolution: {integrity: sha512-vvYx3MhWqeZtGPwDStM2dwgljd5smolYD2lR2UyFcHfxbBQebqx8yjmFmxtJ/E6nN6u1D9srOiVWm3Rb4tmcUA==}
|
||||
|
||||
'@lezer/json@1.0.3':
|
||||
resolution: {integrity: sha512-BP9KzdF9Y35PDpv04r0VeSTKDeox5vVr3efE7eBbx3r4s3oNLfunchejZhjArmeieBH+nVOpgIiBJpEAv8ilqQ==}
|
||||
|
||||
'@lezer/lr@1.4.8':
|
||||
resolution: {integrity: sha512-bPWa0Pgx69ylNlMlPvBPryqeLYQjyJjqPx+Aupm5zydLIF3NE+6MMLT8Yi23Bd9cif9VS00aUebn+6fDIGBcDA==}
|
||||
|
||||
'@lezer/markdown@1.6.3':
|
||||
resolution: {integrity: sha512-jpGm5Ps+XErS+xA4urw7ogEGkeZOahVQF21Z6oECF0sj+2liwZopd2+I8uH5I/vZsRuuze3OxBREIANLf6KKUw==}
|
||||
|
||||
'@lezer/php@1.0.5':
|
||||
resolution: {integrity: sha512-W7asp9DhM6q0W6DYNwIkLSKOvxlXRrif+UXBMxzsJUuqmhE7oVU+gS3THO4S/Puh7Xzgm858UNaFi6dxTP8dJA==}
|
||||
|
||||
'@lezer/python@1.1.18':
|
||||
resolution: {integrity: sha512-31FiUrU7z9+d/ElGQLJFXl+dKOdx0jALlP3KEOsGTex8mvj+SoE1FgItcHWK/axkxCHGUSpqIHt6JAWfWu9Rhg==}
|
||||
|
||||
'@lezer/rust@1.0.2':
|
||||
resolution: {integrity: sha512-Lz5sIPBdF2FUXcWeCu1//ojFAZqzTQNRga0aYv6dYXqJqPfMdCAI0NzajWUd4Xijj1IKJLtjoXRPMvTKWBcqKg==}
|
||||
|
||||
'@lezer/sass@1.1.0':
|
||||
resolution: {integrity: sha512-3mMGdCTUZ/84ArHOuXWQr37pnf7f+Nw9ycPUeKX+wu19b7pSMcZGLbaXwvD2APMBDOGxPmpK/O6S1v1EvLoqgQ==}
|
||||
|
||||
'@lezer/xml@1.0.6':
|
||||
resolution: {integrity: sha512-CdDwirL0OEaStFue/66ZmFSeppuL6Dwjlk8qk153mSQwiSH/Dlri4GNymrNWnUmPl2Um7QfV1FO9KFUyX3Twww==}
|
||||
|
||||
'@lezer/yaml@1.0.4':
|
||||
resolution: {integrity: sha512-2lrrHqxalACEbxIbsjhqGpSW8kWpUKuY6RHgnSAFZa6qK62wvnPxA8hGOwOoDbwHcOFs5M4o27mjGu+P7TvBmw==}
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2':
|
||||
resolution: {integrity: sha512-l0h88YhZFyKdXIFNfSWpyjStDjGHwZ/U7iobcK1cQQD8sejsONdQtTVU+1wVN1PBw40PiiHB1vA5S7VTfQiP9g==}
|
||||
|
||||
'@mcaptcha/core-glue@0.1.0-alpha-5':
|
||||
resolution: {integrity: sha512-16qWm5O5X0Y9LXULULaAks8Vf9FNlUUBcR5KDt49aWhFhG5++JzxNmCwQM9EJSHNU7y0U+FdyAWcGmjfKlkRLA==}
|
||||
|
||||
@@ -894,6 +1086,50 @@ packages:
|
||||
'@primer/octicons@19.23.1':
|
||||
resolution: {integrity: sha512-CzjGmxkmNhyst6EekrS3SJPdtzgIkUMP/LSJch65y99/kmiFXbO1a+q7zoYe3hnI9NaOM0IN+ydDIbOmd8YqcA==}
|
||||
|
||||
'@replit/codemirror-indentation-markers@6.5.3':
|
||||
resolution: {integrity: sha512-hL5Sfvw3C1vgg7GolLe/uxX5T3tmgOA3ZzqlMv47zjU1ON51pzNWiVbS22oh6crYhtVhv8b3gdXwoYp++2ilHw==}
|
||||
peerDependencies:
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
|
||||
'@replit/codemirror-lang-nix@6.0.1':
|
||||
resolution: {integrity: sha512-lvzjoYn9nfJzBD5qdm3Ut6G3+Or2wEacYIDJ49h9+19WSChVnxv4ojf+rNmQ78ncuxIt/bfbMvDLMeMP0xze6g==}
|
||||
peerDependencies:
|
||||
'@codemirror/autocomplete': ^6.0.0
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
'@lezer/common': ^1.0.0
|
||||
'@lezer/highlight': ^1.0.0
|
||||
'@lezer/lr': ^1.0.0
|
||||
|
||||
'@replit/codemirror-lang-svelte@6.0.0':
|
||||
resolution: {integrity: sha512-U2OqqgMM6jKelL0GNWbAmqlu1S078zZNoBqlJBW+retTc5M4Mha6/Y2cf4SVg6ddgloJvmcSpt4hHrVoM4ePRA==}
|
||||
peerDependencies:
|
||||
'@codemirror/autocomplete': ^6.0.0
|
||||
'@codemirror/lang-css': ^6.0.1
|
||||
'@codemirror/lang-html': ^6.2.0
|
||||
'@codemirror/lang-javascript': ^6.1.1
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
'@lezer/common': ^1.0.0
|
||||
'@lezer/highlight': ^1.0.0
|
||||
'@lezer/javascript': ^1.2.0
|
||||
'@lezer/lr': ^1.0.0
|
||||
|
||||
'@replit/codemirror-vscode-keymap@6.0.2':
|
||||
resolution: {integrity: sha512-j45qTwGxzpsv82lMD/NreGDORFKSctMDVkGRopaP+OrzSzv+pXDQuU3LnFvKpasyjVT0lf+PKG1v2DSCn/vxxg==}
|
||||
peerDependencies:
|
||||
'@codemirror/autocomplete': ^6.0.0
|
||||
'@codemirror/commands': ^6.0.0
|
||||
'@codemirror/language': ^6.0.0
|
||||
'@codemirror/lint': ^6.0.0
|
||||
'@codemirror/search': ^6.0.0
|
||||
'@codemirror/state': ^6.0.0
|
||||
'@codemirror/view': ^6.0.0
|
||||
|
||||
'@resvg/resvg-wasm@2.6.2':
|
||||
resolution: {integrity: sha512-FqALmHI8D4o6lk/LRWDnhw95z5eO+eAa6ORjVg09YRR7BkcM6oPHU9uyC0gtQG5vpFLvgpeU4+zEAz2H8APHNw==}
|
||||
engines: {node: '>= 10'}
|
||||
@@ -1840,6 +2076,9 @@ packages:
|
||||
clippie@4.1.10:
|
||||
resolution: {integrity: sha512-zUjK2fLH8/wju2lks5mH0u8wSRYCOJoHfT1KQ61+aCT5O1ouONnSrnKQ3BTKvIYLUYJarbLZo4FLHyce/SLF2g==}
|
||||
|
||||
codemirror-lang-elixir@4.0.1:
|
||||
resolution: {integrity: sha512-z6W/XB4b7TZrp9EZYBGVq93vQfvKbff+1iM8YZaVErL0dguBAeLmVRlEv1NuDZHOP1qjJ3NwyibkUkNWn7q9VQ==}
|
||||
|
||||
codemirror-spell-checker@1.1.2:
|
||||
resolution: {integrity: sha512-2Tl6n0v+GJRsC9K3MLCdLaMOmvWL0uukajNJseorZJsslaxZyZMgENocPU8R0DyoTAiKsyqiemSOZo7kjGV0LQ==}
|
||||
|
||||
@@ -1919,6 +2158,9 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
crelt@1.0.6:
|
||||
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
|
||||
|
||||
cropperjs@1.6.2:
|
||||
resolution: {integrity: sha512-nhymn9GdnV3CqiEHJVai54TULFAE3VshJTXSqSJKa8yXAKyBKDWdhHarnlIPrshJ0WMFTGuFvG02YjLXfPiuOA==}
|
||||
|
||||
@@ -2187,9 +2429,6 @@ packages:
|
||||
resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
|
||||
engines: {node: '>= 4'}
|
||||
|
||||
dompurify@3.2.7:
|
||||
resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==}
|
||||
|
||||
dompurify@3.3.3:
|
||||
resolution: {integrity: sha512-Oj6pzI2+RqBfFG+qOaOLbFXLQ90ARpcGG6UePL82bJLtdsa6CYJD7nmiU8MW9nQNOtCHV3lZ/Bzq1X0QYbBZCA==}
|
||||
|
||||
@@ -2895,6 +3134,9 @@ packages:
|
||||
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
|
||||
engines: {node: '>= 0.8.0'}
|
||||
|
||||
lezer-elixir@1.1.3:
|
||||
resolution: {integrity: sha512-Ymc58/WhxdZS9yEOlnKbF3rdeBdFcPm4OEm26KMqA1Za9vztXi7I5qwGw1KxYmm3Nv0iDHq//EQyBwSEzKG9Mg==}
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==}
|
||||
engines: {node: '>= 12.0.0'}
|
||||
@@ -3023,11 +3265,6 @@ packages:
|
||||
resolution: {integrity: sha512-UKybllYNheWac61Ia7T6fzuQNDZimFIpCg2w6hHjgV1Qu0w1TV0LlSgryUGzM0bkKQCBhy2FDhEELB73Kb0kAg==}
|
||||
engines: {node: '>=20'}
|
||||
|
||||
marked@14.0.0:
|
||||
resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==}
|
||||
engines: {node: '>= 18'}
|
||||
hasBin: true
|
||||
|
||||
marked@16.4.2:
|
||||
resolution: {integrity: sha512-TI3V8YYWvkVf3KJe1dRkpnjs68JUPyEa5vjKrp1XEEJUAOaQc+Qj+L1qWbPd0SJuAdQkFU0h73sXXqwDYxsiDA==}
|
||||
engines: {node: '>= 20'}
|
||||
@@ -3160,9 +3397,6 @@ packages:
|
||||
moment@2.30.1:
|
||||
resolution: {integrity: sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==}
|
||||
|
||||
monaco-editor@0.55.1:
|
||||
resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==}
|
||||
|
||||
moo@0.5.3:
|
||||
resolution: {integrity: sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==}
|
||||
|
||||
@@ -3655,6 +3889,9 @@ packages:
|
||||
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
style-mod@4.1.3:
|
||||
resolution: {integrity: sha512-i/n8VsZydrugj3Iuzll8+x/00GH2vnYsk1eomD8QiRrSAeW6ItbCQDtfXCeJHd0iwiNagqjQkvpvREEPtW3IoQ==}
|
||||
|
||||
style-search@0.1.0:
|
||||
resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==}
|
||||
|
||||
@@ -4004,6 +4241,9 @@ packages:
|
||||
typescript:
|
||||
optional: true
|
||||
|
||||
w3c-keyname@2.2.8:
|
||||
resolution: {integrity: sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==}
|
||||
|
||||
webidl-conversions@3.0.1:
|
||||
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
|
||||
|
||||
@@ -4185,6 +4425,254 @@ snapshots:
|
||||
'@citation-js/date': 0.5.1
|
||||
'@citation-js/name': 0.4.2
|
||||
|
||||
'@codemirror/autocomplete@6.20.1':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
|
||||
'@codemirror/commands@6.10.3':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
|
||||
'@codemirror/lang-angular@0.1.4':
|
||||
dependencies:
|
||||
'@codemirror/lang-html': 6.4.11
|
||||
'@codemirror/lang-javascript': 6.2.5
|
||||
'@codemirror/language': 6.12.3
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@codemirror/lang-cpp@6.0.3':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
'@lezer/cpp': 1.1.5
|
||||
|
||||
'@codemirror/lang-css@6.3.1':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/css': 1.3.3
|
||||
|
||||
'@codemirror/lang-go@6.0.1':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/go': 1.0.1
|
||||
|
||||
'@codemirror/lang-html@6.4.11':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/lang-css': 6.3.1
|
||||
'@codemirror/lang-javascript': 6.2.5
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/css': 1.3.3
|
||||
'@lezer/html': 1.3.13
|
||||
|
||||
'@codemirror/lang-java@6.0.2':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
'@lezer/java': 1.1.3
|
||||
|
||||
'@codemirror/lang-javascript@6.2.5':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/lint': 6.9.5
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/javascript': 1.5.4
|
||||
|
||||
'@codemirror/lang-jinja@6.0.0':
|
||||
dependencies:
|
||||
'@codemirror/lang-html': 6.4.11
|
||||
'@codemirror/language': 6.12.3
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@codemirror/lang-json@6.0.2':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
'@lezer/json': 1.0.3
|
||||
|
||||
'@codemirror/lang-less@6.0.2':
|
||||
dependencies:
|
||||
'@codemirror/lang-css': 6.3.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@codemirror/lang-liquid@6.3.2':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/lang-html': 6.4.11
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@codemirror/lang-markdown@6.5.0':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/lang-html': 6.4.11
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/markdown': 1.6.3
|
||||
|
||||
'@codemirror/lang-php@6.0.2':
|
||||
dependencies:
|
||||
'@codemirror/lang-html': 6.4.11
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/php': 1.0.5
|
||||
|
||||
'@codemirror/lang-python@6.2.1':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/python': 1.1.18
|
||||
|
||||
'@codemirror/lang-rust@6.0.2':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
'@lezer/rust': 1.0.2
|
||||
|
||||
'@codemirror/lang-sass@6.0.2':
|
||||
dependencies:
|
||||
'@codemirror/lang-css': 6.3.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/sass': 1.1.0
|
||||
|
||||
'@codemirror/lang-sql@6.10.0':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@codemirror/lang-vue@0.1.3':
|
||||
dependencies:
|
||||
'@codemirror/lang-html': 6.4.11
|
||||
'@codemirror/lang-javascript': 6.2.5
|
||||
'@codemirror/language': 6.12.3
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@codemirror/lang-wast@6.0.2':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@codemirror/lang-xml@6.1.0':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/xml': 1.0.6
|
||||
|
||||
'@codemirror/lang-yaml@6.1.3':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
'@lezer/yaml': 1.0.4
|
||||
|
||||
'@codemirror/language-data@6.5.2':
|
||||
dependencies:
|
||||
'@codemirror/lang-angular': 0.1.4
|
||||
'@codemirror/lang-cpp': 6.0.3
|
||||
'@codemirror/lang-css': 6.3.1
|
||||
'@codemirror/lang-go': 6.0.1
|
||||
'@codemirror/lang-html': 6.4.11
|
||||
'@codemirror/lang-java': 6.0.2
|
||||
'@codemirror/lang-javascript': 6.2.5
|
||||
'@codemirror/lang-jinja': 6.0.0
|
||||
'@codemirror/lang-json': 6.0.2
|
||||
'@codemirror/lang-less': 6.0.2
|
||||
'@codemirror/lang-liquid': 6.3.2
|
||||
'@codemirror/lang-markdown': 6.5.0
|
||||
'@codemirror/lang-php': 6.0.2
|
||||
'@codemirror/lang-python': 6.2.1
|
||||
'@codemirror/lang-rust': 6.0.2
|
||||
'@codemirror/lang-sass': 6.0.2
|
||||
'@codemirror/lang-sql': 6.10.0
|
||||
'@codemirror/lang-vue': 0.1.3
|
||||
'@codemirror/lang-wast': 6.0.2
|
||||
'@codemirror/lang-xml': 6.1.0
|
||||
'@codemirror/lang-yaml': 6.1.3
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/legacy-modes': 6.5.2
|
||||
|
||||
'@codemirror/language@6.12.3':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
style-mod: 4.1.3
|
||||
|
||||
'@codemirror/legacy-modes@6.5.2':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
|
||||
'@codemirror/lint@6.9.5':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/search@6.6.0':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
crelt: 1.0.6
|
||||
|
||||
'@codemirror/state@6.6.0':
|
||||
dependencies:
|
||||
'@marijn/find-cluster-break': 1.0.2
|
||||
|
||||
'@codemirror/view@6.40.0':
|
||||
dependencies:
|
||||
'@codemirror/state': 6.6.0
|
||||
crelt: 1.0.6
|
||||
style-mod: 4.1.3
|
||||
w3c-keyname: 2.2.8
|
||||
|
||||
'@csstools/css-calc@3.1.1(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
|
||||
dependencies:
|
||||
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||
@@ -4440,6 +4928,101 @@ snapshots:
|
||||
|
||||
'@kurkle/color@0.3.4': {}
|
||||
|
||||
'@lezer/common@1.5.1': {}
|
||||
|
||||
'@lezer/cpp@1.1.5':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/css@1.3.3':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/go@1.0.1':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/highlight@1.2.3':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
|
||||
'@lezer/html@1.3.13':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/java@1.1.3':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/javascript@1.5.4':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/json@1.0.3':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/lr@1.4.8':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
|
||||
'@lezer/markdown@1.6.3':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
|
||||
'@lezer/php@1.0.5':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/python@1.1.18':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/rust@1.0.2':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/sass@1.1.0':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/xml@1.0.6':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@lezer/yaml@1.0.4':
|
||||
dependencies:
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@marijn/find-cluster-break@1.0.2': {}
|
||||
|
||||
'@mcaptcha/core-glue@0.1.0-alpha-5': {}
|
||||
|
||||
'@mcaptcha/vanilla-glue@0.1.0-alpha-3':
|
||||
@@ -4548,6 +5131,46 @@ snapshots:
|
||||
dependencies:
|
||||
object-assign: 4.1.1
|
||||
|
||||
'@replit/codemirror-indentation-markers@6.5.3(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)':
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
|
||||
'@replit/codemirror-lang-nix@6.0.1(@codemirror/autocomplete@6.20.1)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/lr@1.4.8)':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@replit/codemirror-lang-svelte@6.0.0(@codemirror/autocomplete@6.20.1)(@codemirror/lang-css@6.3.1)(@codemirror/lang-html@6.4.11)(@codemirror/lang-javascript@6.2.5)(@codemirror/language@6.12.3)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)(@lezer/common@1.5.1)(@lezer/highlight@1.2.3)(@lezer/javascript@1.5.4)(@lezer/lr@1.4.8)':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/lang-css': 6.3.1
|
||||
'@codemirror/lang-html': 6.4.11
|
||||
'@codemirror/lang-javascript': 6.2.5
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
'@lezer/common': 1.5.1
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/javascript': 1.5.4
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
'@replit/codemirror-vscode-keymap@6.0.2(@codemirror/autocomplete@6.20.1)(@codemirror/commands@6.10.3)(@codemirror/language@6.12.3)(@codemirror/lint@6.9.5)(@codemirror/search@6.6.0)(@codemirror/state@6.6.0)(@codemirror/view@6.40.0)':
|
||||
dependencies:
|
||||
'@codemirror/autocomplete': 6.20.1
|
||||
'@codemirror/commands': 6.10.3
|
||||
'@codemirror/language': 6.12.3
|
||||
'@codemirror/lint': 6.9.5
|
||||
'@codemirror/search': 6.6.0
|
||||
'@codemirror/state': 6.6.0
|
||||
'@codemirror/view': 6.40.0
|
||||
|
||||
'@resvg/resvg-wasm@2.6.2': {}
|
||||
|
||||
'@rolldown/binding-android-arm64@1.0.0-rc.12':
|
||||
@@ -5407,6 +6030,11 @@ snapshots:
|
||||
|
||||
clippie@4.1.10: {}
|
||||
|
||||
codemirror-lang-elixir@4.0.1:
|
||||
dependencies:
|
||||
'@codemirror/language': 6.12.3
|
||||
lezer-elixir: 1.1.3
|
||||
|
||||
codemirror-spell-checker@1.1.2:
|
||||
dependencies:
|
||||
typo-js: 1.3.1
|
||||
@@ -5469,6 +6097,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
crelt@1.0.6: {}
|
||||
|
||||
cropperjs@1.6.2: {}
|
||||
|
||||
cross-spawn@7.0.6:
|
||||
@@ -5750,10 +6380,6 @@ snapshots:
|
||||
dependencies:
|
||||
domelementtype: 2.3.0
|
||||
|
||||
dompurify@3.2.7:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
|
||||
dompurify@3.3.3:
|
||||
optionalDependencies:
|
||||
'@types/trusted-types': 2.0.7
|
||||
@@ -6516,6 +7142,11 @@ snapshots:
|
||||
prelude-ls: 1.2.1
|
||||
type-check: 0.4.0
|
||||
|
||||
lezer-elixir@1.1.3:
|
||||
dependencies:
|
||||
'@lezer/highlight': 1.2.3
|
||||
'@lezer/lr': 1.4.8
|
||||
|
||||
lightningcss-android-arm64@1.32.0:
|
||||
optional: true
|
||||
|
||||
@@ -6637,8 +7268,6 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
marked@14.0.0: {}
|
||||
|
||||
marked@16.4.2: {}
|
||||
|
||||
marked@4.3.0: {}
|
||||
@@ -6882,11 +7511,6 @@ snapshots:
|
||||
|
||||
moment@2.30.1: {}
|
||||
|
||||
monaco-editor@0.55.1:
|
||||
dependencies:
|
||||
dompurify: 3.2.7
|
||||
marked: 14.0.0
|
||||
|
||||
moo@0.5.3: {}
|
||||
|
||||
ms@2.1.3: {}
|
||||
@@ -7360,6 +7984,8 @@ snapshots:
|
||||
|
||||
strip-json-comments@3.1.1: {}
|
||||
|
||||
style-mod@4.1.3: {}
|
||||
|
||||
style-search@0.1.0: {}
|
||||
|
||||
stylelint-config-recommended@18.0.0(stylelint@17.6.0(typescript@5.9.3)):
|
||||
@@ -7749,6 +8375,8 @@ snapshots:
|
||||
optionalDependencies:
|
||||
typescript: 5.9.3
|
||||
|
||||
w3c-keyname@2.2.8: {}
|
||||
|
||||
webidl-conversions@3.0.1: {}
|
||||
|
||||
whatwg-mimetype@3.0.0: {}
|
||||
|
||||
@@ -319,7 +319,12 @@ func EditFile(ctx *context.Context) {
|
||||
}
|
||||
}
|
||||
|
||||
ctx.Data["CodeEditorConfig"] = getCodeEditorConfig(ctx, ctx.Repo.TreePath)
|
||||
editorConfig := getCodeEditorConfigByEditorconfig(ctx, ctx.Repo.TreePath)
|
||||
editorConfig.Autofocus = !isNewFile
|
||||
if isNewFile {
|
||||
editorConfig.Filename = ""
|
||||
}
|
||||
ctx.Data["CodeEditorConfig"] = editorConfig
|
||||
ctx.HTML(http.StatusOK, tplEditFile)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ func NewDiffPatch(ctx *context.Context) {
|
||||
}
|
||||
|
||||
ctx.Data["PageIsPatch"] = true
|
||||
ctx.Data["CodeEditorConfig"] = CodeEditorConfig{} // not really editing a file, so no need to fill in the config
|
||||
ctx.Data["CodeEditorConfig"] = CodeEditorConfig{Filename: "diff.patch"}
|
||||
ctx.HTML(http.StatusOK, tplPatchFile)
|
||||
}
|
||||
|
||||
|
||||
@@ -65,27 +65,33 @@ func getClosestParentWithFiles(gitRepo *git.Repository, branchName, originTreePa
|
||||
return f(originTreePath, commit)
|
||||
}
|
||||
|
||||
// CodeEditorConfig is also used by frontend, defined in "codeeditor.ts"
|
||||
// CodeEditorConfig is also used by frontend, defined in "codeeditor" module
|
||||
type CodeEditorConfig struct {
|
||||
PreviewableExtensions []string `json:"previewable_extensions"`
|
||||
LineWrapExtensions []string `json:"line_wrap_extensions"`
|
||||
LineWrapOn bool `json:"line_wrap_on"`
|
||||
Filename string `json:"filename"` // the base name, not full path
|
||||
Autofocus bool `json:"autofocus"`
|
||||
PreviewableExtensions []string `json:"previewableExtensions,omitempty"`
|
||||
LineWrapExtensions []string `json:"lineWrapExtensions,omitempty"`
|
||||
LineWrap bool `json:"lineWrap"`
|
||||
Previewable bool `json:"previewable,omitempty"`
|
||||
|
||||
IndentStyle string `json:"indent_style"`
|
||||
IndentSize int `json:"indent_size"`
|
||||
TabWidth int `json:"tab_width"`
|
||||
TrimTrailingWhitespace *bool `json:"trim_trailing_whitespace,omitempty"`
|
||||
// the following can be read from .editorconfig if exists, or use default value
|
||||
IndentStyle string `json:"indentStyle"` // in most cases, keep it empty by default, detected by the source code
|
||||
IndentSize int `json:"indentSize"`
|
||||
TabWidth int `json:"tabWidth"`
|
||||
TrimTrailingWhitespace *bool `json:"trimTrailingWhitespace,omitempty"`
|
||||
}
|
||||
|
||||
func getCodeEditorConfig(ctx *context_service.Context, treePath string) (ret CodeEditorConfig) {
|
||||
func getCodeEditorConfigByEditorconfig(ctx *context_service.Context, treePath string) CodeEditorConfig {
|
||||
ret := CodeEditorConfig{Filename: path.Base(treePath)}
|
||||
ret.PreviewableExtensions = markup.PreviewableExtensions()
|
||||
ret.LineWrapExtensions = setting.Repository.Editor.LineWrapExtensions
|
||||
ret.LineWrapOn = util.SliceContainsString(ret.LineWrapExtensions, path.Ext(treePath), true)
|
||||
ret.LineWrap = util.SliceContainsString(ret.LineWrapExtensions, path.Ext(treePath), true)
|
||||
ret.Previewable = util.SliceContainsString(ret.PreviewableExtensions, path.Ext(treePath), true)
|
||||
ec, _, err := ctx.Repo.GetEditorconfig()
|
||||
if err == nil {
|
||||
def, err := ec.GetDefinitionForFilename(treePath)
|
||||
if err == nil {
|
||||
ret.IndentStyle = def.IndentStyle
|
||||
ret.IndentStyle = util.IfZero(def.IndentStyle, ret.IndentStyle)
|
||||
ret.IndentSize, _ = strconv.Atoi(def.IndentSize)
|
||||
ret.TabWidth = def.TabWidth
|
||||
ret.TrimTrailingWhitespace = def.TrimTrailingWhitespace
|
||||
|
||||
@@ -42,7 +42,7 @@ func GitHooksEdit(ctx *context.Context) {
|
||||
return
|
||||
}
|
||||
ctx.Data["Hook"] = hook
|
||||
ctx.Data["CodeEditorConfig"] = repo.CodeEditorConfig{} // not really editing a repo file, so no editor config
|
||||
ctx.Data["CodeEditorConfig"] = repo.CodeEditorConfig{Filename: name + ".sh", IndentStyle: "tab", TabWidth: 4}
|
||||
ctx.HTML(http.StatusOK, tplGithookEdit)
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
{{- end}}
|
||||
</h3>
|
||||
<div class="field">
|
||||
<input name="commit_summary" maxlength="100" placeholder="{{if .PageIsDelete}}{{ctx.Locale.Tr "repo.editor.delete" .TreePath}}{{else if .PageIsUpload}}{{ctx.Locale.Tr "repo.editor.upload_files_to_dir" .TreePath}}{{else if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.add_tmpl"}}{{else if .PageIsPatch}}{{ctx.Locale.Tr "repo.editor.patch"}}{{else}}{{ctx.Locale.Tr "repo.editor.update" .TreePath}}{{end}}" value="{{.commit_summary}}" autofocus>
|
||||
<input name="commit_summary" maxlength="100" placeholder="{{if .PageIsDelete}}{{ctx.Locale.Tr "repo.editor.delete" .TreePath}}{{else if .PageIsUpload}}{{ctx.Locale.Tr "repo.editor.upload_files_to_dir" .TreePath}}{{else if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.add_tmpl"}}{{else if .PageIsPatch}}{{ctx.Locale.Tr "repo.editor.patch"}}{{else}}{{ctx.Locale.Tr "repo.editor.update" .TreePath}}{{end}}" value="{{.commit_summary}}">
|
||||
</div>
|
||||
<div class="field">
|
||||
<textarea name="commit_message" placeholder="{{ctx.Locale.Tr "repo.editor.commit_message_desc"}}" rows="5">{{.commit_message}}</textarea>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
{{range $i, $v := .TreeNames}}
|
||||
<div class="breadcrumb-divider">/</div>
|
||||
{{if eq $i $l}}
|
||||
<input id="file-name" maxlength="255" value="{{$v}}" placeholder="{{ctx.Locale.Tr (Iif $.PageIsUpload "repo.editor.add_subdir" "repo.editor.name_your_file")}}" data-code-editor-config="{{JsonUtils.EncodeToString $.CodeEditorConfig}}" {{Iif $.PageIsUpload "" "required"}} autofocus>
|
||||
<input id="file-name" maxlength="255" value="{{$v}}" placeholder="{{ctx.Locale.Tr (Iif $.PageIsUpload "repo.editor.add_subdir" "repo.editor.name_your_file")}}" {{Iif $.PageIsUpload "" "required"}}>
|
||||
<span data-tooltip-content="{{ctx.Locale.Tr "repo.editor.filename_help"}}">{{svg "octicon-info"}}</span>
|
||||
{{else}}
|
||||
<span class="section"><a href="{{$.BranchLink}}/{{index $.TreePaths $i | PathEscapeSegments}}">{{$v}}</a></span>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<div class="flex-text-block tw-justify-between tw-flex-wrap">
|
||||
<div class="ui compact small menu small-menu-items repo-editor-menu tw-self-start">
|
||||
<a class="active item" data-tab="write">{{svg "octicon-code"}} {{if .IsNewFile}}{{ctx.Locale.Tr "repo.editor.new_file"}}{{else}}{{ctx.Locale.Tr "repo.editor.edit_file"}}{{end}}</a>
|
||||
<a class="item" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
|
||||
<a class="item{{if not .CodeEditorConfig.Previewable}} tw-hidden{{end}}" data-tab="preview" data-preview-url="{{.Repository.Link}}/markup" data-preview-context-ref="{{.RepoLink}}/src/{{.RefTypeNameSubURL}}">{{svg "octicon-eye"}} {{ctx.Locale.Tr "preview"}}</a>
|
||||
{{if not .IsNewFile}}
|
||||
<a class="item" data-tab="diff" hx-params="context,content" hx-vals='{"context":"{{.BranchLink}}"}' hx-include="#edit_area" hx-swap="innerHTML" hx-target=".tab[data-tab='diff']" hx-indicator=".tab[data-tab='diff']" hx-post="{{.RepoLink}}/_preview/{{.BranchName | PathEscapeSegments}}/{{.TreePath | PathEscapeSegments}}">{{svg "octicon-diff"}} {{ctx.Locale.Tr "repo.editor.preview_changes"}}</a>
|
||||
{{end}}
|
||||
@@ -32,8 +32,8 @@
|
||||
<div class="ui bottom attached segment tw-p-0">
|
||||
<div class="ui active tab tw-rounded-b" data-tab="write">
|
||||
<textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-{{.TreePath}}"
|
||||
data-previewable-extensions="{{StringUtils.Join $.CodeEditorConfig.PreviewableExtensions ","}}"
|
||||
data-line-wrap-extensions="{{StringUtils.Join $.CodeEditorConfig.LineWrapExtensions ","}}">{{.FileContent}}</textarea>
|
||||
data-code-editor-config="{{JsonUtils.EncodeToString $.CodeEditorConfig}}"
|
||||
placeholder="{{ctx.Locale.Tr "editor.code_editor.placeholder"}}">{{.FileContent}}</textarea>
|
||||
<div class="editor-loading is-loading"></div>
|
||||
</div>
|
||||
<div class="ui tab tw-px-4 tw-py-3" data-tab="preview">
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
{{$indentStyle := $.CodeEditorConfig.IndentStyle}}
|
||||
{{$indentSize := or $.CodeEditorConfig.IndentSize 4}}
|
||||
{{$lineWrapOn := $.CodeEditorConfig.LineWrapOn}}
|
||||
{{$lineWrap := $.CodeEditorConfig.LineWrap}}
|
||||
<div class="flex-text-block code-editor-options">
|
||||
<button type="button" class="js-code-find ui compact mini icon button" aria-label="{{ctx.Locale.Tr "editor.code_editor.find"}}">{{svg "octicon-search"}}</button>
|
||||
<button type="button" class="js-code-command-palette ui compact mini icon button" aria-label="{{ctx.Locale.Tr "editor.code_editor.command_palette"}}">{{svg "octicon-command-palette"}}</button>
|
||||
<div class="native-select">
|
||||
<select class="js-indent-style-select" aria-label="{{ctx.Locale.Tr "text_indent_style"}}">
|
||||
<optgroup label="{{ctx.Locale.Tr "text_indent_style"}}">
|
||||
@@ -22,8 +24,8 @@
|
||||
<div class="native-select">
|
||||
<select class="js-line-wrap-select" aria-label="{{ctx.Locale.Tr "text_line_wrap_mode"}}">
|
||||
<optgroup label="{{ctx.Locale.Tr "text_line_wrap_mode"}}">
|
||||
<option{{if $lineWrapOn}} selected{{end}} value="on">{{ctx.Locale.Tr "text_line_wrap"}}</option>
|
||||
<option{{if not $lineWrapOn}} selected{{end}} value="off">{{ctx.Locale.Tr "text_line_nowrap"}}</option>
|
||||
<option{{if $lineWrap}} selected{{end}} value="on">{{ctx.Locale.Tr "text_line_wrap"}}</option>
|
||||
<option{{if not $lineWrap}} selected{{end}} value="off">{{ctx.Locale.Tr "text_line_nowrap"}}</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
@@ -31,7 +31,9 @@
|
||||
<div class="ui bottom attached segment tw-p-0">
|
||||
<div class="ui active tab tw-rounded-b" data-tab="write">
|
||||
<textarea id="edit_area" name="content" class="tw-hidden" data-id="repo-{{.Repository.Name}}-patch"
|
||||
data-context="{{.RepoLink}}">{{.FileContent}}</textarea>
|
||||
data-code-editor-config="{{JsonUtils.EncodeToString $.CodeEditorConfig}}"
|
||||
data-context="{{.RepoLink}}"
|
||||
placeholder="{{ctx.Locale.Tr "editor.code_editor.placeholder"}}">{{.FileContent}}</textarea>
|
||||
<div class="editor-loading is-loading"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<div class="repo-setting-content">
|
||||
<form class="ui form" action="{{.Link}}" method="post">
|
||||
<h4 class="ui top attached header flex-text-block tw-justify-between tw-flex-wrap">
|
||||
{{ctx.Locale.Tr "repo.settings.githooks"}}
|
||||
{{.Hook.Name}}
|
||||
<div class="tw-font-normal tw-font-sans tw-text-base">
|
||||
{{template "repo/editor/options" dict "CodeEditorConfig" $.CodeEditorConfig}}
|
||||
</div>
|
||||
@@ -10,13 +10,10 @@
|
||||
<div class="ui attached segment">
|
||||
<p>{{ctx.Locale.Tr "repo.settings.githook_edit_desc"}}</p>
|
||||
{{with .Hook}}
|
||||
<div class="inline field">
|
||||
<label>{{ctx.Locale.Tr "repo.settings.githook_name"}}</label>
|
||||
<span class="hook-filename">{{.Name}}</span>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label for="content">{{ctx.Locale.Tr "repo.settings.githook_content"}}</label>
|
||||
<textarea id="content" name="content" class="tw-hidden">{{if .IsActive}}{{.Content}}{{else}}{{.Sample}}{{end}}</textarea>
|
||||
<textarea id="content" name="content" class="tw-hidden"
|
||||
data-code-editor-config="{{JsonUtils.EncodeToString $.CodeEditorConfig}}"
|
||||
placeholder="{{ctx.Locale.Tr "editor.code_editor.placeholder"}}">{{if .IsActive}}{{.Content}}{{else}}{{.Sample}}{{end}}</textarea>
|
||||
<div class="editor-loading is-loading"></div>
|
||||
</div>
|
||||
<div class="inline field">
|
||||
|
||||
21
tests/e2e/codeeditor.test.ts
Normal file
21
tests/e2e/codeeditor.test.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import {env} from 'node:process';
|
||||
import {expect, test} from '@playwright/test';
|
||||
import {login, apiCreateRepo, apiDeleteRepo} from './utils.ts';
|
||||
|
||||
test('codeeditor textarea updates correctly', async ({page, request}) => {
|
||||
const repoName = `e2e-codeeditor-${Date.now()}`;
|
||||
await apiCreateRepo(request, {name: repoName});
|
||||
try {
|
||||
await login(page);
|
||||
await page.goto(`/${env.GITEA_TEST_E2E_USER}/${repoName}/_new/main`);
|
||||
await page.getByPlaceholder('Name your file…').fill('test.js');
|
||||
await expect(page.locator('.editor-loading')).toBeHidden();
|
||||
const editor = page.locator('.cm-content[role="textbox"]');
|
||||
await expect(editor).toBeVisible();
|
||||
await editor.click();
|
||||
await page.keyboard.type('const hello = "world";');
|
||||
await expect(page.locator('textarea[name="content"]')).toHaveValue('const hello = "world";');
|
||||
} finally {
|
||||
await apiDeleteRepo(request, env.GITEA_TEST_E2E_USER, repoName);
|
||||
}
|
||||
});
|
||||
@@ -24,7 +24,7 @@ async function apiRetry(fn: () => Promise<{ok: () => boolean; status: () => numb
|
||||
if (response.ok()) return;
|
||||
if ([500, 502, 503].includes(response.status()) && attempt < maxAttempts - 1) {
|
||||
const jitter = Math.random() * 500;
|
||||
await new Promise((resolve) => globalThis.setTimeout(resolve, 1000 * (attempt + 1) + jitter));
|
||||
await new Promise((resolve) => setTimeout(resolve, 1000 * (attempt + 1) + jitter));
|
||||
continue;
|
||||
}
|
||||
throw new Error(`${label} failed: ${response.status()} ${await response.text()}`);
|
||||
|
||||
@@ -12,12 +12,13 @@
|
||||
--font-weight-medium: 500;
|
||||
--font-weight-semibold: 600;
|
||||
--font-weight-bold: 700;
|
||||
/* line-height: use the default value as "modules/normalize.css" */
|
||||
--line-height-default: normal;
|
||||
--line-height-default: normal; /* line-height: use the default value as "modules/normalize.css" */
|
||||
--line-height-code: 20px; /* line-height for code, also used by the editor */
|
||||
/* images */
|
||||
--checkbox-mask-checked: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="12" height="9" viewBox="0 0 12 9"><path fill-rule="evenodd" d="M11.78.22a.75.75 0 0 1 0 1.061L4.52 8.541a.75.75 0 0 1-1.062 0L.202 5.285a.75.75 0 0 1 1.061-1.061l2.725 2.723L10.718.22a.75.75 0 0 1 1.062 0"/></svg>');
|
||||
--checkbox-mask-indeterminate: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="10" height="2" viewBox="0 0 10 2"><path fill-rule="evenodd" d="M0 1a1 1 0 0 1 1-1h8a1 1 0 1 1 0 2H1a1 1 0 0 1-1-1" clip-rule="evenodd"/></svg>');
|
||||
--octicon-chevron-right: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M6.22 3.22a.75.75 0 0 1 1.06 0l4.25 4.25a.75.75 0 0 1 0 1.06l-4.25 4.25a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L9.94 8 6.22 4.28a.75.75 0 0 1 0-1.06Z"></path></svg>');
|
||||
--octicon-x: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.75.75 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.75.75 0 0 1-.734-.215L8 9.06l-3.22 3.22a.75.75 0 0 1-1.042-.018.75.75 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06"/></svg>');
|
||||
--select-arrows: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16"><path d="m4.074 9.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.043 9H4.251a.25.25 0 0 0-.177.427m0-1.957L7.47 4.073a.25.25 0 0 1 .354 0L11.22 7.47a.25.25 0 0 1-.177.426H4.251a.25.25 0 0 1-.177-.426"/></svg>');
|
||||
/* other variables */
|
||||
--border-radius: 4px;
|
||||
@@ -43,6 +44,7 @@
|
||||
--gap-block: 0.5rem; /* gap for element blocks, for example: spaces between buttons, menu image & title, header icon & title etc */
|
||||
|
||||
--background-view-image: repeating-conic-gradient(var(--color-transparency-grid-dark) 0 25%, var(--color-transparency-grid-light) 0 50%) 0 0 / 18px 18px;
|
||||
--box-shadow-kbd: inset 0 -1px 0 var(--color-secondary);
|
||||
}
|
||||
|
||||
@media (min-width: 768px) and (max-width: 1200px) {
|
||||
@@ -99,7 +101,6 @@ samp {
|
||||
|
||||
pre,
|
||||
code,
|
||||
kbd,
|
||||
samp,
|
||||
.tw-font-mono {
|
||||
font-size: 0.95em; /* compensate for monospace fonts being usually slightly larger */
|
||||
@@ -322,6 +323,8 @@ a.label,
|
||||
.native-select > select {
|
||||
appearance: none; /* hide default triangle */
|
||||
padding: 6px 26px 6px 10px;
|
||||
border: 1px solid var(--color-light-border) !important;
|
||||
color: var(--color-text);
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
@@ -672,7 +675,7 @@ overflow-menu .ui.label {
|
||||
|
||||
.lines-num span::after {
|
||||
content: attr(data-line-number);
|
||||
line-height: 20px !important;
|
||||
line-height: var(--line-height-code) !important;
|
||||
padding: 0 10px;
|
||||
display: block;
|
||||
}
|
||||
@@ -686,7 +689,7 @@ overflow-menu .ui.label {
|
||||
.lines-code {
|
||||
font-size: 12px;
|
||||
font-family: var(--fonts-monospace);
|
||||
line-height: 20px;
|
||||
line-height: var(--line-height-code);
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
vertical-align: top;
|
||||
@@ -743,7 +746,7 @@ overflow-menu .ui.label {
|
||||
width: min(26vw, 300px);
|
||||
display: block;
|
||||
padding: 0 0 0 6px;
|
||||
line-height: 20px;
|
||||
line-height: var(--line-height-code);
|
||||
box-sizing: content-box;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
/* LineTableTD */
|
||||
.chroma .lntd {
|
||||
vertical-align: top;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
/* LineTable */
|
||||
.chroma .lntable {
|
||||
border-spacing: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
width: auto;
|
||||
overflow: auto;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* LineHighlight */
|
||||
.chroma .hl {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* LineNumbersTable */
|
||||
.chroma .lnt {
|
||||
margin-right: 0.4em;
|
||||
padding: 0 0.4em;
|
||||
}
|
||||
|
||||
/* LineNumbers */
|
||||
.chroma .ln {
|
||||
margin-right: 0.4em;
|
||||
padding: 0 0.4em;
|
||||
}
|
||||
|
||||
/* GenericStrong */
|
||||
.chroma .gs {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
/* GenericUnderline */
|
||||
.chroma .gl {
|
||||
text-decoration: underline;
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
/* https://github.com/alecthomas/chroma/blob/6428fb4e65f3c1493491571c8a6a8f1add1da822/types.go#L208 */
|
||||
.chroma .bp { color: #fabd2f; } /* NameBuiltinPseudo */
|
||||
.chroma .c { color: #777e94; } /* Comment */
|
||||
.chroma .c1 { color: #777e94; } /* CommentSingle */
|
||||
.chroma .ch { color: #777e94; } /* CommentHashbang */
|
||||
.chroma .cm { color: #777e94; } /* CommentMultiline */
|
||||
.chroma .cp { color: #8ec07c; } /* CommentPreproc */
|
||||
.chroma .cpf { color: #649bc4; } /* CommentPreprocFile */
|
||||
.chroma .cs { color: #9075cd; } /* CommentSpecial */
|
||||
.chroma .dl { color: #649bc4; } /* LiteralStringDelimiter */
|
||||
.chroma .fm {} /* NameFunctionMagic */
|
||||
.chroma .g {} /* Generic */
|
||||
.chroma .gd { color: #ffffff; background-color: #5f3737; } /* GenericDeleted */
|
||||
.chroma .ge { color: #ddee30; } /* GenericEmph */
|
||||
.chroma .gh { color: #ffaa10; } /* GenericHeading */
|
||||
.chroma .gi { color: #ffffff; background-color: #3a523a; } /* GenericInserted */
|
||||
.chroma .gl {} /* GenericUnderline */
|
||||
.chroma .go { color: #777e94; } /* GenericOutput */
|
||||
.chroma .gp { color: #ebdbb2; } /* GenericPrompt */
|
||||
.chroma .gr { color: #ff4433; } /* GenericError */
|
||||
.chroma .gs { color: #ebdbb2; } /* GenericStrong */
|
||||
.chroma .gt { color: #ff7540; } /* GenericTraceback */
|
||||
.chroma .gu { color: #b8bb26; } /* GenericSubheading */
|
||||
.chroma .il { color: #649bc4; } /* LiteralNumberIntegerLong */
|
||||
.chroma .k { color: #ff7540; } /* Keyword */
|
||||
.chroma .kc { color: #649bc4; } /* KeywordConstant */
|
||||
.chroma .kd { color: #ff7540; } /* KeywordDeclaration */
|
||||
.chroma .kn { color: #ffaa10; } /* KeywordNamespace */
|
||||
.chroma .kp { color: #5f8700; } /* KeywordPseudo */
|
||||
.chroma .kr { color: #ff7540; } /* KeywordReserved */
|
||||
.chroma .kt { color: #ff7b72; } /* KeywordType */
|
||||
.chroma .l {} /* Literal */
|
||||
.chroma .ld {} /* LiteralDate */
|
||||
.chroma .m { color: #649bc4; } /* LiteralNumber */
|
||||
.chroma .mb { color: #649bc4; } /* LiteralNumberBin */
|
||||
.chroma .mf { color: #649bc4; } /* LiteralNumberFloat */
|
||||
.chroma .mh { color: #649bc4; } /* LiteralNumberHex */
|
||||
.chroma .mi { color: #649bc4; } /* LiteralNumberInteger */
|
||||
.chroma .mo { color: #649bc4; } /* LiteralNumberOct */
|
||||
.chroma .n { color: #c9d1d9; } /* Name */
|
||||
.chroma .na { color: #fabd2f; } /* NameAttribute */
|
||||
.chroma .nb { color: #fabd2f; } /* NameBuiltin */
|
||||
.chroma .nc { color: #ffaa10; } /* NameClass */
|
||||
.chroma .nd { color: #8ec07c; } /* NameDecorator */
|
||||
.chroma .ne { color: #ff7540; } /* NameException */
|
||||
.chroma .nf { color: #fabd2f; } /* NameFunction */
|
||||
.chroma .ni { color: #fabd2f; } /* NameEntity */
|
||||
.chroma .nl { color: #ff7540; } /* NameLabel */
|
||||
.chroma .nn { color: #c9d1d9; } /* NameNamespace */
|
||||
.chroma .no { color: #649bc4; } /* NameConstant */
|
||||
.chroma .nt { color: #ff7540; } /* NameTag */
|
||||
.chroma .nv { color: #ebdbb2; } /* NameVariable */
|
||||
.chroma .nx { color: #b6bac5; } /* NameOther */
|
||||
.chroma .o { color: #ff7540; } /* Operator */
|
||||
.chroma .ow { color: #5f8700; } /* OperatorWord */
|
||||
.chroma .p { color: #d2d4db; } /* Punctuation */
|
||||
.chroma .py {} /* NameProperty */
|
||||
.chroma .s { color: #b8bb26; } /* LiteralString */
|
||||
.chroma .s1 { color: #b8bb26; } /* LiteralStringSingle */
|
||||
.chroma .s2 { color: #b8bb26; } /* LiteralStringDouble */
|
||||
.chroma .sa { color: #ffaa10; } /* LiteralStringAffix */
|
||||
.chroma .sb { color: #b8bb26; } /* LiteralStringBacktick */
|
||||
.chroma .sc { color: #ffaa10; } /* LiteralStringChar */
|
||||
.chroma .sd { color: #b8bb26; } /* LiteralStringDoc */
|
||||
.chroma .se { color: #ff8540; } /* LiteralStringEscape */
|
||||
.chroma .sh { color: #b8bb26; } /* LiteralStringHeredoc */
|
||||
.chroma .si { color: #ffaa10; } /* LiteralStringInterpol */
|
||||
.chroma .sr { color: #9075cd; } /* LiteralStringRegex */
|
||||
.chroma .ss { color: #ff8540; } /* LiteralStringSymbol */
|
||||
.chroma .sx { color: #ffaa10; } /* LiteralStringOther */
|
||||
.chroma .vc { color: #649bee; } /* NameVariableClass */
|
||||
.chroma .vg { color: #649bee; } /* NameVariableGlobal */
|
||||
.chroma .vi { color: #649bee; } /* NameVariableInstance */
|
||||
.chroma .vm {} /* NameVariableMagic */
|
||||
.chroma .w { color: #7f8699; } /* TextWhitespace */
|
||||
.chroma .err {/* not styled because Chroma uses it on too many things like JSX */} /* Error */
|
||||
@@ -1,76 +0,0 @@
|
||||
/* https://github.com/alecthomas/chroma/blob/6428fb4e65f3c1493491571c8a6a8f1add1da822/types.go#L208 */
|
||||
.chroma .bp { color: #999999; } /* NameBuiltinPseudo */
|
||||
.chroma .c { color: #6a737d; } /* Comment */
|
||||
.chroma .c1 { color: #6a737d; } /* CommentSingle */
|
||||
.chroma .ch { color: #6a737d; } /* CommentHashbang */
|
||||
.chroma .cm { color: #999988; } /* CommentMultiline */
|
||||
.chroma .cp { color: #109295; } /* CommentPreproc */
|
||||
.chroma .cpf { color: #4c4dbc; } /* CommentPreprocFile */
|
||||
.chroma .cs { color: #999999; } /* CommentSpecial */
|
||||
.chroma .dl { color: #106303; } /* LiteralStringDelimiter */
|
||||
.chroma .fm {} /* NameFunctionMagic */
|
||||
.chroma .g {} /* Generic */
|
||||
.chroma .gd { color: #000000; background-color: #ffdddd; } /* GenericDeleted */
|
||||
.chroma .ge { color: #000000; } /* GenericEmph */
|
||||
.chroma .gh { color: #999999; } /* GenericHeading */
|
||||
.chroma .gi { color: #000000; background-color: #ddffdd; } /* GenericInserted */
|
||||
.chroma .gl {} /* GenericUnderline */
|
||||
.chroma .go { color: #888888; } /* GenericOutput */
|
||||
.chroma .gp { color: #555555; } /* GenericPrompt */
|
||||
.chroma .gr { color: #aa0000; } /* GenericError */
|
||||
.chroma .gs {} /* GenericStrong */
|
||||
.chroma .gt { color: #aa0000; } /* GenericTraceback */
|
||||
.chroma .gu { color: #aaaaaa; } /* GenericSubheading */
|
||||
.chroma .il { color: #009999; } /* LiteralNumberIntegerLong */
|
||||
.chroma .k { color: #d73a49; } /* Keyword */
|
||||
.chroma .kc { color: #d73a49; } /* KeywordConstant */
|
||||
.chroma .kd { color: #d73a49; } /* KeywordDeclaration */
|
||||
.chroma .kn { color: #d73a49; } /* KeywordNamespace */
|
||||
.chroma .kp { color: #d73a49; } /* KeywordPseudo */
|
||||
.chroma .kr { color: #d73a49; } /* KeywordReserved */
|
||||
.chroma .kt { color: #445588; } /* KeywordType */
|
||||
.chroma .l {} /* Literal */
|
||||
.chroma .ld {} /* LiteralDate */
|
||||
.chroma .m { color: #009999; } /* LiteralNumber */
|
||||
.chroma .mb { color: #009999; } /* LiteralNumberBin */
|
||||
.chroma .mf { color: #009999; } /* LiteralNumberFloat */
|
||||
.chroma .mh { color: #009999; } /* LiteralNumberHex */
|
||||
.chroma .mi { color: #009999; } /* LiteralNumberInteger */
|
||||
.chroma .mo { color: #009999; } /* LiteralNumberOct */
|
||||
.chroma .n {} /* Name */
|
||||
.chroma .na { color: #d73a49; } /* NameAttribute */
|
||||
.chroma .nb { color: #005cc5; } /* NameBuiltin */
|
||||
.chroma .nc { color: #445588; } /* NameClass */
|
||||
.chroma .nd { color: #3c5d5d; } /* NameDecorator */
|
||||
.chroma .ne { color: #990000; } /* NameException */
|
||||
.chroma .nf { color: #005cc5; } /* NameFunction */
|
||||
.chroma .ni { color: #6f42c1; } /* NameEntity */
|
||||
.chroma .nl { color: #990000; } /* NameLabel */
|
||||
.chroma .nn { color: #555555; } /* NameNamespace */
|
||||
.chroma .no { color: #008080; } /* NameConstant */
|
||||
.chroma .nt { color: #22863a; } /* NameTag */
|
||||
.chroma .nv { color: #008080; } /* NameVariable */
|
||||
.chroma .nx { color: #24292e; } /* NameOther */
|
||||
.chroma .o { color: #d73a49; } /* Operator */
|
||||
.chroma .ow { color: #d73a49; } /* OperatorWord */
|
||||
.chroma .p {} /* Punctuation */
|
||||
.chroma .py {} /* NameProperty */
|
||||
.chroma .s { color: #106303; } /* LiteralString */
|
||||
.chroma .s1 { color: #106303; } /* LiteralStringSingle */
|
||||
.chroma .s2 { color: #106303; } /* LiteralStringDouble */
|
||||
.chroma .sa { color: #cc7a00; } /* LiteralStringAffix */
|
||||
.chroma .sb { color: #106303; } /* LiteralStringBacktick */
|
||||
.chroma .sc { color: #cc7a00; } /* LiteralStringChar */
|
||||
.chroma .sd { color: #106303; } /* LiteralStringDoc */
|
||||
.chroma .se { color: #994400; } /* LiteralStringEscape */
|
||||
.chroma .sh { color: #106303; } /* LiteralStringHeredoc */
|
||||
.chroma .si { color: #cc7a00; } /* LiteralStringInterpol */
|
||||
.chroma .sr { color: #4c4dbc; } /* LiteralStringRegex */
|
||||
.chroma .ss { color: #994400; } /* LiteralStringSymbol */
|
||||
.chroma .sx { color: #106303; } /* LiteralStringOther */
|
||||
.chroma .vc { color: #008080; } /* NameVariableClass */
|
||||
.chroma .vg { color: #008080; } /* NameVariableGlobal */
|
||||
.chroma .vi { color: #008080; } /* NameVariableInstance */
|
||||
.chroma .vm {} /* NameVariableMagic */
|
||||
.chroma .w { color: #bbbbbb; } /* TextWhitespace */
|
||||
.chroma .err {/* not styled because Chroma uses it on too many things like JSX */} /* Error */
|
||||
@@ -1,106 +0,0 @@
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-property,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-property {
|
||||
color: #a0cc75;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-header,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-header {
|
||||
color: #9daccc;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-quote,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-quote {
|
||||
color: #009900;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-keyword,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-keyword {
|
||||
color: #cc8a61;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-atom,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-atom {
|
||||
color: #ef5e77;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-number,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-number {
|
||||
color: #ff5656;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-def,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-def {
|
||||
color: #e4e4e4;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-variable-2,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-variable-2 {
|
||||
color: #00bdbf;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-variable-3,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-variable-3 {
|
||||
color: #008855;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-comment,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-comment {
|
||||
color: #8e9ab3;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-string,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-string {
|
||||
color: #a77272;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-string-2,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-string-2 {
|
||||
color: #ff5500;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-builtin,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-builtin {
|
||||
color: #b7c951;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-bracket,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-bracket {
|
||||
color: #999977;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-tag,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-tag {
|
||||
color: #f1d273;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-attribute,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-attribute {
|
||||
color: #bfcc70;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-hr,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-hr {
|
||||
color: #999999;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-url,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-url {
|
||||
color: #c5cfd0;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-link,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-link {
|
||||
color: #d8c792;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror.cm-s-default .cm-error,
|
||||
.EasyMDEContainer .CodeMirror.cm-s-paper .cm-error {
|
||||
color: #dbdbeb;
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
/* Placeholder, there is no light theme, it uses CM defaults */
|
||||
@@ -440,3 +440,26 @@
|
||||
border: 1px solid var(--color-secondary);
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.EasyMDEContainer .CodeMirror .cm-keyword { color: var(--color-syntax-keyword); }
|
||||
.EasyMDEContainer .CodeMirror .cm-atom { color: var(--color-syntax-keyword); }
|
||||
.EasyMDEContainer .CodeMirror .cm-number { color: var(--color-syntax-number); }
|
||||
.EasyMDEContainer .CodeMirror .cm-def { color: var(--color-syntax-name); }
|
||||
.EasyMDEContainer .CodeMirror .cm-variable-2 { color: var(--color-syntax-name); }
|
||||
.EasyMDEContainer .CodeMirror .cm-variable-3 { color: var(--color-syntax-type); }
|
||||
.EasyMDEContainer .CodeMirror .cm-property { color: var(--color-syntax-name); }
|
||||
.EasyMDEContainer .CodeMirror .cm-comment { color: var(--color-syntax-comment); }
|
||||
.EasyMDEContainer .CodeMirror .cm-string { color: var(--color-syntax-string); }
|
||||
.EasyMDEContainer .CodeMirror .cm-string-2 { color: var(--color-syntax-regexp); }
|
||||
.EasyMDEContainer .CodeMirror .cm-meta { color: var(--color-syntax-control); }
|
||||
.EasyMDEContainer .CodeMirror .cm-qualifier { color: var(--color-syntax-control); }
|
||||
.EasyMDEContainer .CodeMirror .cm-builtin { color: var(--color-syntax-keyword); }
|
||||
.EasyMDEContainer .CodeMirror .cm-bracket { color: var(--color-syntax-operator); }
|
||||
.EasyMDEContainer .CodeMirror .cm-tag { color: var(--color-syntax-type); }
|
||||
.EasyMDEContainer .CodeMirror .cm-attribute { color: var(--color-syntax-name); }
|
||||
.EasyMDEContainer .CodeMirror .cm-header { color: var(--color-syntax-type); }
|
||||
.EasyMDEContainer .CodeMirror .cm-quote { color: var(--color-syntax-comment); }
|
||||
.EasyMDEContainer .CodeMirror .cm-hr { color: var(--color-syntax-operator); }
|
||||
.EasyMDEContainer .CodeMirror .cm-url { color: var(--color-syntax-link); }
|
||||
.EasyMDEContainer .CodeMirror .cm-link { color: var(--color-syntax-link); }
|
||||
.EasyMDEContainer .CodeMirror .cm-error { color: var(--color-syntax-invalid); }
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
.editor-loading {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.monaco-editor-container,
|
||||
.editor-loading.is-loading {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.edit.githook .monaco-editor-container {
|
||||
border: 1px solid var(--color-secondary);
|
||||
height: 70vh;
|
||||
}
|
||||
|
||||
/* overwrite conflicting styles from fomantic */
|
||||
.monaco-editor-container .inputarea {
|
||||
min-height: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
resize: none !important;
|
||||
border: none !important;
|
||||
color: transparent !important;
|
||||
background-color: transparent !important;
|
||||
}
|
||||
|
||||
.monaco-editor,
|
||||
.monaco-editor .overflow-guard {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
/* fomantic styles destroy this element only visible on IOS, restore it */
|
||||
.monaco-editor .iPadShowKeyboard {
|
||||
border: none !important;
|
||||
width: 58px !important;
|
||||
min-width: 0 !important;
|
||||
height: 36px !important;
|
||||
min-height: 0 !important;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
position: absolute !important;
|
||||
resize: none !important;
|
||||
overflow: hidden !important;
|
||||
border-radius: var(--border-radius-medium) !important;
|
||||
}
|
||||
@@ -31,6 +31,8 @@
|
||||
@import "./modules/toast.css";
|
||||
@import "./modules/svg.css";
|
||||
@import "./modules/flexcontainer.css";
|
||||
@import "./modules/codeeditor.css";
|
||||
@import "./modules/chroma.css";
|
||||
|
||||
@import "./shared/flex-list.css";
|
||||
@import "./shared/milestone.css";
|
||||
@@ -41,7 +43,6 @@
|
||||
@import "./features/gitgraph.css";
|
||||
@import "./features/heatmap.css";
|
||||
@import "./features/imagediff.css";
|
||||
@import "./features/codeeditor.css";
|
||||
@import "./features/projects.css";
|
||||
@import "./features/expander.css";
|
||||
@import "./features/cropper.css";
|
||||
@@ -53,7 +54,6 @@
|
||||
@import "./markup/codepreview.css";
|
||||
@import "./markup/asciicast.css";
|
||||
|
||||
@import "./chroma/base.css";
|
||||
@import "./font_i18n.css";
|
||||
@import "./base.css";
|
||||
@import "./home.css";
|
||||
|
||||
@@ -503,7 +503,7 @@ html[data-gitea-theme-dark="false"] .markup img[src*="#gh-dark-mode-only"] {
|
||||
background-color: var(--color-markup-code-inline);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: inset 0 -1px 0 var(--color-secondary);
|
||||
box-shadow: var(--box-shadow-kbd);
|
||||
}
|
||||
|
||||
.markup .ui.list .list,
|
||||
|
||||
79
web_src/css/modules/chroma.css
Normal file
79
web_src/css/modules/chroma.css
Normal file
@@ -0,0 +1,79 @@
|
||||
/* https://github.com/alecthomas/chroma/blob/6428fb4e65f3c1493491571c8a6a8f1add1da822/types.go#L208 */
|
||||
|
||||
/* structural */
|
||||
.chroma .lntd { vertical-align: top; padding: 0; margin: 0; border: 0; }
|
||||
.chroma .lntable { border-spacing: 0; padding: 0; margin: 0; border: 0; width: auto; overflow: auto; display: block; }
|
||||
.chroma .hl { display: block; width: 100%; }
|
||||
.chroma .lnt { margin-right: 0.4em; padding: 0 0.4em; }
|
||||
.chroma .ln { margin-right: 0.4em; padding: 0 0.4em; }
|
||||
|
||||
/* tokens */
|
||||
.chroma .bp { color: var(--color-syntax-name-pseudo); } /* NameBuiltinPseudo */
|
||||
.chroma .c { color: var(--color-syntax-comment); } /* Comment */
|
||||
.chroma .c1 { color: var(--color-syntax-comment); } /* CommentSingle */
|
||||
.chroma .ch { color: var(--color-syntax-comment); } /* CommentHashbang */
|
||||
.chroma .cm { color: var(--color-syntax-comment); } /* CommentMultiline */
|
||||
.chroma .cp { color: var(--color-syntax-preproc); } /* CommentPreproc */
|
||||
.chroma .cpf { color: var(--color-syntax-preproc-file); } /* CommentPreprocFile */
|
||||
.chroma .cs { color: var(--color-syntax-comment-special); } /* CommentSpecial */
|
||||
.chroma .dl { color: var(--color-syntax-string); } /* LiteralStringDelimiter */
|
||||
.chroma .gd { color: var(--color-syntax-diff-fg); background-color: var(--color-syntax-deleted-bg); } /* GenericDeleted */
|
||||
.chroma .ge { color: var(--color-syntax-emph); } /* GenericEmph */
|
||||
.chroma .gh { color: var(--color-syntax-heading); } /* GenericHeading */
|
||||
.chroma .gi { color: var(--color-syntax-diff-fg); background-color: var(--color-syntax-inserted-bg); } /* GenericInserted */
|
||||
.chroma .gl { text-decoration: underline; } /* GenericUnderline */
|
||||
.chroma .go { color: var(--color-syntax-output); } /* GenericOutput */
|
||||
.chroma .gp { color: var(--color-syntax-prompt); } /* GenericPrompt */
|
||||
.chroma .gr { color: var(--color-syntax-invalid); } /* GenericError */
|
||||
.chroma .gs { color: var(--color-syntax-strong); font-weight: var(--font-weight-semibold); } /* GenericStrong */
|
||||
.chroma .gt { color: var(--color-syntax-traceback); } /* GenericTraceback */
|
||||
.chroma .gu { color: var(--color-syntax-subheading); } /* GenericSubheading */
|
||||
.chroma .il { color: var(--color-syntax-number); } /* LiteralNumberIntegerLong */
|
||||
.chroma .k { color: var(--color-syntax-keyword); } /* Keyword */
|
||||
.chroma .kc { color: var(--color-syntax-bool); } /* KeywordConstant */
|
||||
.chroma .kd { color: var(--color-syntax-keyword); } /* KeywordDeclaration */
|
||||
.chroma .kn { color: var(--color-syntax-control); } /* KeywordNamespace */
|
||||
.chroma .kp { color: var(--color-syntax-keyword); } /* KeywordPseudo */
|
||||
.chroma .kr { color: var(--color-syntax-keyword); } /* KeywordReserved */
|
||||
.chroma .kt { color: var(--color-syntax-type); } /* KeywordType */
|
||||
.chroma .m { color: var(--color-syntax-number); } /* LiteralNumber */
|
||||
.chroma .mb { color: var(--color-syntax-number); } /* LiteralNumberBin */
|
||||
.chroma .mf { color: var(--color-syntax-number); } /* LiteralNumberFloat */
|
||||
.chroma .mh { color: var(--color-syntax-number); } /* LiteralNumberHex */
|
||||
.chroma .mi { color: var(--color-syntax-number); } /* LiteralNumberInteger */
|
||||
.chroma .mo { color: var(--color-syntax-number); } /* LiteralNumberOct */
|
||||
.chroma .n { color: var(--color-syntax-text); } /* Name */
|
||||
.chroma .na { color: var(--color-syntax-attribute); } /* NameAttribute */
|
||||
.chroma .nb { color: var(--color-syntax-name); } /* NameBuiltin */
|
||||
.chroma .nc { color: var(--color-syntax-type); } /* NameClass */
|
||||
.chroma .nd { color: var(--color-syntax-decorator); } /* NameDecorator */
|
||||
.chroma .ne { color: var(--color-syntax-keyword); } /* NameException */
|
||||
.chroma .nf { color: var(--color-syntax-name); } /* NameFunction */
|
||||
.chroma .ni { color: var(--color-syntax-entity); } /* NameEntity */
|
||||
.chroma .nl { color: var(--color-syntax-keyword); } /* NameLabel */
|
||||
.chroma .nn { color: var(--color-syntax-namespace); } /* NameNamespace */
|
||||
.chroma .no { color: var(--color-syntax-variable); } /* NameConstant */
|
||||
.chroma .nt { color: var(--color-syntax-tag); } /* NameTag */
|
||||
.chroma .nv { color: var(--color-syntax-variable); } /* NameVariable */
|
||||
.chroma .nx { color: var(--color-syntax-text-alt); } /* NameOther */
|
||||
.chroma .o { color: var(--color-syntax-operator); } /* Operator */
|
||||
.chroma .ow { color: var(--color-syntax-operator); } /* OperatorWord */
|
||||
.chroma .p { color: var(--color-syntax-punctuation); } /* Punctuation */
|
||||
.chroma .s { color: var(--color-syntax-string); } /* LiteralString */
|
||||
.chroma .s1 { color: var(--color-syntax-string); } /* LiteralStringSingle */
|
||||
.chroma .s2 { color: var(--color-syntax-string); } /* LiteralStringDouble */
|
||||
.chroma .sa { color: var(--color-syntax-string-special); } /* LiteralStringAffix */
|
||||
.chroma .sb { color: var(--color-syntax-string); } /* LiteralStringBacktick */
|
||||
.chroma .sc { color: var(--color-syntax-string-special); } /* LiteralStringChar */
|
||||
.chroma .sd { color: var(--color-syntax-string); } /* LiteralStringDoc */
|
||||
.chroma .se { color: var(--color-syntax-escape); } /* LiteralStringEscape */
|
||||
.chroma .sh { color: var(--color-syntax-string); } /* LiteralStringHeredoc */
|
||||
.chroma .si { color: var(--color-syntax-string-special); } /* LiteralStringInterpol */
|
||||
.chroma .sr { color: var(--color-syntax-regexp); } /* LiteralStringRegex */
|
||||
.chroma .ss { color: var(--color-syntax-escape); } /* LiteralStringSymbol */
|
||||
.chroma .sx { color: var(--color-syntax-string); } /* LiteralStringOther */
|
||||
.chroma .vc { color: var(--color-syntax-variable); } /* NameVariableClass */
|
||||
.chroma .vg { color: var(--color-syntax-variable); } /* NameVariableGlobal */
|
||||
.chroma .vi { color: var(--color-syntax-variable); } /* NameVariableInstance */
|
||||
.chroma .w { color: var(--color-syntax-whitespace); } /* TextWhitespace */
|
||||
.chroma .err {/* not styled because Chroma uses it on too many things like JSX */} /* Error */
|
||||
569
web_src/css/modules/codeeditor.css
Normal file
569
web_src/css/modules/codeeditor.css
Normal file
@@ -0,0 +1,569 @@
|
||||
.code-editor-options {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.editor-loading {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.editor-loading.is-loading {
|
||||
width: 100%;
|
||||
min-height: 200px;
|
||||
height: 90vh;
|
||||
}
|
||||
|
||||
.edit.githook .code-editor-container {
|
||||
border: 1px solid var(--color-secondary);
|
||||
}
|
||||
|
||||
/* editor layout */
|
||||
.code-editor-container .cm-editor {
|
||||
color: var(--color-text);
|
||||
background-color: var(--color-code-bg);
|
||||
font-family: var(--fonts-monospace);
|
||||
font-size: 12px;
|
||||
max-height: 90vh;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-editor,
|
||||
.code-editor-container .cm-editor .cm-scroller {
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-content,
|
||||
.code-editor-container .cm-gutter {
|
||||
min-height: 200px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-scroller {
|
||||
overflow: auto;
|
||||
line-height: var(--line-height-code);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-content * {
|
||||
caret-color: inherit;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-content {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-cursor,
|
||||
.code-editor-container .cm-dropCursor {
|
||||
border-left-color: var(--color-caret);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-editor.cm-focused {
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-editor.cm-focused > .cm-scroller > .cm-selectionLayer .cm-selectionBackground,
|
||||
.code-editor-container .cm-selectionBackground {
|
||||
background-color: var(--color-primary-alpha-30);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panels {
|
||||
background-color: var(--color-body);
|
||||
color: var(--color-text);
|
||||
border-color: var(--color-secondary);
|
||||
font-family: var(--fonts-regular);
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
position: relative;
|
||||
padding-right: 24px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search br {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search::after {
|
||||
content: "";
|
||||
flex-basis: 100%;
|
||||
order: 1;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search .cm-textfield {
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search br + input.cm-textfield,
|
||||
.code-editor-container .cm-panel.cm-search br + input.cm-textfield ~ * {
|
||||
order: 2;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search input,
|
||||
.code-editor-container .cm-panel.cm-search button,
|
||||
.code-editor-container .cm-panel.cm-search label {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search .cm-button + label,
|
||||
.code-editor-container .cm-panel.cm-search label + label {
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search label {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search input[type="checkbox"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-editor .cm-panel button[name="close"] {
|
||||
font-size: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background-color: currentcolor;
|
||||
mask-image: var(--octicon-x);
|
||||
mask-size: cover;
|
||||
-webkit-mask-image: var(--octicon-x);
|
||||
-webkit-mask-size: cover;
|
||||
border-radius: var(--border-radius);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-search button[name="close"] {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-editor .cm-panel button[name="close"]:focus-visible {
|
||||
outline: 2px solid var(--color-primary);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-activeLine,
|
||||
.code-editor-container .cm-activeLineGutter {
|
||||
background-color: var(--color-editor-line-highlight);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-gutters {
|
||||
background-color: var(--color-code-bg);
|
||||
color: var(--color-secondary-dark-6);
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-gutters .cm-lineNumbers .cm-gutterElement {
|
||||
min-width: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-foldGutter .cm-gutterElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 2px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-foldGutter .cm-gutterElement svg {
|
||||
color: var(--color-text-light-2);
|
||||
cursor: pointer;
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-gutters:hover .cm-foldGutter .cm-gutterElement svg {
|
||||
visibility: visible;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-foldGutter .cm-gutterElement:hover svg {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-gutters .cm-lineNumbers .cm-activeLineGutter {
|
||||
color: var(--color-text-light);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-editor .cm-line ::selection,
|
||||
.code-editor-container .cm-editor .cm-line::selection {
|
||||
color: currentcolor;
|
||||
background-color: var(--color-editor-selection);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-foldPlaceholder {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--color-text-light-2);
|
||||
cursor: pointer;
|
||||
vertical-align: middle;
|
||||
padding: 0 1px;
|
||||
margin: 0 2px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-foldPlaceholder:hover {
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-searchMatch {
|
||||
background-color: var(--color-highlight-bg);
|
||||
outline: 1px solid var(--color-highlight-fg);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-searchMatch-selected {
|
||||
background-color: var(--color-primary-alpha-30);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-selectionMatch {
|
||||
background-color: var(--color-highlight-bg);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-tooltip {
|
||||
background-color: var(--color-body);
|
||||
color: var(--color-text);
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-tooltip.cm-tooltip-autocomplete > ul > li {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-completionIcon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 16px;
|
||||
padding-right: 4px;
|
||||
color: var(--color-text-light);
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-tooltip-autocomplete ul li[aria-selected] {
|
||||
background-color: var(--color-primary-alpha-30);
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-completionMatchedText {
|
||||
text-decoration: none;
|
||||
color: var(--color-primary-dark-1);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-placeholder {
|
||||
color: var(--color-placeholder-text);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-button {
|
||||
background-image: none;
|
||||
background-color: var(--color-button);
|
||||
color: var(--color-text);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
height: 28px;
|
||||
padding: 0 10px;
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-button:hover {
|
||||
background-color: var(--color-hover);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-textfield {
|
||||
background-color: var(--color-input-background);
|
||||
color: var(--color-input-text);
|
||||
border: 1px solid var(--color-input-border);
|
||||
border-radius: var(--border-radius);
|
||||
height: 28px;
|
||||
padding: 0 6px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-textfield:focus {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-specialChar {
|
||||
color: var(--color-syntax-invalid);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-trailingSpace {
|
||||
background-color: var(--color-error-bg);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-activeLine .cm-trailingSpace {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.code-editor-container.cm-mod-held .cm-url {
|
||||
text-decoration: underline dotted var(--color-syntax-link);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-editor.cm-focused .cm-matchingBracket {
|
||||
background-color: var(--color-syntax-matching-bracket-bg);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-editor.cm-focused .cm-nonmatchingBracket {
|
||||
background-color: var(--color-syntax-nonmatching-bracket-bg);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panels-top {
|
||||
border-bottom-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panels-bottom {
|
||||
border-top-color: var(--color-secondary);
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-snippetField {
|
||||
background-color: var(--color-primary-alpha-10);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-snippetFieldPosition {
|
||||
border-left-color: var(--color-text-light-3);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-tooltip.cm-tooltip-autocomplete > ul > completion-section {
|
||||
border-bottom-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-tooltip-autocomplete-disabled ul li[aria-selected] {
|
||||
background-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
/* command palette */
|
||||
.code-editor-container {
|
||||
position: relative;
|
||||
min-height: 90vh;
|
||||
}
|
||||
|
||||
.cm-command-palette {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
z-index: 301;
|
||||
width: 400px;
|
||||
max-width: min(calc(100% - 16px), 90vw);
|
||||
background-color: var(--color-body);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: 0 0 var(--border-radius) var(--border-radius);
|
||||
box-shadow: 0 4px 12px var(--color-shadow);
|
||||
font-family: var(--fonts-regular);
|
||||
font-size: 13px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.cm-command-palette-input {
|
||||
display: block;
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 4px 6px !important;
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
background-color: var(--color-input-background);
|
||||
color: var(--color-input-text);
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.cm-command-palette-input:focus {
|
||||
border-color: var(--color-primary);
|
||||
}
|
||||
|
||||
.cm-command-palette-list {
|
||||
position: relative;
|
||||
max-height: calc(8 * 24px);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.cm-command-palette-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 24px;
|
||||
padding: 0 6px;
|
||||
border-radius: 2px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cm-command-palette-item[aria-selected="true"] {
|
||||
background-color: var(--color-primary-alpha-30);
|
||||
}
|
||||
|
||||
.cm-command-palette-empty {
|
||||
color: var(--color-text-light);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.cm-command-palette-label {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cm-command-palette-label mark {
|
||||
background: none;
|
||||
color: var(--color-primary-dark-1);
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
.cm-command-palette-keys {
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-gutter-lint {
|
||||
width: 14px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-gutter-lint .cm-gutterElement {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-lint-marker-error {
|
||||
content: "";
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-red);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-lint-marker-warning {
|
||||
content: "";
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background-color: var(--color-yellow);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-lintRange-error {
|
||||
background-image: none;
|
||||
text-decoration: wavy underline var(--color-red);
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-lintRange-warning {
|
||||
background-image: none;
|
||||
text-decoration: wavy underline var(--color-yellow);
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-lintPoint-error::after {
|
||||
border-bottom-color: var(--color-red);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-lintPoint-warning::after {
|
||||
border-bottom-color: var(--color-yellow);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-tooltip-lint {
|
||||
background-color: var(--color-body);
|
||||
color: var(--color-text);
|
||||
border-color: var(--color-secondary);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-panel-lint button[name="close"] {
|
||||
top: 4px;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-panel-lint ul {
|
||||
max-height: 120px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-panel.cm-panel-lint [aria-selected] {
|
||||
background-color: var(--color-primary-alpha-30) !important;
|
||||
}
|
||||
|
||||
.code-editor-container .cm-diagnostic.cm-diagnostic-error {
|
||||
border-left-color: var(--color-red);
|
||||
}
|
||||
|
||||
.code-editor-container .cm-diagnostic.cm-diagnostic-warning {
|
||||
border-left-color: var(--color-yellow);
|
||||
}
|
||||
|
||||
/* syntax highlighting classes from @lezer/highlight classHighlighter */
|
||||
.code-editor-container .tok-keyword,
|
||||
.code-editor-container .tok-atom { color: var(--color-syntax-keyword); }
|
||||
.code-editor-container .tok-bool { color: var(--color-syntax-bool); }
|
||||
.code-editor-container .tok-variableName { color: var(--color-syntax-variable); }
|
||||
.code-editor-container .tok-variableName2 { color: var(--color-syntax-keyword); }
|
||||
.code-editor-container .tok-propertyName { color: var(--color-syntax-property); }
|
||||
.code-editor-container .tok-typeName,
|
||||
.code-editor-container .tok-className { color: var(--color-syntax-type); }
|
||||
.code-editor-container .tok-namespace { color: var(--color-syntax-namespace); }
|
||||
.code-editor-container .tok-macroName { color: var(--color-syntax-name); }
|
||||
.code-editor-container .tok-labelName { color: var(--color-syntax-name); }
|
||||
.code-editor-container .tok-number { color: var(--color-syntax-number); }
|
||||
.code-editor-container .tok-string { color: var(--color-syntax-string); }
|
||||
.code-editor-container .tok-string2 { color: var(--color-syntax-regexp); }
|
||||
.code-editor-container .tok-operator { color: var(--color-syntax-operator); }
|
||||
.code-editor-container .tok-punctuation { color: var(--color-syntax-punctuation); }
|
||||
.code-editor-container .tok-comment { color: var(--color-syntax-comment); }
|
||||
.code-editor-container .tok-meta { color: var(--color-syntax-preproc); }
|
||||
.code-editor-container .tok-invalid { color: var(--color-syntax-invalid); }
|
||||
.code-editor-container .tok-link { color: var(--color-syntax-link); }
|
||||
.code-editor-container .tok-heading { color: var(--color-syntax-heading); }
|
||||
.code-editor-container .tok-emphasis { color: var(--color-syntax-emph); font-style: italic; }
|
||||
.code-editor-container .tok-strong { font-weight: var(--font-weight-bold); }
|
||||
.code-editor-container .tok-inserted { color: var(--color-syntax-string); }
|
||||
.code-editor-container .tok-deleted { color: var(--color-syntax-invalid); }
|
||||
|
||||
/* language-specific overrides */
|
||||
.code-editor-container[data-language="json"] .tok-propertyName,
|
||||
.code-editor-container[data-language="json5"] .tok-propertyName,
|
||||
.code-editor-container[data-language="yaml"] .tok-propertyName { color: var(--color-syntax-tag); }
|
||||
.code-editor-container[data-language="css"] .tok-propertyName { color: var(--color-syntax-name); }
|
||||
.code-editor-container[data-language="html"] .tok-propertyName,
|
||||
.code-editor-container[data-language="xml"] .tok-propertyName { color: var(--color-syntax-attribute); }
|
||||
|
||||
/* context menu — uses tippy "menu" theme for base styling */
|
||||
.cm-context-menu {
|
||||
min-width: 200px;
|
||||
font-size: 13px;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.cm-context-menu .item {
|
||||
height: 24px !important;
|
||||
padding: 0 12px !important;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cm-context-menu .item.disabled {
|
||||
color: var(--color-text-light-3);
|
||||
cursor: default;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cm-context-menu-keys {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 2px;
|
||||
margin-left: auto;
|
||||
padding-left: 30px;
|
||||
}
|
||||
|
||||
.cm-context-menu-separator {
|
||||
border-top: 1px solid var(--color-secondary);
|
||||
margin: 4px 0;
|
||||
}
|
||||
@@ -2,19 +2,25 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
kbd {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 18px;
|
||||
padding: 2px 4px;
|
||||
font-size: 11px;
|
||||
line-height: 1;
|
||||
color: var(--color-text-light-2);
|
||||
background-color: var(--color-box-body);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: var(--box-shadow-kbd);
|
||||
}
|
||||
|
||||
.global-shortcut-wrapper > kbd {
|
||||
position: absolute;
|
||||
right: 6px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
display: inline-block;
|
||||
padding: 2px 6px;
|
||||
font-size: 11px;
|
||||
line-height: 14px;
|
||||
color: var(--color-text-light-2);
|
||||
background-color: var(--color-box-body);
|
||||
border: 1px solid var(--color-secondary);
|
||||
border-radius: var(--border-radius);
|
||||
box-shadow: inset 0 -1px 0 var(--color-secondary);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@@ -207,6 +207,10 @@ td .commit-summary {
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.repo-editor-menu {
|
||||
min-height: auto !important;
|
||||
}
|
||||
|
||||
.repo-editor-header {
|
||||
display: flex;
|
||||
margin: 1rem 0;
|
||||
@@ -841,7 +845,7 @@ td .commit-summary {
|
||||
width: 1%;
|
||||
min-width: 50px;
|
||||
font-family: monospace;
|
||||
line-height: 20px;
|
||||
line-height: var(--line-height-code);
|
||||
color: var(--color-text-light-1);
|
||||
white-space: nowrap;
|
||||
vertical-align: top;
|
||||
@@ -1763,13 +1767,23 @@ tbody.commit-list {
|
||||
}
|
||||
|
||||
.removed-code {
|
||||
color: var(--color-text);
|
||||
background: var(--color-diff-removed-word-bg);
|
||||
}
|
||||
|
||||
.removed-code span {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.added-code {
|
||||
color: var(--color-text);
|
||||
background: var(--color-diff-added-word-bg);
|
||||
}
|
||||
|
||||
.added-code span {
|
||||
color: inherit !important;
|
||||
}
|
||||
|
||||
.code-diff-unified .del-code,
|
||||
.code-diff-unified .del-code td,
|
||||
.code-diff-split .del-code .lines-num-old,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
@import "../chroma/dark.css";
|
||||
@import "../codemirror/dark.css";
|
||||
|
||||
gitea-theme-meta-info {
|
||||
--theme-display-name: "Dark";
|
||||
--theme-color-scheme: "dark";
|
||||
@@ -224,7 +221,8 @@ gitea-theme-meta-info {
|
||||
--color-secondary-bg: #2a3137;
|
||||
--color-expand-button: #2f363d;
|
||||
--color-placeholder-text: var(--color-text-light-3);
|
||||
--color-editor-line-highlight: var(--color-primary-light-5);
|
||||
--color-editor-line-highlight: var(--color-secondary-alpha-40);
|
||||
--color-editor-selection: var(--color-primary-alpha-50);
|
||||
--color-project-column-bg: var(--color-secondary-light-2);
|
||||
--color-caret: var(--color-text); /* should ideally be --color-text-dark, see #15651 */
|
||||
--color-reaction-bg: #e8f3ff12;
|
||||
@@ -243,12 +241,53 @@ gitea-theme-meta-info {
|
||||
--color-accent: var(--color-primary-light-1);
|
||||
--color-small-accent: var(--color-primary-light-5);
|
||||
--color-highlight-fg: #87651e;
|
||||
--color-highlight-bg: #352c1c;
|
||||
--color-highlight-bg: #443a27;
|
||||
--color-overlay-backdrop: #080808c0;
|
||||
--color-danger: var(--color-red);
|
||||
--color-transparency-grid-light: #2a2a2a;
|
||||
--color-transparency-grid-dark: #1a1a1a;
|
||||
--color-workflow-edge-hover: #616e78;
|
||||
--color-syntax-keyword: #ff8854;
|
||||
--color-syntax-bool: #25bbc9;
|
||||
--color-syntax-control: #dd9e17;
|
||||
--color-syntax-name: #c7a618;
|
||||
--color-syntax-type: #eb8cb3;
|
||||
--color-syntax-number: #63b2dd;
|
||||
--color-syntax-operator: #ff8854;
|
||||
--color-syntax-regexp: #b89de4;
|
||||
--color-syntax-string: #95b62a;
|
||||
--color-syntax-comment: #8898b0;
|
||||
--color-syntax-invalid: #ff8686;
|
||||
--color-syntax-link: var(--color-primary);
|
||||
--color-syntax-tag: #ff8854;
|
||||
--color-syntax-attribute: #c792ff;
|
||||
--color-syntax-property: #55afff;
|
||||
--color-syntax-variable: #e29b33;
|
||||
--color-syntax-string-special: #dd9e17;
|
||||
--color-syntax-escape: #c7a618;
|
||||
--color-syntax-entity: #c792ff;
|
||||
--color-syntax-preproc: #4cbe7a;
|
||||
--color-syntax-preproc-file: #63b2dd;
|
||||
--color-syntax-decorator: #4cbe7a;
|
||||
--color-syntax-namespace: #c9d1d9;
|
||||
--color-syntax-name-pseudo: #c792ff;
|
||||
--color-syntax-comment-special: #b89de4;
|
||||
--color-syntax-text: #c9d1d9;
|
||||
--color-syntax-text-alt: #b9bcc7;
|
||||
--color-syntax-punctuation: #d2d4db;
|
||||
--color-syntax-whitespace: #7f8699;
|
||||
--color-syntax-diff-fg: #ffffff;
|
||||
--color-syntax-deleted-bg: #5f3737;
|
||||
--color-syntax-inserted-bg: #3a523a;
|
||||
--color-syntax-emph: #d1a242;
|
||||
--color-syntax-strong: #e29b33;
|
||||
--color-syntax-heading: #dd9e17;
|
||||
--color-syntax-subheading: #95b62a;
|
||||
--color-syntax-output: #8898b0;
|
||||
--color-syntax-prompt: #e29b33;
|
||||
--color-syntax-traceback: #ff8686;
|
||||
--color-syntax-matching-bracket-bg: #00918a48;
|
||||
--color-syntax-nonmatching-bracket-bg: #cc484848;
|
||||
accent-color: var(--color-accent);
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
@import "../chroma/light.css";
|
||||
@import "../codemirror/light.css";
|
||||
|
||||
gitea-theme-meta-info {
|
||||
--theme-display-name: "Light";
|
||||
--theme-color-scheme: "light";
|
||||
@@ -224,7 +221,8 @@ gitea-theme-meta-info {
|
||||
--color-secondary-bg: #f2f5f8;
|
||||
--color-expand-button: #cfe8fa;
|
||||
--color-placeholder-text: var(--color-text-light-3);
|
||||
--color-editor-line-highlight: var(--color-primary-light-6);
|
||||
--color-editor-line-highlight: var(--color-secondary-alpha-30);
|
||||
--color-editor-selection: var(--color-primary-alpha-30);
|
||||
--color-project-column-bg: var(--color-secondary-light-4);
|
||||
--color-caret: var(--color-text-dark);
|
||||
--color-reaction-bg: #0000170a;
|
||||
@@ -243,12 +241,53 @@ gitea-theme-meta-info {
|
||||
--color-accent: var(--color-primary-light-1);
|
||||
--color-small-accent: var(--color-primary-light-6);
|
||||
--color-highlight-fg: #eed200;
|
||||
--color-highlight-bg: #fffbdd;
|
||||
--color-highlight-bg: #f5efc5;
|
||||
--color-overlay-backdrop: #080808c0;
|
||||
--color-danger: var(--color-red);
|
||||
--color-transparency-grid-light: #fafafa;
|
||||
--color-transparency-grid-dark: #e2e2e2;
|
||||
--color-workflow-edge-hover: #b1b7bd;
|
||||
--color-syntax-keyword: #a73a00;
|
||||
--color-syntax-bool: #076872;
|
||||
--color-syntax-control: #7d5700;
|
||||
--color-syntax-name: #785900;
|
||||
--color-syntax-type: #ae2368;
|
||||
--color-syntax-number: #105ead;
|
||||
--color-syntax-operator: #a73a00;
|
||||
--color-syntax-regexp: #773dc5;
|
||||
--color-syntax-string: #456800;
|
||||
--color-syntax-comment: #506070;
|
||||
--color-syntax-invalid: #c00000;
|
||||
--color-syntax-link: var(--color-primary);
|
||||
--color-syntax-tag: #a73a00;
|
||||
--color-syntax-attribute: #6f41c5;
|
||||
--color-syntax-property: #2060a0;
|
||||
--color-syntax-variable: #944a00;
|
||||
--color-syntax-string-special: #7d5700;
|
||||
--color-syntax-escape: #785900;
|
||||
--color-syntax-entity: #6f41c5;
|
||||
--color-syntax-preproc: #2d6a4b;
|
||||
--color-syntax-preproc-file: #105ead;
|
||||
--color-syntax-decorator: #2d6a4b;
|
||||
--color-syntax-namespace: #555555;
|
||||
--color-syntax-name-pseudo: #6f41c5;
|
||||
--color-syntax-comment-special: #773dc5;
|
||||
--color-syntax-text: inherit;
|
||||
--color-syntax-text-alt: #47525b;
|
||||
--color-syntax-punctuation: inherit;
|
||||
--color-syntax-whitespace: #bbbbbb;
|
||||
--color-syntax-diff-fg: #000000;
|
||||
--color-syntax-deleted-bg: #ffdddd;
|
||||
--color-syntax-inserted-bg: #ddffdd;
|
||||
--color-syntax-emph: #8b5000;
|
||||
--color-syntax-strong: inherit;
|
||||
--color-syntax-heading: #7d5700;
|
||||
--color-syntax-subheading: #456800;
|
||||
--color-syntax-output: #506070;
|
||||
--color-syntax-prompt: #944a00;
|
||||
--color-syntax-traceback: #c00000;
|
||||
--color-syntax-matching-bracket-bg: #00b5ad38;
|
||||
--color-syntax-nonmatching-bracket-bg: #db282838;
|
||||
accent-color: var(--color-accent);
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
@@ -1,255 +0,0 @@
|
||||
import {colord} from 'colord';
|
||||
import {basename, extname, isObject, isDarkTheme} from '../utils.ts';
|
||||
import {onInputDebounce, toggleElem} from '../utils/dom.ts';
|
||||
import type MonacoNamespace from 'monaco-editor';
|
||||
|
||||
type Monaco = typeof MonacoNamespace;
|
||||
type IStandaloneCodeEditor = MonacoNamespace.editor.IStandaloneCodeEditor;
|
||||
type IEditorOptions = MonacoNamespace.editor.IEditorOptions;
|
||||
type IGlobalEditorOptions = MonacoNamespace.editor.IGlobalEditorOptions;
|
||||
type ITextModelUpdateOptions = MonacoNamespace.editor.ITextModelUpdateOptions;
|
||||
type MonacoOpts = IEditorOptions & IGlobalEditorOptions & ITextModelUpdateOptions;
|
||||
|
||||
type CodeEditorConfig = {
|
||||
indent_style?: 'tab' | 'space',
|
||||
indent_size?: number,
|
||||
tab_width?: string | number, // backend emits this as string
|
||||
trim_trailing_whitespace?: boolean,
|
||||
};
|
||||
|
||||
const languagesByFilename: Record<string, string> = {};
|
||||
const languagesByExt: Record<string, string> = {};
|
||||
|
||||
const baseOptions: MonacoOpts = {
|
||||
fontFamily: 'var(--fonts-monospace)',
|
||||
fontSize: 14, // https://github.com/microsoft/monaco-editor/issues/2242
|
||||
guides: {bracketPairs: false, indentation: false},
|
||||
links: false,
|
||||
minimap: {enabled: false},
|
||||
occurrencesHighlight: 'off',
|
||||
overviewRulerLanes: 0,
|
||||
renderLineHighlight: 'all',
|
||||
renderLineHighlightOnlyWhenFocus: true,
|
||||
rulers: [],
|
||||
scrollbar: {horizontalScrollbarSize: 6, verticalScrollbarSize: 6, alwaysConsumeMouseWheel: false},
|
||||
scrollBeyondLastLine: false,
|
||||
automaticLayout: true,
|
||||
indentSize: 'tabSize',
|
||||
wrappingIndent: 'none',
|
||||
wordWrapBreakAfterCharacters: '',
|
||||
wordWrapBreakBeforeCharacters: '',
|
||||
matchBrackets: 'never',
|
||||
editContext: false, // https://github.com/microsoft/monaco-editor/issues/5081
|
||||
};
|
||||
|
||||
function getCodeEditorConfig(input: HTMLInputElement): CodeEditorConfig | null {
|
||||
const json = input.getAttribute('data-code-editor-config');
|
||||
if (!json) return null;
|
||||
try {
|
||||
return JSON.parse(json);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function initLanguages(monaco: Monaco): void {
|
||||
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;
|
||||
}
|
||||
if (id === 'typescript') {
|
||||
monaco.typescript.typescriptDefaults.setCompilerOptions({
|
||||
// this is needed to suppress error annotations in tsx regarding missing --jsx flag.
|
||||
jsx: monaco.typescript.JsxEmit.Preserve,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getLanguage(filename: string): string {
|
||||
return languagesByFilename[filename] || languagesByExt[extname(filename)] || 'plaintext';
|
||||
}
|
||||
|
||||
function updateEditor(monaco: Monaco, editor: IStandaloneCodeEditor, filename: string, lineWrapExts: string[]): void {
|
||||
editor.updateOptions(getFileBasedOptions(filename, lineWrapExts));
|
||||
const model = editor.getModel();
|
||||
if (!model) return;
|
||||
const language = model.getLanguageId();
|
||||
const newLanguage = getLanguage(filename);
|
||||
if (language !== newLanguage) monaco.editor.setModelLanguage(model, newLanguage);
|
||||
// TODO: Need to update the model uri with the new filename, but there is no easy way currently, see
|
||||
// https://github.com/microsoft/monaco-editor/discussions/3751
|
||||
}
|
||||
|
||||
// export editor for customization - https://github.com/go-gitea/gitea/issues/10409
|
||||
function exportEditor(editor: IStandaloneCodeEditor): void {
|
||||
if (!window.codeEditors) window.codeEditors = [];
|
||||
if (!window.codeEditors.includes(editor)) window.codeEditors.push(editor);
|
||||
}
|
||||
|
||||
function updateTheme(monaco: Monaco): void {
|
||||
// https://github.com/microsoft/monaco-editor/issues/2427
|
||||
// also, monaco can only parse 6-digit hex colors, so we convert the colors to that format
|
||||
const styles = window.getComputedStyle(document.documentElement);
|
||||
const getColor = (name: string) => colord(styles.getPropertyValue(name).trim()).alpha(1).toHex();
|
||||
|
||||
monaco.editor.defineTheme('gitea', {
|
||||
base: isDarkTheme() ? 'vs-dark' : 'vs',
|
||||
inherit: true,
|
||||
rules: [
|
||||
{
|
||||
background: getColor('--color-code-bg'),
|
||||
token: '',
|
||||
},
|
||||
],
|
||||
colors: {
|
||||
'editor.background': getColor('--color-code-bg'),
|
||||
'editor.foreground': getColor('--color-text'),
|
||||
'editor.inactiveSelectionBackground': getColor('--color-primary-light-4'),
|
||||
'editor.lineHighlightBackground': getColor('--color-editor-line-highlight'),
|
||||
'editor.selectionBackground': getColor('--color-primary-light-3'),
|
||||
'editor.selectionForeground': getColor('--color-primary-light-3'),
|
||||
'editorLineNumber.background': getColor('--color-code-bg'),
|
||||
'editorLineNumber.foreground': getColor('--color-secondary-dark-6'),
|
||||
'editorWidget.background': getColor('--color-body'),
|
||||
'editorWidget.border': getColor('--color-secondary'),
|
||||
'input.background': getColor('--color-input-background'),
|
||||
'input.border': getColor('--color-input-border'),
|
||||
'input.foreground': getColor('--color-input-text'),
|
||||
'scrollbar.shadow': getColor('--color-shadow-opaque'),
|
||||
'progressBar.background': getColor('--color-primary'),
|
||||
'focusBorder': '#0000', // prevent blue border
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
type CreateMonacoOpts = MonacoOpts & {language?: string};
|
||||
|
||||
export async function createMonaco(textarea: HTMLTextAreaElement, filename: string, opts: CreateMonacoOpts): Promise<{monaco: Monaco, editor: IStandaloneCodeEditor}> {
|
||||
const monaco = await import('../modules/monaco.ts');
|
||||
|
||||
initLanguages(monaco);
|
||||
let {language, ...other} = opts;
|
||||
if (!language) language = getLanguage(filename);
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.className = 'monaco-editor-container';
|
||||
if (!textarea.parentNode) throw new Error('Parent node absent');
|
||||
textarea.parentNode.append(container);
|
||||
|
||||
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
||||
updateTheme(monaco);
|
||||
});
|
||||
updateTheme(monaco);
|
||||
|
||||
const model = monaco.editor.createModel(textarea.value, language, monaco.Uri.file(filename));
|
||||
|
||||
const editor = monaco.editor.create(container, {
|
||||
model,
|
||||
theme: 'gitea',
|
||||
...baseOptions,
|
||||
...other,
|
||||
});
|
||||
|
||||
monaco.editor.addKeybindingRules([
|
||||
{keybinding: monaco.KeyCode.Enter, command: null}, // disable enter from accepting code completion
|
||||
]);
|
||||
|
||||
model.onDidChangeContent(() => {
|
||||
textarea.value = editor.getValue({
|
||||
preserveBOM: true,
|
||||
lineEnding: '',
|
||||
});
|
||||
textarea.dispatchEvent(new Event('change')); // seems to be needed for jquery-are-you-sure
|
||||
});
|
||||
|
||||
const elEditorOptions = textarea.closest('form')?.querySelector('.code-editor-options');
|
||||
if (elEditorOptions) {
|
||||
elEditorOptions.querySelector<HTMLSelectElement>('.js-indent-style-select')!.addEventListener('change', (e) => {
|
||||
const insertSpaces = (e.target as HTMLSelectElement).value === 'space';
|
||||
editor.updateOptions({insertSpaces, useTabStops: !insertSpaces});
|
||||
});
|
||||
elEditorOptions.querySelector<HTMLSelectElement>('.js-indent-size-select')!.addEventListener('change', (e) => {
|
||||
const tabSize = Number((e.target as HTMLSelectElement).value);
|
||||
editor.updateOptions({tabSize});
|
||||
});
|
||||
elEditorOptions.querySelector<HTMLSelectElement>('.js-line-wrap-select')!.addEventListener('change', (e) => {
|
||||
const wordWrap = (e.target as HTMLSelectElement).value as IEditorOptions['wordWrap'];
|
||||
editor.updateOptions({wordWrap});
|
||||
});
|
||||
}
|
||||
|
||||
exportEditor(editor);
|
||||
|
||||
const loading = document.querySelector('.editor-loading');
|
||||
if (loading) loading.remove();
|
||||
|
||||
return {monaco, editor};
|
||||
}
|
||||
|
||||
function getFileBasedOptions(filename: string, lineWrapExts: string[]): MonacoOpts {
|
||||
return {
|
||||
wordWrap: (lineWrapExts || []).includes(extname(filename)) ? 'on' : 'off',
|
||||
};
|
||||
}
|
||||
|
||||
function togglePreviewDisplay(previewable: boolean): void {
|
||||
// FIXME: here and below, the selector is too broad, it should only query in the editor related scope
|
||||
const previewTab = document.querySelector<HTMLElement>('a[data-tab="preview"]');
|
||||
// the "preview tab" exists for "file code editor", but doesn't exist for "git hook editor"
|
||||
if (!previewTab) return;
|
||||
|
||||
toggleElem(previewTab, previewable);
|
||||
if (previewable) return;
|
||||
|
||||
// If not previewable but 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<HTMLElement>('a[data-tab="write"]');
|
||||
writeTab?.click(); // TODO: it shouldn't need null-safe operator, writeTab must exist
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameInput: HTMLInputElement): Promise<IStandaloneCodeEditor> {
|
||||
const filename = basename(filenameInput.value);
|
||||
const previewableExts = new Set((textarea.getAttribute('data-previewable-extensions') || '').split(','));
|
||||
const lineWrapExts = (textarea.getAttribute('data-line-wrap-extensions') || '').split(',');
|
||||
const isPreviewable = previewableExts.has(extname(filename));
|
||||
const editorConfig = getCodeEditorConfig(filenameInput);
|
||||
|
||||
togglePreviewDisplay(isPreviewable);
|
||||
|
||||
const {monaco, editor} = await createMonaco(textarea, filename, {
|
||||
...getFileBasedOptions(filenameInput.value, lineWrapExts),
|
||||
...getMonacoOptsByCodeEditorConfig(editorConfig),
|
||||
});
|
||||
|
||||
filenameInput.addEventListener('input', onInputDebounce(() => {
|
||||
const filename = filenameInput.value;
|
||||
const previewable = previewableExts.has(extname(filename));
|
||||
togglePreviewDisplay(previewable);
|
||||
updateEditor(monaco, editor, filename, lineWrapExts);
|
||||
}));
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
function getMonacoOptsByCodeEditorConfig(ec: CodeEditorConfig | null): MonacoOpts {
|
||||
if (!ec || !isObject(ec)) return {};
|
||||
|
||||
const opts: MonacoOpts = {};
|
||||
opts.detectIndentation = !ec.indent_style || !ec.indent_size;
|
||||
|
||||
// with indentSize='tabSize', this also controls the `indentSize` option
|
||||
if (!opts.detectIndentation) {
|
||||
opts.tabSize = Number(ec.tab_width) || Number(ec.indent_size) || 4;
|
||||
}
|
||||
|
||||
opts.trimAutoWhitespace = ec.trim_trailing_whitespace === true;
|
||||
opts.insertSpaces = ec.indent_style === 'space';
|
||||
opts.useTabStops = ec.indent_style === 'tab';
|
||||
return opts;
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import {html, htmlRaw} from '../utils/html.ts';
|
||||
import {createCodeEditor} from './codeeditor.ts';
|
||||
import {hideElem, queryElems, showElem, createElementFromHTML} from '../utils/dom.ts';
|
||||
import {createCodeEditor} from '../modules/codeeditor/main.ts';
|
||||
import {trimTrailingWhitespaceFromView} from '../modules/codeeditor/utils.ts';
|
||||
import {hideElem, queryElems, showElem, createElementFromHTML, onInputDebounce} from '../utils/dom.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {initDropzone} from './dropzone.ts';
|
||||
import {confirmModal} from './comp/ConfirmModal.ts';
|
||||
@@ -50,8 +51,12 @@ export function initRepoEditor() {
|
||||
});
|
||||
}
|
||||
|
||||
// ATTENTION: two pages have this filename input
|
||||
// * new/edit file page: there is a code editor
|
||||
// * upload page: there is no code editor, but a uploader
|
||||
const filenameInput = document.querySelector<HTMLInputElement>('#file-name')!;
|
||||
if (!filenameInput) return;
|
||||
filenameInput.value = filenameInput.defaultValue; // prevent browser from restoring form values on refresh
|
||||
function joinTreePath() {
|
||||
const parts = [];
|
||||
for (const el of document.querySelectorAll('.breadcrumb span.section')) {
|
||||
@@ -143,7 +148,8 @@ export function initRepoEditor() {
|
||||
|
||||
const elForm = document.querySelector<HTMLFormElement>('.repository.editor .edit.form')!;
|
||||
|
||||
// on the upload page, there is no editor(textarea)
|
||||
// see the ATTENTION above, on the upload page, there is no editor(textarea)
|
||||
// so only the filename input above is initialized, the code below (for the code editor) will be skipped
|
||||
const editArea = document.querySelector<HTMLTextAreaElement>('.page-content.repository.editor textarea#edit_area');
|
||||
if (!editArea) return;
|
||||
|
||||
@@ -170,16 +176,22 @@ export function initRepoEditor() {
|
||||
|
||||
(async () => {
|
||||
const editor = await createCodeEditor(editArea, filenameInput);
|
||||
filenameInput.addEventListener('input', onInputDebounce(() => editor.updateFilename(filenameInput.value)));
|
||||
|
||||
// Update the editor from query params, if available,
|
||||
// only after the dirtyFileClass initialization
|
||||
const params = new URLSearchParams(window.location.search);
|
||||
const value = params.get('value');
|
||||
if (value) {
|
||||
editor.setValue(value);
|
||||
editor.view.dispatch({
|
||||
changes: {from: 0, to: editor.view.state.doc.length, insert: value},
|
||||
});
|
||||
}
|
||||
|
||||
commitButton.addEventListener('click', async (e) => {
|
||||
if (editor.trimTrailingWhitespace) {
|
||||
trimTrailingWhitespaceFromView(editor.view);
|
||||
}
|
||||
// A modal which asks if an empty file should be committed
|
||||
if (!editArea.value) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {createMonaco} from './codeeditor.ts';
|
||||
import {createCodeEditor} from '../modules/codeeditor/main.ts';
|
||||
import {onInputDebounce, queryElems, toggleElem} from '../utils/dom.ts';
|
||||
import {POST} from '../modules/fetch.ts';
|
||||
import {initRepoSettingsBranchesDrag} from './repo-settings-branches.ts';
|
||||
@@ -72,8 +72,7 @@ function initRepoSettingsSearchTeamBox() {
|
||||
|
||||
function initRepoSettingsGitHook() {
|
||||
if (!document.querySelector('.page-content.repository.settings.edit.githook')) return;
|
||||
const filename = document.querySelector('.hook-filename')!.textContent;
|
||||
createMonaco(document.querySelector<HTMLTextAreaElement>('#content')!, filename, {language: 'shell'});
|
||||
createCodeEditor(document.querySelector<HTMLTextAreaElement>('#content')!);
|
||||
}
|
||||
|
||||
function initRepoSettingsBranches() {
|
||||
|
||||
5
web_src/js/globals.d.ts
vendored
5
web_src/js/globals.d.ts
vendored
@@ -61,13 +61,8 @@ interface Window {
|
||||
_inited: boolean,
|
||||
push: (e: ErrorEvent & PromiseRejectionEvent) => void | number,
|
||||
},
|
||||
codeEditors: any[], // export editor for customization
|
||||
localUserSettings: typeof import('./modules/user-settings.ts').localUserSettings,
|
||||
|
||||
MonacoEnvironment?: {
|
||||
getWorker: (workerId: string, label: string) => Worker,
|
||||
},
|
||||
|
||||
// various captcha plugins
|
||||
grecaptcha: any,
|
||||
turnstile: any,
|
||||
|
||||
216
web_src/js/modules/codeeditor/command-palette.ts
Normal file
216
web_src/js/modules/codeeditor/command-palette.ts
Normal file
@@ -0,0 +1,216 @@
|
||||
import {isMac, keySymbols} from '../../utils.ts';
|
||||
import {trimTrailingWhitespaceFromView} from './utils.ts';
|
||||
import type {EditorView} from '@codemirror/view';
|
||||
import type {CodemirrorModules} from './main.ts';
|
||||
|
||||
export type PaletteCommand = {
|
||||
label: string;
|
||||
keys: string;
|
||||
run: (view: EditorView) => void;
|
||||
};
|
||||
|
||||
function formatKeys(keys: string): string[][] {
|
||||
return keys.split(' ').map((chord) => chord.split('+').map((k) => keySymbols[k] || k));
|
||||
}
|
||||
|
||||
export function commandPalette(cm: CodemirrorModules) {
|
||||
const commands: PaletteCommand[] = [
|
||||
{label: 'Undo', keys: 'Mod+Z', run: cm.commands.undo},
|
||||
{label: 'Redo', keys: 'Mod+Shift+Z', run: cm.commands.redo},
|
||||
{label: 'Find', keys: 'Mod+F', run: cm.search.openSearchPanel},
|
||||
{label: 'Go to line', keys: 'Mod+Alt+G', run: cm.search.gotoLine},
|
||||
{label: 'Select All', keys: 'Mod+A', run: cm.commands.selectAll},
|
||||
{label: 'Delete Line', keys: 'Mod+Shift+K', run: cm.commands.deleteLine},
|
||||
{label: 'Move Line Up', keys: 'Alt+Up', run: cm.commands.moveLineUp},
|
||||
{label: 'Move Line Down', keys: 'Alt+Down', run: cm.commands.moveLineDown},
|
||||
{label: 'Copy Line Up', keys: 'Shift+Alt+Up', run: cm.commands.copyLineUp},
|
||||
{label: 'Copy Line Down', keys: 'Shift+Alt+Down', run: cm.commands.copyLineDown},
|
||||
{label: 'Toggle Comment', keys: 'Mod+/', run: cm.commands.toggleComment},
|
||||
{label: 'Insert Blank Line', keys: 'Mod+Enter', run: cm.commands.insertBlankLine},
|
||||
{label: 'Add Cursor Above', keys: isMac ? 'Mod+Alt+Up' : 'Ctrl+Alt+Up', run: cm.commands.addCursorAbove},
|
||||
{label: 'Add Cursor Below', keys: isMac ? 'Mod+Alt+Down' : 'Ctrl+Alt+Down', run: cm.commands.addCursorBelow},
|
||||
{label: 'Add Next Occurrence', keys: 'Mod+D', run: cm.search.selectNextOccurrence},
|
||||
{label: 'Go to Matching Bracket', keys: 'Mod+Shift+\\', run: cm.commands.cursorMatchingBracket},
|
||||
{label: 'Indent More', keys: 'Mod+]', run: cm.commands.indentMore},
|
||||
{label: 'Indent Less', keys: 'Mod+[', run: cm.commands.indentLess},
|
||||
{label: 'Fold Code', keys: isMac ? 'Mod+Alt+[' : 'Ctrl+Shift+[', run: cm.language.foldCode},
|
||||
{label: 'Unfold Code', keys: isMac ? 'Mod+Alt+]' : 'Ctrl+Shift+]', run: cm.language.unfoldCode},
|
||||
{label: 'Fold All', keys: 'Ctrl+Alt+[', run: cm.language.foldAll},
|
||||
{label: 'Unfold All', keys: 'Ctrl+Alt+]', run: cm.language.unfoldAll},
|
||||
{label: 'Trigger Autocomplete', keys: 'Ctrl+Space', run: cm.autocomplete.startCompletion},
|
||||
{label: 'Trim Trailing Whitespace', keys: 'Mod+K Mod+X', run: trimTrailingWhitespaceFromView},
|
||||
];
|
||||
|
||||
let overlay: HTMLElement | null = null;
|
||||
let filtered: PaletteCommand[] = [];
|
||||
let selectedIndex = 0;
|
||||
let cleanupClickOutside: (() => void) | null = null;
|
||||
|
||||
function hide(view: EditorView) {
|
||||
if (!overlay) return;
|
||||
cleanupClickOutside?.();
|
||||
cleanupClickOutside = null;
|
||||
overlay.remove();
|
||||
overlay = null;
|
||||
view.focus();
|
||||
}
|
||||
|
||||
function renderList(list: HTMLElement, query: string) {
|
||||
list.textContent = '';
|
||||
if (!filtered.length) {
|
||||
const empty = document.createElement('div');
|
||||
empty.className = 'cm-command-palette-empty';
|
||||
empty.textContent = 'No matches';
|
||||
list.append(empty);
|
||||
return;
|
||||
}
|
||||
for (const [index, cmd] of filtered.entries()) {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'cm-command-palette-item';
|
||||
item.setAttribute('role', 'option');
|
||||
item.setAttribute('data-index', String(index));
|
||||
if (index === selectedIndex) item.setAttribute('aria-selected', 'true');
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'cm-command-palette-label';
|
||||
const matchIndex = query ? cmd.label.toLowerCase().indexOf(query) : -1;
|
||||
if (matchIndex !== -1) {
|
||||
label.append(cmd.label.slice(0, matchIndex));
|
||||
const mark = document.createElement('mark');
|
||||
mark.textContent = cmd.label.slice(matchIndex, matchIndex + query.length);
|
||||
label.append(mark, cmd.label.slice(matchIndex + query.length));
|
||||
} else {
|
||||
label.textContent = cmd.label;
|
||||
}
|
||||
item.append(label);
|
||||
|
||||
if (cmd.keys) {
|
||||
const keysEl = document.createElement('span');
|
||||
keysEl.className = 'cm-command-palette-keys';
|
||||
for (const [chordIndex, chord] of formatKeys(cmd.keys).entries()) {
|
||||
if (chordIndex > 0) keysEl.append('→');
|
||||
for (const k of chord) {
|
||||
const kbd = document.createElement('kbd');
|
||||
kbd.textContent = k;
|
||||
keysEl.append(kbd);
|
||||
}
|
||||
}
|
||||
item.append(keysEl);
|
||||
}
|
||||
list.append(item);
|
||||
}
|
||||
}
|
||||
|
||||
function show(view: EditorView, items?: PaletteCommand[], placeholder?: string) {
|
||||
const container = view.dom.closest('.code-editor-container')!;
|
||||
overlay = document.createElement('div');
|
||||
overlay.className = 'cm-command-palette';
|
||||
|
||||
const input = document.createElement('input');
|
||||
input.className = 'cm-command-palette-input';
|
||||
input.placeholder = placeholder || 'Type a command...';
|
||||
|
||||
const list = document.createElement('div');
|
||||
list.className = 'cm-command-palette-list';
|
||||
list.setAttribute('role', 'listbox');
|
||||
|
||||
const source = items || commands;
|
||||
filtered = source;
|
||||
selectedIndex = 0;
|
||||
|
||||
const updateSelected = () => {
|
||||
list.querySelector('[aria-selected]')?.removeAttribute('aria-selected');
|
||||
const el = list.children[selectedIndex] as HTMLElement | undefined;
|
||||
if (el) {
|
||||
el.setAttribute('aria-selected', 'true');
|
||||
if (el.offsetTop < list.scrollTop) {
|
||||
list.scrollTop = el.offsetTop;
|
||||
} else if (el.offsetTop + el.offsetHeight > list.scrollTop + list.clientHeight) {
|
||||
list.scrollTop = el.offsetTop + el.offsetHeight - list.clientHeight;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const execute = (cmd: PaletteCommand) => {
|
||||
hide(view);
|
||||
cmd.run(view);
|
||||
};
|
||||
|
||||
list.addEventListener('pointerover', (e) => {
|
||||
const item = (e.target as Element).closest<HTMLElement>('.cm-command-palette-item');
|
||||
if (!item) return;
|
||||
selectedIndex = Number(item.getAttribute('data-index'));
|
||||
updateSelected();
|
||||
});
|
||||
|
||||
list.addEventListener('mousedown', (e) => {
|
||||
const item = (e.target as Element).closest<HTMLElement>('.cm-command-palette-item');
|
||||
if (!item) return;
|
||||
e.preventDefault();
|
||||
const cmd = filtered[Number(item.getAttribute('data-index'))];
|
||||
if (cmd) execute(cmd);
|
||||
});
|
||||
|
||||
input.addEventListener('input', () => {
|
||||
const q = input.value.toLowerCase();
|
||||
filtered = q ? source.filter((cmd) => cmd.label.toLowerCase().includes(q)) : source;
|
||||
selectedIndex = 0;
|
||||
renderList(list, q);
|
||||
});
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'ArrowDown') {
|
||||
e.preventDefault();
|
||||
selectedIndex = (selectedIndex + 1) % filtered.length;
|
||||
updateSelected();
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
e.preventDefault();
|
||||
selectedIndex = (selectedIndex - 1 + filtered.length) % filtered.length;
|
||||
updateSelected();
|
||||
} else if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
if (filtered[selectedIndex]) execute(filtered[selectedIndex]);
|
||||
} else if (e.key === 'Escape') {
|
||||
e.preventDefault();
|
||||
hide(view);
|
||||
}
|
||||
});
|
||||
|
||||
overlay.append(input, list);
|
||||
container.append(overlay);
|
||||
renderList(list, '');
|
||||
input.focus();
|
||||
|
||||
const handleClickOutside = (e: MouseEvent) => {
|
||||
const target = e.target as Element;
|
||||
if (overlay && !overlay.contains(target) && !target.closest('.js-code-command-palette')) {
|
||||
hide(view);
|
||||
}
|
||||
};
|
||||
document.addEventListener('mousedown', handleClickOutside);
|
||||
cleanupClickOutside = () => document.removeEventListener('mousedown', handleClickOutside);
|
||||
}
|
||||
|
||||
function showWithItems(view: EditorView, items: PaletteCommand[], placeholder: string) {
|
||||
if (overlay) hide(view);
|
||||
show(view, items, placeholder);
|
||||
}
|
||||
|
||||
function togglePalette(view: EditorView) {
|
||||
if (overlay) {
|
||||
hide(view);
|
||||
} else {
|
||||
show(view);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return {
|
||||
extensions: cm.view.keymap.of([
|
||||
{key: 'Mod-Shift-p', run: togglePalette, preventDefault: true},
|
||||
{key: 'F1', run: togglePalette, preventDefault: true},
|
||||
]),
|
||||
togglePalette,
|
||||
showWithItems,
|
||||
};
|
||||
}
|
||||
249
web_src/js/modules/codeeditor/context-menu.ts
Normal file
249
web_src/js/modules/codeeditor/context-menu.ts
Normal file
@@ -0,0 +1,249 @@
|
||||
import {clippie} from 'clippie';
|
||||
import {createTippy} from '../tippy.ts';
|
||||
import {keySymbols} from '../../utils.ts';
|
||||
import {goToDefinitionAt} from './utils.ts';
|
||||
import type {Instance} from 'tippy.js';
|
||||
import type {EditorView} from '@codemirror/view';
|
||||
import type {CodemirrorModules} from './main.ts';
|
||||
|
||||
type MenuItem = {
|
||||
label: string;
|
||||
keys?: string;
|
||||
disabled?: boolean;
|
||||
run: (view: EditorView) => void | Promise<void>;
|
||||
} | 'separator';
|
||||
|
||||
/** Get the word at cursor, or selected text. Checks adjacent positions when cursor is on a non-word char. */
|
||||
export function getWordAtPosition(view: EditorView, from: number, to: number): string {
|
||||
if (from !== to) return view.state.doc.sliceString(from, to);
|
||||
for (const pos of [from, from - 1, from + 1]) {
|
||||
const range = view.state.wordAt(pos);
|
||||
if (range) return view.state.doc.sliceString(range.from, range.to);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
/** Select all occurrences of the word at cursor for multi-cursor editing. */
|
||||
export function selectAllOccurrences(cm: CodemirrorModules, view: EditorView) {
|
||||
const {from, to} = view.state.selection.main;
|
||||
const word = getWordAtPosition(view, from, to);
|
||||
if (!word) return;
|
||||
const ranges = [];
|
||||
let main = 0;
|
||||
const cursor = new cm.search.SearchCursor(view.state.doc, word);
|
||||
while (!cursor.done) {
|
||||
cursor.next();
|
||||
if (cursor.done) break;
|
||||
if (cursor.value.from <= from && cursor.value.to >= from) main = ranges.length;
|
||||
ranges.push(cm.state.EditorSelection.range(cursor.value.from, cursor.value.to));
|
||||
}
|
||||
if (ranges.length) {
|
||||
view.dispatch({selection: cm.state.EditorSelection.create(ranges, main)});
|
||||
}
|
||||
}
|
||||
|
||||
/** Collect symbol definitions from the Lezer syntax tree. */
|
||||
export function collectSymbols(cm: CodemirrorModules, view: EditorView): {label: string; kind: string; from: number}[] {
|
||||
const tree = cm.language.syntaxTree(view.state);
|
||||
const symbols: {label: string; kind: string; from: number}[] = [];
|
||||
const seen = new Set<number>(); // track by position to avoid O(n²) dedup
|
||||
const addSymbol = (label: string, kind: string, from: number) => {
|
||||
if (!seen.has(from)) {
|
||||
seen.add(from);
|
||||
symbols.push({label, kind, from});
|
||||
}
|
||||
};
|
||||
tree.iterate({
|
||||
enter(node): false | void {
|
||||
if (node.name === 'VariableDefinition' || node.name === 'DefName') {
|
||||
addSymbol(view.state.doc.sliceString(node.from, node.to), 'variable', node.from);
|
||||
} else if (node.name === 'FunctionDeclaration' || node.name === 'FunctionDecl' || node.name === 'ClassDeclaration') {
|
||||
const nameNode = node.node.getChild('VariableDefinition') || node.node.getChild('DefName');
|
||||
if (nameNode) {
|
||||
const kind = node.name === 'ClassDeclaration' ? 'class' : 'function';
|
||||
addSymbol(view.state.doc.sliceString(nameNode.from, nameNode.to), kind, nameNode.from);
|
||||
}
|
||||
return false;
|
||||
} else if (node.name === 'MethodDeclaration' || node.name === 'MethodDecl' || node.name === 'PropertyDefinition') {
|
||||
const nameNode = node.node.getChild('PropertyDefinition') || node.node.getChild('PropertyName') || node.node.getChild('DefName');
|
||||
if (nameNode) {
|
||||
addSymbol(view.state.doc.sliceString(nameNode.from, nameNode.to), node.name === 'PropertyDefinition' ? 'property' : 'method', nameNode.from);
|
||||
}
|
||||
} else if (node.name === 'TypeDecl' || node.name === 'TypeSpec') {
|
||||
const nameNode = node.node.getChild('DefName');
|
||||
if (nameNode) {
|
||||
addSymbol(view.state.doc.sliceString(nameNode.from, nameNode.to), 'type', nameNode.from);
|
||||
}
|
||||
}
|
||||
},
|
||||
});
|
||||
return symbols;
|
||||
}
|
||||
|
||||
function buildMenuItems(cm: CodemirrorModules, view: EditorView, togglePalette: (view: EditorView) => boolean, goToSymbol: (view: EditorView) => void): MenuItem[] {
|
||||
const {from, to} = view.state.selection.main;
|
||||
const hasSelection = from !== to;
|
||||
// Check if cursor is on a symbol that has a definition
|
||||
const tree = cm.language.syntaxTree(view.state);
|
||||
const nodeAtCursor = tree.resolveInner(from, 1);
|
||||
const hasDefinition = nodeAtCursor?.name === 'VariableName';
|
||||
const hasWord = Boolean(getWordAtPosition(view, from, to));
|
||||
return [
|
||||
{label: 'Go to Definition', keys: 'F12', disabled: !hasDefinition, run: (v) => { goToDefinitionAt(cm, v, v.state.selection.main.from) }},
|
||||
{label: 'Go to Symbol…', keys: 'Mod+Shift+O', run: goToSymbol},
|
||||
{label: 'Change All Occurrences', keys: 'Mod+F2', disabled: !hasWord, run: (v) => selectAllOccurrences(cm, v)},
|
||||
'separator',
|
||||
{label: 'Cut', keys: 'Mod+X', disabled: !hasSelection, run: async (v) => {
|
||||
const {from, to} = v.state.selection.main;
|
||||
if (await clippie(v.state.doc.sliceString(from, to))) {
|
||||
v.dispatch({changes: {from, to}});
|
||||
}
|
||||
}},
|
||||
{label: 'Copy', keys: 'Mod+C', disabled: !hasSelection, run: async (v) => {
|
||||
const {from, to} = v.state.selection.main;
|
||||
await clippie(v.state.doc.sliceString(from, to));
|
||||
}},
|
||||
{label: 'Paste', keys: 'Mod+V', run: async (view) => {
|
||||
try {
|
||||
const text = await navigator.clipboard.readText();
|
||||
view.dispatch(view.state.replaceSelection(text));
|
||||
} catch { /* clipboard permission denied */ }
|
||||
}},
|
||||
'separator',
|
||||
{label: 'Command Palette', keys: 'F1', run: (v) => { togglePalette(v) }},
|
||||
];
|
||||
}
|
||||
|
||||
type MenuResult = {el: HTMLElement; actions: ((() => void) | null)[]};
|
||||
|
||||
function createMenuElement(items: MenuItem[], view: EditorView, onAction: () => void): MenuResult {
|
||||
const menu = document.createElement('div');
|
||||
menu.className = 'cm-context-menu';
|
||||
const actions: ((() => void) | null)[] = [];
|
||||
for (const item of items) {
|
||||
if (item === 'separator') {
|
||||
const sep = document.createElement('div');
|
||||
sep.className = 'cm-context-menu-separator';
|
||||
menu.append(sep);
|
||||
continue;
|
||||
}
|
||||
const row = document.createElement('div');
|
||||
row.className = `item${item.disabled ? ' disabled' : ''}`;
|
||||
if (item.disabled) row.setAttribute('aria-disabled', 'true');
|
||||
const label = document.createElement('span');
|
||||
label.className = 'cm-context-menu-label';
|
||||
label.textContent = item.label;
|
||||
row.append(label);
|
||||
if (item.keys) {
|
||||
const keysEl = document.createElement('span');
|
||||
keysEl.className = 'cm-context-menu-keys';
|
||||
for (const key of item.keys.split('+')) {
|
||||
const kbd = document.createElement('kbd');
|
||||
kbd.textContent = keySymbols[key] || key;
|
||||
keysEl.append(kbd);
|
||||
}
|
||||
row.append(keysEl);
|
||||
}
|
||||
const execute = item.disabled ? null : () => { onAction(); item.run(view) };
|
||||
if (execute) {
|
||||
row.addEventListener('mousedown', (e) => { e.preventDefault(); e.stopPropagation(); execute() });
|
||||
}
|
||||
actions.push(execute);
|
||||
menu.append(row);
|
||||
}
|
||||
return {el: menu, actions};
|
||||
}
|
||||
|
||||
export function contextMenu(cm: CodemirrorModules, togglePalette: (view: EditorView) => boolean, goToSymbol: (view: EditorView) => void) {
|
||||
let instance: Instance | null = null;
|
||||
|
||||
function hideMenu() {
|
||||
if (instance) {
|
||||
instance.destroy();
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
|
||||
return cm.view.EditorView.domEventHandlers({
|
||||
contextmenu(event: MouseEvent, view: EditorView) {
|
||||
event.preventDefault();
|
||||
hideMenu();
|
||||
|
||||
// Place cursor at right-click position if not inside a selection
|
||||
const pos = view.posAtCoords({x: event.clientX, y: event.clientY});
|
||||
if (pos !== null) {
|
||||
const {from, to} = view.state.selection.main;
|
||||
if (pos < from || pos > to) {
|
||||
view.dispatch({selection: {anchor: pos}});
|
||||
}
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
const dismiss = () => {
|
||||
controller.abort();
|
||||
hideMenu();
|
||||
};
|
||||
|
||||
const menuItems = buildMenuItems(cm, view, togglePalette, goToSymbol);
|
||||
const {el: menuEl, actions} = createMenuElement(menuItems, view, dismiss);
|
||||
|
||||
// Create a virtual anchor at mouse position for tippy
|
||||
const anchor = document.createElement('div');
|
||||
anchor.style.position = 'fixed';
|
||||
anchor.style.left = `${event.clientX}px`;
|
||||
anchor.style.top = `${event.clientY}px`;
|
||||
document.body.append(anchor);
|
||||
|
||||
instance = createTippy(anchor, {
|
||||
content: menuEl,
|
||||
theme: 'menu',
|
||||
trigger: 'manual',
|
||||
placement: 'bottom-start',
|
||||
interactive: true,
|
||||
arrow: false,
|
||||
offset: [0, 0],
|
||||
showOnCreate: true,
|
||||
onHidden: () => {
|
||||
anchor.remove();
|
||||
instance = null;
|
||||
},
|
||||
});
|
||||
const rows = menuEl.querySelectorAll<HTMLElement>('.item');
|
||||
let focusIndex = -1;
|
||||
const setFocus = (idx: number) => {
|
||||
focusIndex = idx;
|
||||
for (const [rowIdx, el] of rows.entries()) {
|
||||
el.classList.toggle('active', rowIdx === focusIndex);
|
||||
}
|
||||
};
|
||||
const nextEnabled = (from: number, dir: number) => {
|
||||
for (let step = 1; step <= actions.length; step++) {
|
||||
const idx = (from + dir * step + actions.length) % actions.length;
|
||||
if (actions[idx]) return idx;
|
||||
}
|
||||
return from;
|
||||
};
|
||||
|
||||
document.addEventListener('mousedown', (e: MouseEvent) => {
|
||||
if (!menuEl.contains(e.target as Element)) dismiss();
|
||||
}, {signal: controller.signal});
|
||||
document.addEventListener('keydown', (e: KeyboardEvent) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
if (e.key === 'Escape') {
|
||||
dismiss(); view.focus();
|
||||
} else if (e.key === 'ArrowDown') {
|
||||
setFocus(nextEnabled(focusIndex, 1));
|
||||
} else if (e.key === 'ArrowUp') {
|
||||
setFocus(nextEnabled(focusIndex, -1));
|
||||
} else if (e.key === 'Enter' && focusIndex >= 0 && actions[focusIndex]) {
|
||||
actions[focusIndex]!();
|
||||
}
|
||||
}, {signal: controller.signal, capture: true});
|
||||
view.scrollDOM.addEventListener('scroll', dismiss, {signal: controller.signal, once: true});
|
||||
document.addEventListener('scroll', dismiss, {signal: controller.signal, once: true});
|
||||
window.addEventListener('blur', dismiss, {signal: controller.signal});
|
||||
window.addEventListener('resize', dismiss, {signal: controller.signal});
|
||||
},
|
||||
});
|
||||
}
|
||||
39
web_src/js/modules/codeeditor/linter.ts
Normal file
39
web_src/js/modules/codeeditor/linter.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
import type {CodemirrorModules} from './main.ts';
|
||||
import type {Extension} from '@codemirror/state';
|
||||
|
||||
/** Creates a linter for JSON files using `jsonParseLinter` from `@codemirror/lang-json`. */
|
||||
export async function createJsonLinter(cm: CodemirrorModules): Promise<Extension> {
|
||||
const {jsonParseLinter} = await import(/* webpackChunkName: "codemirror" */ '@codemirror/lang-json');
|
||||
const baseLinter = jsonParseLinter();
|
||||
return cm.lint.linter((view) => {
|
||||
return baseLinter(view).map((d) => {
|
||||
if (d.from === d.to) {
|
||||
const line = view.state.doc.lineAt(d.from);
|
||||
// expand to end of line content, or at least 1 char
|
||||
d.to = Math.min(Math.max(d.from + 1, line.to), view.state.doc.length);
|
||||
}
|
||||
return d;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/** Creates a generic linter that detects Lezer parse-tree error nodes. */
|
||||
export function createSyntaxErrorLinter(cm: CodemirrorModules): Extension {
|
||||
return cm.lint.linter((view) => {
|
||||
const diagnostics: {from: number, to: number, severity: 'error', message: string}[] = [];
|
||||
const tree = cm.language.syntaxTree(view.state);
|
||||
tree.iterate({
|
||||
enter(node) {
|
||||
if (node.type.isError) {
|
||||
diagnostics.push({
|
||||
from: node.from,
|
||||
to: node.to === node.from ? Math.min(node.from + 1, view.state.doc.length) : node.to,
|
||||
severity: 'error',
|
||||
message: 'Syntax error',
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
return diagnostics;
|
||||
});
|
||||
}
|
||||
330
web_src/js/modules/codeeditor/main.ts
Normal file
330
web_src/js/modules/codeeditor/main.ts
Normal file
@@ -0,0 +1,330 @@
|
||||
import {extname} from '../../utils.ts';
|
||||
import {createElementFromHTML, toggleElem} from '../../utils/dom.ts';
|
||||
import {html, htmlRaw} from '../../utils/html.ts';
|
||||
import {svg} from '../../svg.ts';
|
||||
import {commandPalette} from './command-palette.ts';
|
||||
import type {PaletteCommand} from './command-palette.ts';
|
||||
import {contextMenu, collectSymbols, selectAllOccurrences} from './context-menu.ts';
|
||||
import {createJsonLinter, createSyntaxErrorLinter} from './linter.ts';
|
||||
import {clickableUrls, goToDefinitionAt, trimTrailingWhitespaceFromView} from './utils.ts';
|
||||
import type {LanguageDescription} from '@codemirror/language';
|
||||
import type {Compartment, Extension} from '@codemirror/state';
|
||||
import type {EditorView, ViewUpdate} from '@codemirror/view';
|
||||
|
||||
// CodeEditorConfig is also used by backend, defined in "editor_util.go"
|
||||
const codeEditorConfigDefault = {
|
||||
filename: '', // the current filename (base name, not full path), used for language detection
|
||||
autofocus: false, // whether to autofocus the editor on load
|
||||
previewableExtensions: [] as string[], // file extensions that support preview rendering
|
||||
lineWrapExtensions: [] as string[], // file extensions that enable line wrapping by default
|
||||
lineWrap: false, // whether line wrapping is enabled for the current file
|
||||
|
||||
indentStyle: '', // "space" or "tab", from .editorconfig, or empty for not specified (detect from source code)
|
||||
indentSize: 0, // number of spaces per indent level, from .editorconfig, or 0 for not specified (detect from source code)
|
||||
tabWidth: 4, // display width of a tab character, from .editorconfig, defaults to 4
|
||||
trimTrailingWhitespace: false, // whether to trim trailing whitespace on save, from .editorconfig
|
||||
};
|
||||
type CodeEditorConfig = typeof codeEditorConfigDefault;
|
||||
|
||||
export type CodemirrorEditor = {
|
||||
view: EditorView;
|
||||
trimTrailingWhitespace: boolean;
|
||||
togglePalette: (view: EditorView) => boolean;
|
||||
updateFilename: (filename: string) => Promise<void>;
|
||||
languages: LanguageDescription[];
|
||||
compartments: {
|
||||
wordWrap: Compartment;
|
||||
language: Compartment;
|
||||
tabSize: Compartment;
|
||||
indentUnit: Compartment;
|
||||
lint: Compartment;
|
||||
};
|
||||
};
|
||||
|
||||
export type CodemirrorModules = Awaited<ReturnType<typeof importCodemirror>>;
|
||||
|
||||
async function importCodemirror() {
|
||||
const [autocomplete, commands, language, languageData, lint, search, state, view, highlight, indentMarkers, vscodeKeymap] = await Promise.all([
|
||||
import(/* webpackChunkName: "codemirror" */ '@codemirror/autocomplete'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@codemirror/commands'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@codemirror/language'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@codemirror/language-data'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@codemirror/lint'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@codemirror/search'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@codemirror/state'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@codemirror/view'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@lezer/highlight'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@replit/codemirror-indentation-markers'),
|
||||
import(/* webpackChunkName: "codemirror" */ '@replit/codemirror-vscode-keymap'),
|
||||
]);
|
||||
return {autocomplete, commands, language, languageData, lint, search, state, view, highlight, indentMarkers, vscodeKeymap};
|
||||
}
|
||||
|
||||
function togglePreviewDisplay(previewable: boolean): void {
|
||||
// FIXME: here and below, the selector is too broad, it should only query in the editor related scope
|
||||
const previewTab = document.querySelector<HTMLElement>('a[data-tab="preview"]');
|
||||
// the "preview tab" exists for "file code editor", but doesn't exist for "git hook editor"
|
||||
if (!previewTab) return;
|
||||
|
||||
toggleElem(previewTab, previewable);
|
||||
if (previewable) return;
|
||||
|
||||
// If not previewable but 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<HTMLElement>('a[data-tab="write"]');
|
||||
writeTab!.click();
|
||||
}
|
||||
}
|
||||
|
||||
export async function createCodeEditor(textarea: HTMLTextAreaElement, filenameInput?: HTMLInputElement): Promise<CodemirrorEditor> {
|
||||
const config: CodeEditorConfig = {
|
||||
...codeEditorConfigDefault,
|
||||
...JSON.parse(textarea.getAttribute('data-code-editor-config')!),
|
||||
};
|
||||
const previewableExts = new Set(config.previewableExtensions || []);
|
||||
const lineWrapExts = config.lineWrapExtensions || [];
|
||||
const cm = await importCodemirror();
|
||||
|
||||
const languageDescriptions: LanguageDescription[] = [
|
||||
...cm.languageData.languages.filter((l: LanguageDescription) => l.name !== 'Markdown'),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Markdown', extensions: ['md', 'markdown', 'mkd'],
|
||||
load: async () => (await import('@codemirror/lang-markdown')).markdown({codeLanguages: languageDescriptions}),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Elixir', extensions: ['ex', 'exs'],
|
||||
load: async () => (await import('codemirror-lang-elixir')).elixir(),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Nix', extensions: ['nix'],
|
||||
load: async () => (await import('@replit/codemirror-lang-nix')).nix(),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Svelte', extensions: ['svelte'],
|
||||
load: async () => (await import('@replit/codemirror-lang-svelte')).svelte(),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Makefile', filename: /^(GNUm|M|m)akefile$/,
|
||||
load: async () => new cm.language.LanguageSupport(cm.language.StreamLanguage.define((await import('@codemirror/legacy-modes/mode/shell')).shell)),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'Dotenv', extensions: ['env'], filename: /^\.env(\..*)?$/,
|
||||
load: async () => new cm.language.LanguageSupport(cm.language.StreamLanguage.define((await import('@codemirror/legacy-modes/mode/shell')).shell)),
|
||||
}),
|
||||
cm.language.LanguageDescription.of({
|
||||
name: 'JSON5', extensions: ['json5', 'jsonc'],
|
||||
load: async () => (await import('@codemirror/lang-json')).json(),
|
||||
}),
|
||||
];
|
||||
const matchedLang = cm.language.LanguageDescription.matchFilename(languageDescriptions, config.filename);
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.className = 'code-editor-container';
|
||||
container.setAttribute('data-language', matchedLang?.name.toLowerCase() || '');
|
||||
// Replace the loading placeholder with the editor container in one operation
|
||||
// to avoid a flash where neither element is in the DOM.
|
||||
const loading = textarea.parentNode!.querySelector<HTMLElement>('.editor-loading');
|
||||
if (loading) {
|
||||
loading.replaceWith(container);
|
||||
} else {
|
||||
textarea.parentNode!.append(container);
|
||||
}
|
||||
|
||||
const loadedLang = matchedLang ? await matchedLang.load() : null;
|
||||
const wordWrap = new cm.state.Compartment();
|
||||
const language = new cm.state.Compartment();
|
||||
const tabSize = new cm.state.Compartment();
|
||||
const indentUnitComp = new cm.state.Compartment();
|
||||
const lintComp = new cm.state.Compartment();
|
||||
const palette = commandPalette(cm);
|
||||
|
||||
const goToSymbol = (view: EditorView) => {
|
||||
const symbols = collectSymbols(cm, view);
|
||||
const items: PaletteCommand[] = symbols.map((sym) => ({
|
||||
label: `${sym.label} (${sym.kind})`,
|
||||
keys: '',
|
||||
run: (v: EditorView) => v.dispatch({selection: {anchor: sym.from}, scrollIntoView: true}),
|
||||
}));
|
||||
palette.showWithItems(view, items, 'Go to symbol…');
|
||||
return true;
|
||||
};
|
||||
|
||||
const view = new cm.view.EditorView({
|
||||
doc: textarea.defaultValue, // use defaultValue to prevent browser from restoring form values on refresh
|
||||
parent: container,
|
||||
extensions: [
|
||||
cm.view.lineNumbers(),
|
||||
cm.language.codeFolding({
|
||||
placeholderDOM(_view: EditorView, onclick: (event: Event) => void) {
|
||||
const el = createElementFromHTML(html`<span class="cm-foldPlaceholder">${htmlRaw(svg('octicon-kebab-horizontal', 13))}</span>`);
|
||||
el.addEventListener('click', onclick);
|
||||
return el as unknown as HTMLElement;
|
||||
},
|
||||
}),
|
||||
cm.language.foldGutter({
|
||||
markerDOM(open: boolean) {
|
||||
return createElementFromHTML(svg(open ? 'octicon-chevron-down' : 'octicon-chevron-right', 13));
|
||||
},
|
||||
}),
|
||||
cm.view.highlightActiveLineGutter(),
|
||||
cm.view.highlightSpecialChars(),
|
||||
cm.view.highlightActiveLine(),
|
||||
cm.view.drawSelection(),
|
||||
cm.view.dropCursor(),
|
||||
cm.view.rectangularSelection(),
|
||||
cm.view.crosshairCursor(),
|
||||
cm.view.placeholder(textarea.placeholder),
|
||||
config.trimTrailingWhitespace ? cm.view.highlightTrailingWhitespace() : [],
|
||||
cm.search.search({top: true}),
|
||||
cm.search.highlightSelectionMatches(),
|
||||
cm.view.keymap.of([
|
||||
...cm.vscodeKeymap.vscodeKeymap,
|
||||
...cm.search.searchKeymap,
|
||||
...cm.lint.lintKeymap,
|
||||
cm.commands.indentWithTab,
|
||||
{key: 'Mod-k Mod-x', run: (view) => { trimTrailingWhitespaceFromView(view); return true }, preventDefault: true},
|
||||
{key: 'Mod-Enter', run: cm.commands.insertBlankLine, preventDefault: true},
|
||||
{key: 'Mod-k Mod-k', run: cm.commands.deleteToLineEnd, preventDefault: true},
|
||||
{key: 'Mod-k Mod-Backspace', run: cm.commands.deleteToLineStart, preventDefault: true},
|
||||
]),
|
||||
cm.state.EditorState.allowMultipleSelections.of(true),
|
||||
cm.language.indentOnInput(),
|
||||
cm.language.syntaxHighlighting(cm.highlight.classHighlighter),
|
||||
cm.language.bracketMatching(),
|
||||
indentUnitComp.of(
|
||||
cm.language.indentUnit.of(
|
||||
config.indentStyle === 'tab' ? '\t' : ' '.repeat(config.indentSize || 4),
|
||||
),
|
||||
),
|
||||
cm.autocomplete.closeBrackets(),
|
||||
cm.autocomplete.autocompletion(),
|
||||
cm.state.EditorState.languageData.of(() => [{autocomplete: cm.autocomplete.completeAnyWord}]),
|
||||
cm.indentMarkers.indentationMarkers({
|
||||
colors: {
|
||||
light: 'transparent',
|
||||
dark: 'transparent',
|
||||
activeLight: 'var(--color-secondary-dark-3)',
|
||||
activeDark: 'var(--color-secondary-dark-3)',
|
||||
},
|
||||
}),
|
||||
cm.commands.history(),
|
||||
palette.extensions,
|
||||
cm.view.keymap.of([
|
||||
{key: 'Mod-Shift-o', run: goToSymbol, preventDefault: true},
|
||||
{key: 'Mod-F2', run: (v) => { selectAllOccurrences(cm, v); return true }, preventDefault: true},
|
||||
{key: 'F12', run: (v) => goToDefinitionAt(cm, v, v.state.selection.main.from), preventDefault: true},
|
||||
]),
|
||||
contextMenu(cm, palette.togglePalette, goToSymbol),
|
||||
clickableUrls(cm),
|
||||
tabSize.of(cm.state.EditorState.tabSize.of(config.tabWidth || 4)),
|
||||
wordWrap.of(config.lineWrap ? cm.view.EditorView.lineWrapping : []),
|
||||
language.of(loadedLang ?? []),
|
||||
lintComp.of(await getLinterExtension(cm, config.filename, loadedLang)),
|
||||
cm.view.EditorView.updateListener.of((update: ViewUpdate) => {
|
||||
if (update.docChanged) {
|
||||
textarea.value = update.state.doc.toString();
|
||||
textarea.dispatchEvent(new Event('change')); // needed for jquery-are-you-sure
|
||||
}
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
const editor: CodemirrorEditor = {
|
||||
view,
|
||||
trimTrailingWhitespace: config.trimTrailingWhitespace,
|
||||
togglePalette: palette.togglePalette,
|
||||
updateFilename: async (filename: string) => {
|
||||
togglePreviewDisplay(previewableExts.has(extname(filename)));
|
||||
await updateEditorLanguage(cm, editor, filename, lineWrapExts);
|
||||
},
|
||||
languages: languageDescriptions,
|
||||
compartments: {wordWrap, language, tabSize, indentUnit: indentUnitComp, lint: lintComp},
|
||||
};
|
||||
|
||||
const elEditorOptions = textarea.closest('form')!.querySelector('.code-editor-options');
|
||||
if (elEditorOptions) {
|
||||
const indentStyleSelect = elEditorOptions.querySelector<HTMLSelectElement>('.js-indent-style-select')!;
|
||||
const indentSizeSelect = elEditorOptions.querySelector<HTMLSelectElement>('.js-indent-size-select')!;
|
||||
|
||||
const applyIndentSettings = (style: string, size: number) => {
|
||||
view.dispatch({
|
||||
effects: [
|
||||
indentUnitComp.reconfigure(cm.language.indentUnit.of(style === 'tab' ? '\t' : ' '.repeat(size))),
|
||||
tabSize.reconfigure(cm.state.EditorState.tabSize.of(size)),
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
indentStyleSelect.addEventListener('change', () => {
|
||||
applyIndentSettings(indentStyleSelect.value, Number(indentSizeSelect.value) || 4);
|
||||
});
|
||||
|
||||
indentSizeSelect.addEventListener('change', () => {
|
||||
applyIndentSettings(indentStyleSelect.value || 'space', Number(indentSizeSelect.value) || 4);
|
||||
});
|
||||
|
||||
elEditorOptions.querySelector('.js-code-find')!.addEventListener('click', () => {
|
||||
if (cm.search.searchPanelOpen(view.state)) {
|
||||
cm.search.closeSearchPanel(view);
|
||||
} else {
|
||||
cm.search.openSearchPanel(view);
|
||||
}
|
||||
});
|
||||
|
||||
elEditorOptions.querySelector('.js-code-command-palette')!.addEventListener('click', () => {
|
||||
palette.togglePalette(view);
|
||||
});
|
||||
|
||||
elEditorOptions.querySelector<HTMLSelectElement>('.js-line-wrap-select')!.addEventListener('change', (e) => {
|
||||
const target = e.target as HTMLSelectElement;
|
||||
view.dispatch({
|
||||
effects: wordWrap.reconfigure(target.value === 'on' ? cm.view.EditorView.lineWrapping : []),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
togglePreviewDisplay(previewableExts.has(extname(config.filename)));
|
||||
|
||||
if (config.autofocus) {
|
||||
editor.view.focus();
|
||||
} else if (filenameInput) {
|
||||
filenameInput.focus();
|
||||
}
|
||||
|
||||
return editor;
|
||||
}
|
||||
|
||||
// files that are JSONC despite having a .json extension
|
||||
const jsoncFilesRegex = /^([jt]sconfig.*|devcontainer)\.json$/;
|
||||
|
||||
async function getLinterExtension(cm: CodemirrorModules, filename: string, loadedLang: {language: unknown} | null): Promise<Extension> {
|
||||
const ext = extname(filename).toLowerCase();
|
||||
if (ext === '.json' || ext === '.map') {
|
||||
return jsoncFilesRegex.test(filename) ? [] : [cm.lint.lintGutter(), await createJsonLinter(cm)];
|
||||
}
|
||||
// StreamLanguage (legacy modes) don't produce Lezer error nodes
|
||||
if (!loadedLang || loadedLang.language instanceof cm.language.StreamLanguage) return [];
|
||||
return [cm.lint.lintGutter(), createSyntaxErrorLinter(cm)];
|
||||
}
|
||||
|
||||
async function updateEditorLanguage(cm: CodemirrorModules, editor: CodemirrorEditor, filename: string, lineWrapExts: string[]): Promise<void> {
|
||||
const {compartments, view, languages: editorLanguages} = editor;
|
||||
|
||||
const newLanguage = cm.language.LanguageDescription.matchFilename(editorLanguages, filename);
|
||||
const newLoadedLang = newLanguage ? await newLanguage.load() : null;
|
||||
view.dom.closest('.code-editor-container')!.setAttribute('data-language', newLanguage?.name.toLowerCase() || '');
|
||||
view.dispatch(
|
||||
{
|
||||
effects: [
|
||||
compartments.wordWrap.reconfigure(
|
||||
lineWrapExts.includes(extname(filename).toLowerCase()) ? cm.view.EditorView.lineWrapping : [],
|
||||
),
|
||||
compartments.language.reconfigure(newLoadedLang ?? []),
|
||||
compartments.lint.reconfigure(await getLinterExtension(cm, filename, newLoadedLang)),
|
||||
],
|
||||
},
|
||||
// clear stale diagnostics from the previous language on filename change
|
||||
cm.lint.setDiagnostics(view.state, []),
|
||||
);
|
||||
}
|
||||
47
web_src/js/modules/codeeditor/utils.test.ts
Normal file
47
web_src/js/modules/codeeditor/utils.test.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import {findUrlAtPosition, trimUrlPunctuation, urlRawRegex} from './utils.ts';
|
||||
|
||||
function matchUrls(text: string): string[] {
|
||||
return Array.from(text.matchAll(urlRawRegex), (m) => trimUrlPunctuation(m[0]));
|
||||
}
|
||||
|
||||
test('matchUrls', () => {
|
||||
expect(matchUrls('visit https://example.com for info')).toEqual(['https://example.com']);
|
||||
expect(matchUrls('see https://example.com.')).toEqual(['https://example.com']);
|
||||
expect(matchUrls('see https://example.com, and')).toEqual(['https://example.com']);
|
||||
expect(matchUrls('see https://example.com; and')).toEqual(['https://example.com']);
|
||||
expect(matchUrls('(https://example.com)')).toEqual(['https://example.com']);
|
||||
expect(matchUrls('"https://example.com"')).toEqual(['https://example.com']);
|
||||
expect(matchUrls('https://example.com/path?q=1&b=2#hash')).toEqual(['https://example.com/path?q=1&b=2#hash']);
|
||||
expect(matchUrls('https://example.com/path?q=1&b=2#hash.')).toEqual(['https://example.com/path?q=1&b=2#hash']);
|
||||
expect(matchUrls('https://x.co')).toEqual(['https://x.co']);
|
||||
expect(matchUrls('https://example.com/path_(wiki)')).toEqual(['https://example.com/path_(wiki)']);
|
||||
expect(matchUrls('https://en.wikipedia.org/wiki/Rust_(programming_language)')).toEqual(['https://en.wikipedia.org/wiki/Rust_(programming_language)']);
|
||||
expect(matchUrls('(https://en.wikipedia.org/wiki/Rust_(programming_language))')).toEqual(['https://en.wikipedia.org/wiki/Rust_(programming_language)']);
|
||||
expect(matchUrls('http://example.com')).toEqual(['http://example.com']);
|
||||
expect(matchUrls('no url here')).toEqual([]);
|
||||
expect(matchUrls('https://a.com and https://b.com')).toEqual(['https://a.com', 'https://b.com']);
|
||||
expect(matchUrls('[](https://www.npmjs.org/package/pkg)')).toEqual(['https://img.shields.io/npm/v/pkg.svg?style=flat', 'https://www.npmjs.org/package/pkg']);
|
||||
});
|
||||
|
||||
test('trimUrlPunctuation', () => {
|
||||
expect(trimUrlPunctuation('https://example.com.')).toEqual('https://example.com');
|
||||
expect(trimUrlPunctuation('https://example.com,')).toEqual('https://example.com');
|
||||
expect(trimUrlPunctuation('https://example.com;')).toEqual('https://example.com');
|
||||
expect(trimUrlPunctuation('https://example.com:')).toEqual('https://example.com');
|
||||
expect(trimUrlPunctuation("https://example.com'")).toEqual('https://example.com');
|
||||
expect(trimUrlPunctuation('https://example.com"')).toEqual('https://example.com');
|
||||
expect(trimUrlPunctuation('https://example.com.,;')).toEqual('https://example.com');
|
||||
expect(trimUrlPunctuation('https://example.com/path')).toEqual('https://example.com/path');
|
||||
expect(trimUrlPunctuation('https://example.com/path_(wiki)')).toEqual('https://example.com/path_(wiki)');
|
||||
expect(trimUrlPunctuation('https://example.com)')).toEqual('https://example.com');
|
||||
expect(trimUrlPunctuation('https://en.wikipedia.org/wiki/Rust_(lang))')).toEqual('https://en.wikipedia.org/wiki/Rust_(lang)');
|
||||
});
|
||||
|
||||
test('findUrlAtPosition', () => {
|
||||
const doc = 'visit https://example.com for info';
|
||||
expect(findUrlAtPosition(doc, 0)).toBeNull();
|
||||
expect(findUrlAtPosition(doc, 6)).toEqual('https://example.com');
|
||||
expect(findUrlAtPosition(doc, 15)).toEqual('https://example.com');
|
||||
expect(findUrlAtPosition(doc, 24)).toEqual('https://example.com');
|
||||
expect(findUrlAtPosition(doc, 25)).toBeNull();
|
||||
});
|
||||
131
web_src/js/modules/codeeditor/utils.ts
Normal file
131
web_src/js/modules/codeeditor/utils.ts
Normal file
@@ -0,0 +1,131 @@
|
||||
import type {EditorView, ViewUpdate} from '@codemirror/view';
|
||||
import type {CodemirrorModules} from './main.ts';
|
||||
|
||||
/** Remove trailing whitespace from all lines in the editor. */
|
||||
export function trimTrailingWhitespaceFromView(view: EditorView): void {
|
||||
const changes = [];
|
||||
const doc = view.state.doc;
|
||||
for (let i = 1; i <= doc.lines; i++) {
|
||||
const line = doc.line(i);
|
||||
const trimmed = line.text.replace(/\s+$/, '');
|
||||
if (trimmed.length < line.text.length) {
|
||||
changes.push({from: line.from + trimmed.length, to: line.to});
|
||||
}
|
||||
}
|
||||
if (changes.length) view.dispatch({changes});
|
||||
}
|
||||
|
||||
/** Matches URLs, excluding characters that are never valid unencoded in URLs per RFC 3986. */
|
||||
export const urlRawRegex = /\bhttps?:\/\/[^\s<>[\]]+/gi;
|
||||
|
||||
/** Strip trailing punctuation that is likely not part of the URL. */
|
||||
export function trimUrlPunctuation(url: string): string {
|
||||
url = url.replace(/[.,;:'"]+$/, '');
|
||||
// Strip trailing closing parens only if unbalanced (not part of the URL like Wikipedia links)
|
||||
while (url.endsWith(')') && (url.match(/\(/g) || []).length < (url.match(/\)/g) || []).length) {
|
||||
url = url.slice(0, -1);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
/** Find the URL at the given character position in a document string, or null if none. */
|
||||
export function findUrlAtPosition(doc: string, pos: number): string | null {
|
||||
for (const match of doc.matchAll(urlRawRegex)) {
|
||||
const url = trimUrlPunctuation(match[0]);
|
||||
if (match.index !== undefined && pos >= match.index && pos < match.index + url.length) {
|
||||
return url;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Lezer syntax tree node names for identifier usages and definitions across grammars
|
||||
const usageNodes = new Set(['VariableName', 'Identifier', 'TypeIdentifier', 'TypeName', 'FieldIdentifier']);
|
||||
const definitionNodes = new Set(['VariableDefinition', 'DefName', 'Definition', 'TypeDefinition', 'TypeDef']);
|
||||
|
||||
export function goToDefinitionAt(cm: CodemirrorModules, view: EditorView, pos: number): boolean {
|
||||
const tree = cm.language.syntaxTree(view.state);
|
||||
const node = tree.resolveInner(pos, 1);
|
||||
if (!node || !usageNodes.has(node.name)) return false;
|
||||
const name = view.state.doc.sliceString(node.from, node.to);
|
||||
let target: number | null = null;
|
||||
tree.iterate({
|
||||
enter(n): false | void {
|
||||
if (target !== null) return false;
|
||||
if (definitionNodes.has(n.name) && n.from !== node.from && view.state.doc.sliceString(n.from, n.to) === name) {
|
||||
target = n.from;
|
||||
return false;
|
||||
}
|
||||
},
|
||||
});
|
||||
if (target === null) return false;
|
||||
view.dispatch({selection: {anchor: target}, scrollIntoView: true});
|
||||
return true;
|
||||
}
|
||||
|
||||
/** CodeMirror extension that makes URLs clickable via Ctrl/Cmd+click. */
|
||||
export function clickableUrls(cm: CodemirrorModules) {
|
||||
const urlMark = cm.view.Decoration.mark({class: 'cm-url'});
|
||||
const urlDecorator = new cm.view.MatchDecorator({
|
||||
regexp: urlRawRegex,
|
||||
decorate: (add, from, _to, match) => {
|
||||
const trimmed = trimUrlPunctuation(match[0]);
|
||||
add(from, from + trimmed.length, urlMark);
|
||||
},
|
||||
});
|
||||
|
||||
const plugin = cm.view.ViewPlugin.fromClass(class {
|
||||
decorations: ReturnType<typeof urlDecorator.createDeco>;
|
||||
constructor(view: EditorView) {
|
||||
this.decorations = urlDecorator.createDeco(view);
|
||||
}
|
||||
update(update: ViewUpdate) {
|
||||
this.decorations = urlDecorator.updateDeco(update, this.decorations);
|
||||
}
|
||||
}, {decorations: (v) => v.decorations});
|
||||
|
||||
const handler = cm.view.EditorView.domEventHandlers({
|
||||
mousedown(event: MouseEvent, view: EditorView) {
|
||||
if (!event.metaKey && !event.ctrlKey) return false;
|
||||
const pos = view.posAtCoords({x: event.clientX, y: event.clientY});
|
||||
if (pos === null) return false;
|
||||
const line = view.state.doc.lineAt(pos);
|
||||
const url = findUrlAtPosition(line.text, pos - line.from);
|
||||
if (url) {
|
||||
window.open(url, '_blank', 'noopener');
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
// Fall back to go-to-definition: find the symbol at cursor and jump to its definition
|
||||
if (goToDefinitionAt(cm, view, pos)) {
|
||||
event.preventDefault();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
});
|
||||
|
||||
const modClass = cm.view.ViewPlugin.fromClass(class {
|
||||
container: Element;
|
||||
handleKeyDown: (e: KeyboardEvent) => void;
|
||||
handleKeyUp: (e: KeyboardEvent) => void;
|
||||
handleBlur: () => void;
|
||||
constructor(view: EditorView) {
|
||||
this.container = view.dom.closest('.code-editor-container')!;
|
||||
this.handleKeyDown = (e) => { if (e.key === 'Meta' || e.key === 'Control') this.container.classList.add('cm-mod-held'); };
|
||||
this.handleKeyUp = (e) => { if (e.key === 'Meta' || e.key === 'Control') this.container.classList.remove('cm-mod-held'); };
|
||||
this.handleBlur = () => this.container.classList.remove('cm-mod-held');
|
||||
document.addEventListener('keydown', this.handleKeyDown);
|
||||
document.addEventListener('keyup', this.handleKeyUp);
|
||||
window.addEventListener('blur', this.handleBlur);
|
||||
}
|
||||
destroy() {
|
||||
document.removeEventListener('keydown', this.handleKeyDown);
|
||||
document.removeEventListener('keyup', this.handleKeyUp);
|
||||
window.removeEventListener('blur', this.handleBlur);
|
||||
this.container.classList.remove('cm-mod-held');
|
||||
}
|
||||
});
|
||||
|
||||
return [plugin, handler, modClass];
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
import {showGlobalErrorMessage, shouldIgnoreError} from './errors.ts';
|
||||
import {showGlobalErrorMessage} from './errors.ts';
|
||||
|
||||
test('showGlobalErrorMessage', () => {
|
||||
document.body.innerHTML = '<div class="page-content"></div>';
|
||||
@@ -10,19 +10,3 @@ test('showGlobalErrorMessage', () => {
|
||||
expect(document.body.innerHTML).toContain('>test msg 2<');
|
||||
expect(document.querySelectorAll('.js-global-error').length).toEqual(2);
|
||||
});
|
||||
|
||||
test('shouldIgnoreError', () => {
|
||||
for (const url of [
|
||||
'https://gitea.test/assets/js/monaco.D14TzjS9.js',
|
||||
'https://gitea.test/assets/js/editor.api2.BdhK7zNg.js',
|
||||
'https://gitea.test/assets/js/editor.worker.BYgvyFya.js',
|
||||
]) {
|
||||
const err = new Error('test');
|
||||
err.stack = `Error: test\n at ${url}:1:1`;
|
||||
expect(shouldIgnoreError(err)).toEqual(true);
|
||||
}
|
||||
|
||||
const otherError = new Error('test');
|
||||
otherError.stack = 'Error: test\n at https://gitea.test/assets/js/index.js:1:1';
|
||||
expect(shouldIgnoreError(otherError)).toEqual(false);
|
||||
});
|
||||
|
||||
@@ -23,19 +23,6 @@ export function showGlobalErrorMessage(msg: string, msgType: Intent = 'error') {
|
||||
msgContainer.prepend(msgDiv);
|
||||
}
|
||||
|
||||
export function shouldIgnoreError(err: Error) {
|
||||
const ignorePatterns: Array<RegExp> = [
|
||||
// https://github.com/go-gitea/gitea/issues/30861
|
||||
// https://github.com/microsoft/monaco-editor/issues/4496
|
||||
// https://github.com/microsoft/monaco-editor/issues/4679
|
||||
/\/assets\/js\/.*(monaco|editor\.(api|worker))/,
|
||||
];
|
||||
for (const pattern of ignorePatterns) {
|
||||
if (pattern.test(err.stack ?? '')) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
export function processWindowErrorEvent({error, reason, message, type, filename, lineno, colno}: ErrorEvent & PromiseRejectionEvent) {
|
||||
const err = error ?? reason;
|
||||
const assetBaseUrl = String(new URL(`${window.config?.assetUrlPrefix ?? '/assets'}/`, window.location.origin));
|
||||
@@ -51,13 +38,9 @@ export function processWindowErrorEvent({error, reason, message, type, filename,
|
||||
if (runModeIsProd) return;
|
||||
}
|
||||
|
||||
if (err instanceof Error) {
|
||||
// If the error stack trace does not include the base URL of our script assets, it likely came
|
||||
// from a browser extension or inline script. Do not show such errors in production.
|
||||
if (!err.stack?.includes(assetBaseUrl) && runModeIsProd) return;
|
||||
// Ignore some known errors that are unable to fix
|
||||
if (shouldIgnoreError(err)) return;
|
||||
}
|
||||
// If the error stack trace does not include the base URL of our script assets, it likely came
|
||||
// from a browser extension or inline script. Do not show such errors in production.
|
||||
if (err instanceof Error && !err.stack?.includes(assetBaseUrl) && runModeIsProd) return;
|
||||
|
||||
let msg = err?.message ?? message;
|
||||
if (lineno) msg += ` (${filename} @ ${lineno}:${colno})`;
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker';
|
||||
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker';
|
||||
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker';
|
||||
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker';
|
||||
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker';
|
||||
|
||||
window.MonacoEnvironment = {
|
||||
getWorker(_: string, label: string) {
|
||||
if (label === 'json') return new jsonWorker();
|
||||
if (label === 'css' || label === 'scss' || label === 'less') return new cssWorker();
|
||||
if (label === 'html' || label === 'handlebars' || label === 'razor') return new htmlWorker();
|
||||
if (label === 'typescript' || label === 'javascript') return new tsWorker();
|
||||
return new editorWorker();
|
||||
},
|
||||
};
|
||||
|
||||
export * from 'monaco-editor';
|
||||
@@ -10,12 +10,17 @@ function initShortcutKbd(kbd: HTMLElement) {
|
||||
kbd.setAttribute('aria-keyshortcuts', kbd.getAttribute('data-shortcut-keys')!);
|
||||
}
|
||||
|
||||
function shortcutWrapper(el: HTMLElement): HTMLElement | null {
|
||||
const parent = el.parentElement;
|
||||
return parent?.matches('.global-shortcut-wrapper') ? parent : null;
|
||||
}
|
||||
|
||||
function elemFromKbd(kbd: HTMLElement): HTMLInputElement | HTMLTextAreaElement | null {
|
||||
return kbd.parentElement!.querySelector<HTMLInputElement>('input, textarea') || null;
|
||||
return shortcutWrapper(kbd)?.querySelector<HTMLInputElement>('input, textarea') || null;
|
||||
}
|
||||
|
||||
function kbdFromElem(input: HTMLElement): HTMLElement | null {
|
||||
return input.parentElement!.querySelector<HTMLElement>('kbd') || null;
|
||||
return shortcutWrapper(input)?.querySelector<HTMLElement>('kbd') || null;
|
||||
}
|
||||
|
||||
export function initGlobalShortcut() {
|
||||
|
||||
@@ -27,6 +27,14 @@ export function isObject<T = Record<string, any>>(obj: any): obj is T {
|
||||
return Object.prototype.toString.call(obj) === '[object Object]';
|
||||
}
|
||||
|
||||
/** Whether the current platform is macOS or iOS. */
|
||||
export const isMac = /Mac/i.test(navigator.userAgent);
|
||||
|
||||
/** Platform-aware display symbols for keyboard modifier and special keys. */
|
||||
export const keySymbols: Record<string, string> = isMac ?
|
||||
{Mod: '⌘', Alt: '⌥', Shift: '⇧', Ctrl: '⌃', Up: '↑', Down: '↓', Enter: '⏎'} :
|
||||
{Mod: 'Ctrl', Shift: 'Shift', Alt: 'Alt', Up: '↑', Down: '↓', Enter: '⏎'};
|
||||
|
||||
/** returns whether a dark theme is enabled */
|
||||
export function isDarkTheme(): boolean {
|
||||
const style = window.getComputedStyle(document.documentElement);
|
||||
|
||||
Reference in New Issue
Block a user