Skip to content

Conversation

@bennothommo
Copy link
Member

@bennothommo bennothommo commented Dec 13, 2022

Fixes #431. Related: wintercms/wn-builder-plugin#73
Screenshot 2026-01-02 at 1 02 11 AM
Screenshot 2026-01-02 at 1 02 40 AM
Screenshot 2026-01-02 at 1 03 25 AM

This PR introduces the Monaco editor in replacement of the Ace Editor. At this stage for the PR, we are aiming for parity with the functionality previously provided by Ace, with the intention of increasing the functionality after this PR is merged.

Still to do:

  • Support all prior configuration options.
  • Re-implement all previous themes, or find suitable alternatives (see https://github.com/brijeshb42/monaco-themes)
  • Ensure code editor works in all current core locations (settings areas, CMS editor, Builder plugin)
  • Try and figure out how to remove the automated loading of the Codicon font, as it's being implemented in the widget's CSS assets.

Changes:

  • Auto-completing tags preference is no longer available, as Monaco doesn't have this as an option.
  • Autocompletion and code snippets preferences are no longer available, as these are more contextual and will be implemented based on what is being edited in the editor.
  • Code folding is now a simple boolean.
  • The Crimson Editor theme has been dropped - I couldn't find a complete replica of this theme, and it looks too similar to other themes in the list.

Summary by CodeRabbit

  • New Features

    • Upgraded code editor with enhanced syntax highlighting and language support
    • Added multiple editor themes (25+ new themes including One Dark Pro)
    • New editor preferences UI for font size, themes, minimap, bracket coloring, and more
    • Improved status bar with fullscreen toggle and position indicators
    • Better language-specific code completion and editor capabilities
  • Style

    • Modernized editor toolbar and layout styling
    • Updated icon system for editor controls

✏️ Tip: You can customize this high-level summary in your review settings.

@bennothommo bennothommo added enhancement PRs that implement a new feature or substantial change Status: In Progress labels Dec 13, 2022
@bennothommo bennothommo added this to the v1.2.2 milestone Dec 13, 2022
@what-the-diff

This comment was marked as outdated.

@coderabbitai

This comment was marked as resolved.

@coderabbitai

This comment was marked as resolved.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 12

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
modules/backend/assets/ui/js/ajax/Handler.js (1)

40-54: Critical: Duplicate event registrations will cause double render events.

The ready() method has accidental code duplication:

  1. delegate('render') is called twice (lines 40 and 43)
  2. The $render listener is registered twice (lines 44-46 and 51-54)

This will cause the render Snowboard global event to fire multiple times per render cycle, potentially triggering widget double-initialization and degraded performance.

🔎 Proposed fix to remove duplicate registrations
     ready() {
         if (!window.jQuery) {
             return;
         }
-        delegate('render');
 
         // Add global event for rendering in Snowboard
         delegate('render');
         document.addEventListener('$render', () => {
             this.snowboard.globalEvent('render');
         });
 
         // Add "render" event for backwards compatibility
         window.jQuery(document).trigger('render');
-
-        // Add global event for rendering in Snowboard
-        document.addEventListener('$render', () => {
-            this.snowboard.globalEvent('render');
-        });
     }
♻️ Duplicate comments (1)
modules/backend/formwidgets/codeeditor/assets/less/codeeditor.less (1)

87-109: Codicon CSS inclusion is acceptable given Monaco's architecture.

Per the previous discussion, the codicon CSS is bundled inline because Monaco doesn't export it as a separate importable module. The MIT license header is correctly preserved.

One consideration: this adds ~520 lines of icon definitions. If only a subset of icons is used, a future optimization could trim unused glyphs, though this may not be worth the maintenance overhead given these are stable definitions.

🧹 Nitpick comments (14)
modules/backend/formwidgets/codeeditor/assets/less/codeeditor.less (1)

23-28: Consider using flex shorthand for brevity.

The flex-grow and flex-shrink properties can be combined using the flex shorthand, which is more concise and commonly used.

🔎 Suggested simplification
     .editor-container {
-        flex-grow: 1;
-        flex-shrink: 1;
+        flex: 1 1 auto;
         height: 100%;
         width: 100%;
     }
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/744.js (2)

1-8: Consider excluding generated bundles from source control.

This appears to be a minified webpack bundle output (note the /build/ directory and minified content). Committing generated/built artifacts can increase repository size, cause merge conflicts, and complicate code reviews.

Consider:

  • Adding modules/backend/formwidgets/codeeditor/assets/js/build/ to .gitignore
  • Documenting the build process for generating these bundles
  • Building assets during the deployment/CI pipeline

If there's a specific reason to commit built assets (e.g., simplified deployment without build tools), please document this decision.


2-7: Update Monaco Editor to current version.

The bundle includes Monaco Editor v0.34.1 from 2022. The current stable version is 0.55.1 (November 2025). While no security vulnerabilities have been reported for v0.34.1, updating to the latest version is recommended to benefit from 3+ years of bug fixes and improvements before merging.

modules/backend/controllers/preferences/_field_editor_preview_lang.php (1)

1-8: Consider using href="javascript:void(0)" or role="button" for non-navigating links.

Using href="#" can cause the page to scroll to the top when clicked (if JavaScript fails or before it captures the event). Consider using href="javascript:void(0)" or adding role="button" with a proper event handler pattern to improve UX.

Additionally, the inline style margin-top: -15px could be moved to a CSS class for better maintainability.

🔎 Suggested improvement
-<p class="help-block" style="margin-top: -15px">
+<p class="help-block preview-lang-switcher">
     Preview language:
-    <a href="#" data-switch-lang="css">CSS</a> |
-    <a href="#" data-switch-lang="html">HTML</a> |
-    <a href="#" data-switch-lang="javascript">JavaScript</a> |
-    <a href="#" data-switch-lang="twig">Twig</a> |
-    <a href="#" data-switch-lang="php">PHP</a>
+    <a href="javascript:void(0)" data-switch-lang="css">CSS</a> |
+    <a href="javascript:void(0)" data-switch-lang="html">HTML</a> |
+    <a href="javascript:void(0)" data-switch-lang="javascript">JavaScript</a> |
+    <a href="javascript:void(0)" data-switch-lang="twig">Twig</a> |
+    <a href="javascript:void(0)" data-switch-lang="php">PHP</a>
 </p>
modules/backend/formwidgets/codeeditor/assets/themes/github.tmTheme (2)

163-175: Duplicate theme rule detected.

This "Built-in constant" rule for scope constant.language (lines 163-175) duplicates an identical rule defined earlier at lines 140-151. While this doesn't break functionality (the later rule simply takes precedence), one of these entries could be removed for cleaner theme definition.

🔎 Proposed fix - remove duplicate entry

Remove lines 163-175 as they duplicate the earlier definition:

         </dict>
-        <dict>
-            <key>name</key>
-            <string>Built-in constant</string>
-            <key>scope</key>
-            <string>constant.language</string>
-            <key>settings</key>
-            <dict>
-                <key>fontStyle</key>
-                <string>bold</string>
-                <key>foreground</key>
-                <string>#000000</string>
-            </dict>
-        </dict>
         <dict>

200-212: Minor typo in rule name.

Line 202: "Inherited class seperator" should be "Inherited class separator". This is cosmetic and doesn't affect functionality since only the scope value matters for theme matching.

modules/backend/formwidgets/codeeditor/assets/themes/solarized_dark.tmTheme (1)

1003-1013: Minor typo in rule name.

Line 1005: "PHP: Numberic Constant" should be "PHP: Numeric Constant". This is cosmetic only.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/.gitignore (1)

1-2: Appropriate .gitignore for development chunks.

This correctly excludes development-only webpack chunks while allowing production bundles to be tracked.

Consider also ignoring source maps if they're generated during development:

🔎 Optional: extend pattern to include source maps
 # Ignore dev chunks
 node_modules_*.js
+*.js.map
modules/backend/formwidgets/codeeditor/assets/themes/ambiance.tmTheme (2)

5-6: Theme name mismatch with filename.

The file is named ambiance.tmTheme but the theme's internal name is "Amy" (by William D. Neumann). This mismatch may cause confusion in the UI if the theme name is displayed to users, as they might expect an "Ambiance" theme but see "Amy" instead.

Consider either:

  • Renaming the file to amy.tmTheme to match the theme name, or
  • Updating the theme name to "Ambiance" if that's the intended display name

507-519: Minor typo in scope name.

Line 509 has "depricated" instead of "deprecated". While this is likely from the original theme source and won't affect functionality (it's just a label), it's a minor blemish.

modules/backend/formwidgets/codeeditor/assets/themes/chaos.tmTheme (1)

5-6: Theme name mismatch with filename.

Similar to ambiance.tmTheme, this file is named chaos.tmTheme but the internal theme name is "[ Argonaut ]". This inconsistency may cause confusion in the theme selection UI.

Consider aligning the filename with the theme name or vice versa for clarity.

modules/backend/formwidgets/codeeditor/assets/themes/chrome.tmTheme (2)

5-6: Theme name mismatch with filename.

The file is named chrome.tmTheme but the internal theme name is "CSSEdit". This is the third such mismatch in this batch (along with ambiance.tmTheme → "Amy" and chaos.tmTheme → "[ Argonaut ]").

Consider a consistent approach across all theme files to avoid user confusion in the theme picker.


59-198: Minimal styling with many empty scope entries.

Several scope entries have empty <dict/> settings (e.g., Built-in constant, User-defined constant, Variable, Storage, etc.). While valid (they'll inherit defaults), if these scopes aren't intended to be styled differently, they could be removed to reduce file size. However, keeping them may be intentional for future customization or consistency with other themes.

modules/backend/formwidgets/codeeditor/assets/js/index.js (1)

674-676: Consider using console.error for theme loading failures.

Using console.log for error conditions makes debugging harder. Consider console.error for consistency with error logging elsewhere in the codebase (e.g., line 850).

🔎 Proposed fix
                         error: () => {
-                            console.log(`Unable to load theme "${newTheme}"`);
+                            console.error(`Unable to load theme "${newTheme}"`);
                         },
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 59d3199 and bcb66a8.

⛔ Files ignored due to path filters (13)
  • modules/backend/formwidgets/codeeditor/assets/fonts/codicon.ttf is excluded by !**/*.ttf
  • modules/backend/formwidgets/colorpicker/assets/js/dist/colorpicker.js is excluded by !**/dist/**
  • modules/backend/formwidgets/iconpicker/assets/js/dist/iconpicker.js is excluded by !**/dist/**
  • modules/backend/formwidgets/markdowneditor/assets/js/dist/markdowneditor.js is excluded by !**/dist/**
  • modules/backend/formwidgets/sensitive/assets/js/dist/sensitive.js is excluded by !**/dist/**
  • modules/system/assets/ui/font/fa-brands-400.ttf is excluded by !**/*.ttf
  • modules/system/assets/ui/font/fa-brands-400.woff2 is excluded by !**/*.woff2
  • modules/system/assets/ui/font/fa-regular-400.ttf is excluded by !**/*.ttf
  • modules/system/assets/ui/font/fa-regular-400.woff2 is excluded by !**/*.woff2
  • modules/system/assets/ui/font/fa-solid-900.ttf is excluded by !**/*.ttf
  • modules/system/assets/ui/font/fa-solid-900.woff2 is excluded by !**/*.woff2
  • modules/system/assets/ui/font/fa-v4compatibility.ttf is excluded by !**/*.ttf
  • modules/system/assets/ui/font/fa-v4compatibility.woff2 is excluded by !**/*.woff2
📒 Files selected for processing (130)
  • modules/backend/ServiceProvider.php
  • modules/backend/assets/js/preferences/preferences.js
  • modules/backend/assets/ui/js/ajax/Handler.js
  • modules/backend/assets/ui/js/build/backend.js
  • modules/backend/assets/ui/js/build/manifest.js
  • modules/backend/assets/ui/js/build/vendor.js
  • modules/backend/assets/ui/js/pages/Preferences.js
  • modules/backend/controllers/Preferences.php
  • modules/backend/controllers/preferences/_example_code.php
  • modules/backend/controllers/preferences/_field_editor_preview.php
  • modules/backend/controllers/preferences/_field_editor_preview_lang.php
  • modules/backend/formwidgets/CodeEditor.php
  • modules/backend/formwidgets/MarkdownEditor.php
  • modules/backend/formwidgets/codeeditor/assets/css/codeeditor.css
  • modules/backend/formwidgets/codeeditor/assets/js/build-min.js
  • modules/backend/formwidgets/codeeditor/assets/js/build.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/.gitignore
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/114.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/129.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/133.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/134.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/2.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/202.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/237.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/257.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/294.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/309.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/326.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/342.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/362.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/504.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/54.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/566.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/612.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/641.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/683.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/70.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/726.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/74.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/744.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/754.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/758.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/832.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/847.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/851.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/897.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/910.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/919.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/952.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/99.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/css.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/editor.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/html.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/json.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/ts.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/codeeditor.old.js
  • modules/backend/formwidgets/codeeditor/assets/js/index.js
  • modules/backend/formwidgets/codeeditor/assets/less/codeeditor.less
  • modules/backend/formwidgets/codeeditor/assets/themes/ambiance.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/behave.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/chaos.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/chrome.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/clouds.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/clouds_midnight.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/cobalt.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/dawn.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/dreamweaver.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/eclipse.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/github.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/idle_fingers.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/iplastic.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/katzenmilch.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/kr_theme.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/kuroir.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/merbivore.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/merbivore_soft.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/mono_industrial.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/monokai.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/pastel_on_dark.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/solarized_dark.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/solarized_light.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/sqlserver.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/terminal.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/textmate.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/tomorrow.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/tomorrow_night.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/tomorrow_night_blue.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/tomorrow_night_bright.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/tomorrow_night_eighties.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/twilight.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/vibrant_ink.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/themes/xcode.tmTheme
  • modules/backend/formwidgets/codeeditor/assets/winter.mix.js
  • modules/backend/formwidgets/codeeditor/partials/_codeeditor.php
  • modules/backend/formwidgets/markdowneditor/assets/css/markdowneditor.css
  • modules/backend/formwidgets/markdowneditor/assets/js/markdowneditor.old.js
  • modules/backend/formwidgets/markdowneditor/assets/js/src/EditorAction.js
  • modules/backend/formwidgets/markdowneditor/assets/js/src/MarkdownEditor.js
  • modules/backend/formwidgets/markdowneditor/assets/js/src/actions/Bold.js
  • modules/backend/formwidgets/markdowneditor/assets/js/src/actions/Italic.js
  • modules/backend/formwidgets/markdowneditor/assets/less/markdowneditor.less
  • modules/backend/formwidgets/markdowneditor/partials/_markdowneditor.php
  • modules/backend/lang/en/lang.php
  • modules/backend/models/Preference.php
  • modules/backend/models/preference/fields.yaml
  • modules/backend/package.json
  • modules/backend/winter.mix.js
  • modules/cms/assets/js/winter.cmspage.js
  • modules/cms/assets/js/winter.dragcomponents.js
  • modules/cms/controllers/Index.php
  • modules/system/assets/css/styles.css
  • modules/system/assets/js/build/manifest.js
  • modules/system/assets/js/build/system.debug.js
  • modules/system/assets/js/build/system.js
  • modules/system/assets/js/snowboard/build/snowboard.base.debug.js
  • modules/system/assets/js/snowboard/build/snowboard.base.js
  • modules/system/assets/js/snowboard/build/snowboard.data-attr.js
  • modules/system/assets/js/snowboard/build/snowboard.extras.js
  • modules/system/assets/js/snowboard/build/snowboard.request.js
  • modules/system/assets/js/snowboard/build/snowboard.vendor.js
  • modules/system/assets/js/snowboard/extras/StripeLoader.js
  • modules/system/assets/js/snowboard/main/Snowboard.js
  • modules/system/assets/ui/icons.css
  • modules/system/assets/ui/storm.css
  • modules/system/assets/ui/vendor/select2/css/select2.css
  • modules/system/classes/asset/PackageManager.php
  • package.json
  • themes/demo/pages/ajax.htm
💤 Files with no reviewable changes (2)
  • modules/backend/formwidgets/codeeditor/assets/js/build.js
  • modules/backend/controllers/preferences/_field_editor_preview.php
🧰 Additional context used
🧬 Code graph analysis (14)
modules/backend/assets/ui/js/build/backend.js (1)
modules/system/assets/js/snowboard/main/Snowboard.js (1)
  • Snowboard (21-595)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/134.js (2)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/133.js (23)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js (13)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • a (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/MarkdownEditor.php (1)
modules/system/traits/AssetMaker.php (2)
  • addJs (122-149)
  • addCss (157-176)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/952.js (4)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/754.js (11)
  • e (1-1)
  • e (1-1)
  • t (1-1)
  • n (1-1)
  • n (1-1)
  • n (1-1)
  • n (1-1)
  • n (1-1)
  • n (1-1)
  • n (1-1)
  • i (1-1)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/309.js (23)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/683.js (19)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/74.js (13)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/129.js (2)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/309.js (23)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/683.js (20)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • a (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/237.js (4)
modules/backend/formwidgets/codeeditor/assets/js/build/editor.worker.js (14)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
  • e (1-1)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/309.js (23)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/683.js (19)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/74.js (13)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/ServiceProvider.php (1)
modules/system/classes/asset/PackageManager.php (3)
  • PackageManager (24-414)
  • registerCallback (185-188)
  • registerPackage (289-361)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/897.js (2)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/309.js (23)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/683.js (20)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • a (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/504.js (2)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/683.js (12)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/74.js (13)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/54.js (3)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/133.js (23)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/257.js (20)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js (13)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • a (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/641.js (3)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/309.js (14)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/683.js (12)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/74.js (13)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/754.js (2)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/309.js (23)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/683.js (20)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • a (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/257.js (1)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js (20)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • a (7-7)
modules/backend/formwidgets/codeeditor/assets/js/index.js (1)
modules/backend/formwidgets/CodeEditor.php (1)
  • CodeEditor (15-258)
🪛 Biome (2.1.2)
modules/backend/assets/ui/js/build/manifest.js

[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/74.js

[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/683.js

[error] 7-7: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 7-7: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 7-7: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 7-7: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js

[error] 7-7: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 7-7: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 7-7: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 7-7: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/133.js

[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/309.js

[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/assets/js/preferences/preferences.js

[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/257.js

[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

Comment on lines +356 to +358
document.addEventListener('click', this.callbacks.click, {
capture: true,
});
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Click listener accumulates on repeated visibility changes.

The click listener is added each time attachListeners() runs (via createEditor()), but only removed in destruct(), not in dispose(). Since the IntersectionObserver calls dispose()/createEditor() as the element enters/exits the viewport, multiple click listeners can accumulate.

🔎 Proposed fix

Move click listener cleanup to dispose():

         dispose() {
             if (this.disposables.length > 0) {
                 this.disposables.forEach((disposable) => {
                     disposable.dispose();
                 });
                 this.disposables = [];
             }
             if (this.resizeListener) {
                 window.removeEventListener('resize', this.callbacks.resize);
                 this.resizeListener = false;
             }
+            if (this.clickListener) {
+                document.removeEventListener('click', this.callbacks.click, { capture: true });
+                this.clickListener = false;
+            }
             if (this.editor) {
                 this.editor.dispose();
                 this.editor = null;
             }
             this.events.fire('dispose', this, this.editor);
         }

And track the listener in attachListeners():

             document.addEventListener('click', this.callbacks.click, {
                 capture: true,
             });
+            this.clickListener = true;

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +5 to +6
<key>name</key>
<string>Coda</string>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Theme name mismatch with filename.

The file is named eclipse.tmTheme but the internal theme name is "Coda". This inconsistency may cause confusion when users select themes in the UI, as the displayed name won't match the expected theme.

Consider either:

  • Renaming the file to coda.tmTheme, or
  • Updating the internal name to "Eclipse"
🤖 Prompt for AI Agents
In modules/backend/formwidgets/codeeditor/assets/themes/eclipse.tmTheme around
lines 5 to 6, the internal theme name is "Coda" but the filename is
eclipse.tmTheme; update the inconsistency by either renaming the file to
coda.tmTheme everywhere it’s referenced (including asset imports, build scripts,
and any theme selection UI entries) or change the <string> value at these lines
to "Eclipse" so the internal name matches the filename; ensure references and
any documentation are updated accordingly and run a quick UI/theme load check to
verify the chosen approach works.

Comment on lines +7 to +8
<key>name</key>
<string>Tango</string>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Theme name mismatch with filename.

The file is named katzenmilch.tmTheme but the internal theme name is "Tango" (authored by Chris Thomas). Similar to dreamweaver.tmTheme, this inconsistency may cause confusion in theme selection.

Consider aligning the filename with the theme name for consistency across the theme assets.

🤖 Prompt for AI Agents
In modules/backend/formwidgets/codeeditor/assets/themes/katzenmilch.tmTheme
around lines 7-8, the theme's internal <string> value is "Tango" while the
filename is katzenmilch.tmTheme; update one to match the other for consistency:
either rename the file to tango.tmTheme (and adjust any references) or change
the <string> name to "Katzenmilch" (and update author/metadata if needed),
ensuring all references and packaging/manifest entries are updated accordingly.

Comment on lines +48 to +58
<dict>
<key>name</key>
<string>StringNumber</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#586E75</string>
</dict>
</dict>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Potential scope mismatch in theme rule.

The rule named "StringNumber" uses scope string, which seems inconsistent with its name. This will override the earlier "String" rule (lines 37-47) that also targets scope string, causing strings to appear in color #586E75 instead of #2AA198.

If this is intentional, the first "String" rule is redundant. If "StringNumber" was meant to target numeric values, the scope should likely be constant.numeric or similar.

🔎 Potential fixes

Option 1: If strings should be #2AA198, remove the "StringNumber" rule (lines 48-58).

Option 2: If "StringNumber" was meant for numbers, fix the scope:

         <dict>
             <key>name</key>
             <string>StringNumber</string>
             <key>scope</key>
-            <string>string</string>
+            <string>constant.numeric</string>
             <key>settings</key>
             <dict>
                 <key>foreground</key>
                 <string>#586E75</string>
             </dict>
         </dict>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<dict>
<key>name</key>
<string>StringNumber</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#586E75</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>StringNumber</string>
<key>scope</key>
<string>constant.numeric</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#586E75</string>
</dict>
</dict>
🤖 Prompt for AI Agents
In modules/backend/formwidgets/codeeditor/assets/themes/solarized_dark.tmTheme
around lines 48 to 58, the "StringNumber" rule uses scope "string" which
overrides the earlier "String" rule and causes strings to use #586E75; either
remove this redundant "StringNumber" block if strings should remain #2AA198, or
change its scope to a numeric scope such as "constant.numeric" (or
"constant.numeric, constant.integer, constant.float") so it only applies to
numbers and not general strings.

Comment on lines +37 to +58
<dict>
<key>name</key>
<string>String</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#2AA198</string>
</dict>
</dict>
<dict>
<key>name</key>
<string>StringNumber</string>
<key>scope</key>
<string>string</string>
<key>settings</key>
<dict>
<key>foreground</key>
<string>#586E75</string>
</dict>
</dict>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Duplicate string scope definition may cause unintended behavior.

Lines 37-47 define string scope with foreground #2AA198 (cyan), but lines 48-58 redefine the same string scope with foreground #586E75 (base gray). The second definition will override the first, making the cyan color unused for plain strings.

If the cyan color is intended for strings, consider removing or renaming the "StringNumber" scope. If this is intentional (perhaps from the original Solarized theme source), you can ignore this note.

🤖 Prompt for AI Agents
In modules/backend/formwidgets/codeeditor/assets/themes/solarized_light.tmTheme
around lines 37 to 58 there are duplicate entries targeting the same scope
"string" (one with foreground #2AA198 and a second labeled "StringNumber" with
foreground #586E75), so the latter will override the former; fix by either
removing the redundant second dict if cyan is the intended string color, or
change the second dict's scope to the correct specific scope (e.g.,
string.number, constant.numeric.string, or another language-specific scope) or
rename the key so it targets numbers inside strings rather than the generic
"string" scope, ensuring no two dictionaries declare the identical scope.

Comment on lines +5 to +6
<key>name</key>
<string>BBEdit</string>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Theme name mismatch with filename.

The file is named sqlserver.tmTheme but the internal theme name is "BBEdit". This is the third theme file with a naming discrepancy (along with dreamweaver.tmTheme→"Eiffel" and katzenmilch.tmTheme→"Tango").

Consider a bulk renaming pass to align filenames with internal theme names across all theme assets for consistency and to avoid user confusion during theme selection.

🤖 Prompt for AI Agents
modules/backend/formwidgets/codeeditor/assets/themes/sqlserver.tmTheme around
lines 5–6: the internal theme name is "BBEdit" but the filename is
sqlserver.tmTheme; update the <string> value to match the filename (e.g.,
"sqlserver" or "SQL Server" following project naming conventions) so the
internal name and filename align, and while here run a quick pass over other
theme files (dreamweaver.tmTheme, katzenmilch.tmTheme, etc.) to fix similar
mismatches for consistency.

LukeTowers and others added 22 commits December 29, 2025 13:11
# Conflicts:
#	modules/backend/formwidgets/codeeditor/assets/css/codeeditor.css
#	modules/system/assets/ui/storm.css
Also improved the UX for previewing the code editor settings, added one-dark-pro and winter themes, renamed codeeditor's source JS file to codeeditor.js, fixed a bug with alt key binding, and removed unused code from the Preferences controller.
RichEditor is currently using Froala v2.9.3 which is difficult to customize as we don't have the original DRM files anymore. Ideally we would like to replace the codeeditor view of Froala with Monaco, but for now we'll retain ace for this use case.
Markdown editor needs some more work, we might look at using TipTap or Milkdown / Crepe in the future.
Interacting with the PackageManager triggers a need to use the database connection to get the active theme, the database connection is not available until the boot process.
…educe issues caused by timing issues with webpack loading
For anyone else trying to track down self being clobbered, this is very handy:

(function () {
  try {
    const desc = Object.getOwnPropertyDescriptor(window, 'self');
    console.log('window.self descriptor:', desc);

    let _selfValue = window;
    Object.defineProperty(window, 'self', {
      configurable: true,
      get() { return _selfValue; },
      set(v) {
        console.error('window.self set to:', v);
        console.trace('stack:');     // <- prints the exact script/line
        _selfValue = v;
      },
    });
  } catch (e) {
    console.warn('Failed to trap window.self:', e);
  }
})();
@LukeTowers LukeTowers marked this pull request as ready for review January 2, 2026 07:14
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

♻️ Duplicate comments (3)
modules/backend/formwidgets/codeeditor/assets/js/codeeditor.js (3)

196-212: Click listener cleanup missing in dispose() — issue persists from prior review.

The click listener is added each time attachListeners() runs (via createEditor()), but cleanup only happens in destruct(), not dispose(). Since IntersectionObserver triggers dispose()/createEditor() cycles, multiple click listeners can accumulate.

🔎 Proposed fix
         dispose() {
             if (this.disposables.length > 0) {
                 this.disposables.forEach((disposable) => {
                     disposable.dispose();
                 });
                 this.disposables = [];
             }
             if (this.resizeListener) {
                 window.removeEventListener('resize', this.callbacks.resize);
                 this.resizeListener = false;
             }
+            if (this.clickListener) {
+                document.removeEventListener('click', this.callbacks.click, { capture: true });
+                this.clickListener = false;
+            }
             if (this.editor) {
                 this.editor.dispose();
                 this.editor = null;
             }
             this.events.fire('dispose', this, this.editor);
         }

And in attachListeners() (around line 401-403):

             document.addEventListener('click', this.callbacks.click, {
                 capture: true,
             });
+            this.clickListener = true;

1497-1508: Null pointer dereference when resolver is not provided — issue persists.

When addCodeLens is called without a resolver (using the default null), line 1504 throws a TypeError because resolver(...) is invoked before the nullish coalescing operator evaluates.

🔎 Proposed fix
         addCodeLens(language, provider, resolver = null) {
             this.disposables.push(
                 monaco.languages.registerCodeLensProvider(language, {
                     provideCodeLenses: (model, token) => {
                         return provider(model, token);
                     },
                     resolveCodeLens: (model, codeLens, token) => {
-                        return resolver(model, codeLens, token) ?? codeLens;
+                        return resolver ? resolver(model, codeLens, token) : codeLens;
                     },
                 })
             );
         }

401-411: Missing clickListener flag update.

The clickListener property is initialized but never set to true when the listener is added, making the flag useless for tracking.

🔎 Proposed fix
             document.addEventListener('click', this.callbacks.click, {
                 capture: true,
             });
+            this.clickListener = true;
🧹 Nitpick comments (8)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_less_less_js.js (1)

1-187: Auto-generated Monaco Editor language bundle — LGTM with one consideration.

This is a webpack-generated chunk from the official monaco-editor package (v0.34.1) providing LESS language support. The tokenizer and configuration are maintained upstream by Microsoft.

One optional consideration: the inline base64 source map on line 187 increases bundle size. If source maps aren't needed for debugging Monaco's internal language definitions in production, consider configuring webpack to externalize or omit source maps for these vendor chunks.

modules/backend/assets/less/layout/fancylayout.less (1)

440-442: Consider using a LESS variable instead of hardcoded color.

The hardcoded #000000 value reduces maintainability. For consistency with the rest of the file (which uses variables like @color-fancy-master-tabs-active-text), consider defining a variable for this color value.

🔎 Suggested refactor

Define a variable (e.g., @color-fancy-secondary-tabs-icon-default) and use it here:

        .tab-collapse-icon.primary {
-            color: #000000;
+            color: @color-fancy-secondary-tabs-icon-default;
        }
modules/backend/controllers/Preferences.php (1)

4-11: Imports look good, but ApplicationException and File appear unused.

The explicit facade imports are appropriate. However, ApplicationException (line 9) and File (line 10) are imported but not used in the visible controller code.

🔎 Consider removing unused imports
 use Backend\Facades\Backend;
 use Backend\Facades\BackendMenu;
 use Backend\Models\Preference as PreferenceModel;
 use Illuminate\Support\Facades\Lang;
 use System\Classes\SettingsManager;
-use Winter\Storm\Exception\ApplicationException;
-use Winter\Storm\Support\Facades\File;
 use Winter\Storm\Support\Facades\Flash;
modules/backend/assets/vendor/ace-codeeditor/codeeditor.js (1)

247-263: Variable redeclaration in nested loops.

The static analysis correctly identifies that i and len are redeclared in the second loop (line 255) after being declared in the first loop (line 251). While JavaScript hoists var declarations and this works at runtime, it's a code smell that can cause confusion.

🔎 Proposed fix to eliminate redeclaration
     CodeEditor.prototype.disposeAttachedControls = function() {
         this.editor.destroy()
 
         var keys = Object.keys(this.editor.renderer)
-        for (var i=0, len=keys.length; i<len; i++)
+        for (var i=0, len=keys.length; i<len; i++) {
             this.editor.renderer[keys[i]] = null
+        }
 
         keys = Object.keys(this.editor)
-        for (var i=0, len=keys.length; i<len; i++)
+        for (i=0, len=keys.length; i<len; i++) {
             this.editor[keys[i]] = null
+        }
 
         this.editor = null
modules/backend/formwidgets/codeeditor/assets/js/codeeditor.js (4)

28-35: Consider revoking blob URLs to prevent memory leaks.

Blob URLs created via URL.createObjectURL should ideally be revoked after use. However, since these worker URLs are typically loaded once and cached by the browser, the impact is minimal.

🔎 Proposed improvement (if workers are loaded multiple times)
         // Cross-origin: create blob URL wrapper
         const script = `importScripts('${workerUrl}');`;
         const blob = new Blob([script], { type: 'application/javascript' });
-        return URL.createObjectURL(blob);
+        const blobUrl = URL.createObjectURL(blob);
+        // Note: Cannot revoke immediately as worker needs the URL
+        // Consider caching blob URLs if workers are recreated frequently
+        return blobUrl;

435-446: Consider null checks for model access.

getValue() and setValue() access this.model directly. If called when the editor is disposed (e.g., element out of viewport), this will throw.

🔎 Proposed defensive check
         getValue() {
+            if (!this.model) {
+                return this.valueBag?.value ?? '';
+            }
             return this.model.getValue();
         }

         setValue(value) {
+            if (!this.model) {
+                if (this.valueBag) {
+                    this.valueBag.value = value;
+                }
+                return;
+            }
             this.model.setValue(value);
         }

1029-1039: Early return added — redundant regex match remains.

The past review issue is fixed with the early return at line 1034. However, the regex is matched twice (lines 1032 and 1036), which is inefficient.

🔎 Proposed optimization
             if (color.length === 9) {
                 return color;
             }
-            if (!color.match(/^#(..)(..)(..)(..)$/)) {
+            const rgbaMatch = color.match(/^#(..)(..)(..)(..)$/);
+            if (!rgbaMatch) {
                 // Invalid color format - return original color
                 return color;
             }
-            const rgba = color.match(/^#(..)(..)(..)(..)$/).slice(1).map((c) => parseInt(c, 16));
+            const rgba = rgbaMatch.slice(1).map((c) => parseInt(c, 16));
             rgba[3] = (rgba[3] / 0xFF).toPrecision(2);
             return `rgba(${rgba.join(', ')})`;

1047-1056: Missing null checks for regex matches could cause runtime errors.

Both regex matches on lines 1052 and 1055 could return null if the color format is unexpected, causing .slice(1) to throw.

🔎 Proposed defensive fix
         rgbColor(color) {
             if (typeof color === 'object') {
                 return color;
             }
             if (color[0] === '#') {
-                return color.match(/^#(..)(..)(..)/).slice(1).map((c) => parseInt(c, 16));
+                const match = color.match(/^#(..)(..)(..)$/);
+                return match ? match.slice(1).map((c) => parseInt(c, 16)) : [0, 0, 0];
             }
-
-            return color.match(/\(([^,]+),([^,]+),([^,]+)/).slice(1).map((c) => parseInt(c, 10));
+            const match = color.match(/\(([^,]+),\s*([^,]+),\s*([^,)]+)/);
+            return match ? match.slice(1).map((c) => parseInt(c, 10)) : [0, 0, 0];
         }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bcb66a8 and 3ebf35c.

⛔ Files ignored due to path filters (3)
  • modules/backend/formwidgets/colorpicker/assets/js/dist/colorpicker.js is excluded by !**/dist/**
  • modules/backend/formwidgets/iconpicker/assets/js/dist/iconpicker.js is excluded by !**/dist/**
  • modules/backend/formwidgets/sensitive/assets/js/dist/sensitive.js is excluded by !**/dist/**
📒 Files selected for processing (135)
  • modules/backend/ServiceProvider.php
  • modules/backend/assets/.gitignore
  • modules/backend/assets/css/winter.css
  • modules/backend/assets/js/preferences/preferences.js
  • modules/backend/assets/less/layout/fancylayout.less
  • modules/backend/assets/ui/js/build/backend.js
  • modules/backend/assets/ui/js/build/manifest.js
  • modules/backend/assets/ui/js/build/vendor.js
  • modules/backend/assets/vendor/ace-codeeditor/build-min.js
  • modules/backend/assets/vendor/ace-codeeditor/build.js
  • modules/backend/assets/vendor/ace-codeeditor/codeeditor.js
  • modules/backend/assets/vendor/ace/ace.js
  • modules/backend/assets/vendor/ace/ext-emmet.js
  • modules/backend/assets/vendor/ace/ext-language_tools.js
  • modules/backend/assets/vendor/ace/ext-searchbox.js
  • modules/backend/assets/vendor/ace/mode-css.js
  • modules/backend/assets/vendor/ace/mode-html.js
  • modules/backend/assets/vendor/ace/mode-javascript.js
  • modules/backend/assets/vendor/ace/mode-less.js
  • modules/backend/assets/vendor/ace/mode-markdown.js
  • modules/backend/assets/vendor/ace/mode-php.js
  • modules/backend/assets/vendor/ace/mode-plain_text.js
  • modules/backend/assets/vendor/ace/mode-sass.js
  • modules/backend/assets/vendor/ace/mode-scss.js
  • modules/backend/assets/vendor/ace/mode-twig.js
  • modules/backend/assets/vendor/ace/mode-yaml.js
  • modules/backend/assets/vendor/ace/snippets/css.js
  • modules/backend/assets/vendor/ace/snippets/html.js
  • modules/backend/assets/vendor/ace/snippets/javascript.js
  • modules/backend/assets/vendor/ace/snippets/markdown.js
  • modules/backend/assets/vendor/ace/snippets/php-inline.js
  • modules/backend/assets/vendor/ace/snippets/php.js
  • modules/backend/assets/vendor/ace/snippets/plain_text.js
  • modules/backend/assets/vendor/ace/snippets/sass.js
  • modules/backend/assets/vendor/ace/snippets/scss.js
  • modules/backend/assets/vendor/ace/snippets/text.js
  • modules/backend/assets/vendor/ace/snippets/twig.js
  • modules/backend/assets/vendor/ace/snippets/yaml.js
  • modules/backend/assets/vendor/ace/theme-ambiance.js
  • modules/backend/assets/vendor/ace/theme-chaos.js
  • modules/backend/assets/vendor/ace/theme-chrome.js
  • modules/backend/assets/vendor/ace/theme-clouds.js
  • modules/backend/assets/vendor/ace/theme-clouds_midnight.js
  • modules/backend/assets/vendor/ace/theme-cobalt.js
  • modules/backend/assets/vendor/ace/theme-crimson_editor.js
  • modules/backend/assets/vendor/ace/theme-dawn.js
  • modules/backend/assets/vendor/ace/theme-dreamweaver.js
  • modules/backend/assets/vendor/ace/theme-eclipse.js
  • modules/backend/assets/vendor/ace/theme-github.js
  • modules/backend/assets/vendor/ace/theme-idle_fingers.js
  • modules/backend/assets/vendor/ace/theme-iplastic.js
  • modules/backend/assets/vendor/ace/theme-katzenmilch.js
  • modules/backend/assets/vendor/ace/theme-kr_theme.js
  • modules/backend/assets/vendor/ace/theme-kuroir.js
  • modules/backend/assets/vendor/ace/theme-merbivore.js
  • modules/backend/assets/vendor/ace/theme-merbivore_soft.js
  • modules/backend/assets/vendor/ace/theme-mono_industrial.js
  • modules/backend/assets/vendor/ace/theme-monokai.js
  • modules/backend/assets/vendor/ace/theme-pastel_on_dark.js
  • modules/backend/assets/vendor/ace/theme-solarized_dark.js
  • modules/backend/assets/vendor/ace/theme-solarized_light.js
  • modules/backend/assets/vendor/ace/theme-sqlserver.js
  • modules/backend/assets/vendor/ace/theme-terminal.js
  • modules/backend/assets/vendor/ace/theme-textmate.js
  • modules/backend/assets/vendor/ace/theme-tomorrow.js
  • modules/backend/assets/vendor/ace/theme-tomorrow_night.js
  • modules/backend/assets/vendor/ace/theme-tomorrow_night_blue.js
  • modules/backend/assets/vendor/ace/theme-tomorrow_night_bright.js
  • modules/backend/assets/vendor/ace/theme-tomorrow_night_eighties.js
  • modules/backend/assets/vendor/ace/theme-twilight.js
  • modules/backend/assets/vendor/ace/theme-vibrant_ink.js
  • modules/backend/assets/vendor/ace/theme-xcode.js
  • modules/backend/assets/vendor/ace/worker-css.js
  • modules/backend/assets/vendor/ace/worker-html.js
  • modules/backend/assets/vendor/ace/worker-javascript.js
  • modules/backend/assets/vendor/ace/worker-php.js
  • modules/backend/assets/vendor/emmet/emmet.js
  • modules/backend/controllers/Preferences.php
  • modules/backend/controllers/preferences/_field_editor_preview_lang.php
  • modules/backend/formwidgets/CodeEditor.php
  • modules/backend/formwidgets/MarkdownEditor.php
  • modules/backend/formwidgets/RichEditor.php
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/133.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/257.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/851.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_css_css_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_html_html_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_ini_ini_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_javascript_javascript_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_less_less_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_markdown_markdown_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_mysql_mysql_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_php_php_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_scss_scss_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_twig_twig_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_typescript_typescript_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_xml_xml_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_yaml_yaml_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_language_css_cssMode_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_language_html_htmlMode_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_language_json_jsonMode_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_language_typescript_tsMode_js.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/css.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/editor.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/html.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/json.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/build/ts.worker.js
  • modules/backend/formwidgets/codeeditor/assets/js/codeeditor.js
  • modules/backend/formwidgets/codeeditor/assets/themes/one-dark-pro.json
  • modules/backend/formwidgets/codeeditor/assets/themes/winter.json
  • modules/backend/formwidgets/codeeditor/assets/winter.mix.js
  • modules/backend/formwidgets/markdowneditor/partials/_markdowneditor.php
  • modules/backend/formwidgets/richeditor/partials/_richeditor.php
  • modules/backend/lang/en/lang.php
  • modules/backend/layouts/_head.php
  • modules/backend/layouts/auth.php
  • modules/backend/models/Preference.php
  • modules/backend/models/preference/fields.yaml
  • modules/backend/widgets/table/assets/js/build-min.js
  • modules/backend/widgets/table/assets/js/table.processor.dropdown.js
  • modules/backend/winter.mix.js
  • modules/cms/ServiceProvider.php
  • modules/cms/controllers/Index.php
  • modules/system/ServiceProvider.php
  • modules/system/assets/js/build/manifest.js
  • modules/system/assets/js/build/system.debug.js
  • modules/system/assets/js/build/system.js
  • modules/system/assets/js/snowboard/build/snowboard.base.debug.js
  • modules/system/assets/js/snowboard/build/snowboard.base.js
  • modules/system/assets/js/snowboard/build/snowboard.data-attr.js
  • modules/system/assets/js/snowboard/build/snowboard.extras.js
  • modules/system/assets/js/snowboard/build/snowboard.request.js
  • package.json
✅ Files skipped from review due to trivial changes (3)
  • modules/backend/assets/vendor/ace-codeeditor/build.js
  • modules/backend/formwidgets/codeeditor/assets/themes/one-dark-pro.json
  • modules/backend/assets/.gitignore
🚧 Files skipped from review as they are similar to previous changes (2)
  • modules/backend/assets/ui/js/build/backend.js
  • modules/backend/controllers/preferences/_field_editor_preview_lang.php
🧰 Additional context used
🧬 Code graph analysis (7)
modules/backend/formwidgets/MarkdownEditor.php (2)
modules/backend/facades/BackendAuth.php (1)
  • BackendAuth (16-27)
modules/system/traits/AssetMaker.php (1)
  • addJs (122-149)
modules/backend/formwidgets/RichEditor.php (2)
modules/backend/facades/BackendAuth.php (1)
  • BackendAuth (16-27)
modules/system/traits/AssetMaker.php (1)
  • addJs (122-149)
modules/backend/assets/vendor/ace-codeeditor/codeeditor.js (3)
modules/backend/formwidgets/CodeEditor.php (1)
  • CodeEditor (15-271)
modules/backend/classes/FormField.php (1)
  • options (228-246)
modules/cms/assets/js/winter.cmspage.js (1)
  • $form (268-268)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/133.js (1)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js (21)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • a (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js (2)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/133.js (23)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/257.js (20)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • t (7-7)
  • n (7-7)
  • n (7-7)
  • n (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • r (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
  • i (7-7)
modules/backend/ServiceProvider.php (1)
modules/system/classes/asset/PackageManager.php (2)
  • PackageManager (24-414)
  • registerCallback (185-188)
modules/backend/controllers/Preferences.php (3)
modules/backend/facades/BackendMenu.php (1)
  • BackendMenu (30-41)
modules/backend/models/Preference.php (1)
  • Preference (21-323)
modules/system/classes/SettingsManager.php (1)
  • SettingsManager (14-417)
🪛 Biome (2.1.2)
modules/backend/assets/ui/js/build/manifest.js

[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/assets/vendor/ace-codeeditor/codeeditor.js

[error] 255-255: Shouldn't redeclare 'i'. Consider to delete it or rename it.

'i' is defined here:

(lint/suspicious/noRedeclare)


[error] 255-255: Shouldn't redeclare 'len'. Consider to delete it or rename it.

'len' is defined here:

(lint/suspicious/noRedeclare)

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/133.js

[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js

[error] 7-7: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 7-7: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 7-7: This case is falling through to the next case.

Add a break or return statement to the end of this case to prevent fallthrough.

(lint/suspicious/noFallthroughSwitchClause)


[error] 7-7: Other switch clauses can erroneously access this declaration.
Wrap the declaration in a block to restrict its access to the switch clause.

The declaration is defined in this switch clause:

Safe fix: Wrap the declaration in a block.

(lint/correctness/noSwitchDeclarations)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/257.js

[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 7-7: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

modules/backend/assets/js/preferences/preferences.js

[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

🔇 Additional comments (37)
modules/backend/assets/ui/js/build/manifest.js (1)

1-1: This file is a committed build artifact—build artifacts are intentionally tracked in Winter CMS.

The functional change adding chunk 908 tracking for the Monaco editor is correct and aligns with Winter CMS's established practice of committing compiled build artifacts to version control. No changes are needed.

Note: Static analysis warnings about variables being used before declaration are false positives typical of minified webpack bundles and can be safely ignored.

Likely an incorrect or invalid review comment.

modules/backend/assets/css/winter.css (1)

1024-1025: Compiled CSS output looks correct.

These changes introduce explicit color handling for the tab collapse icon in both collapsed and non-collapsed states, which improves visual distinction. The compiled output correctly reflects the source LESS changes.

Note: Since this is compiled CSS, the substantive review should focus on the LESS source file (fancylayout.less).

modules/backend/assets/less/layout/fancylayout.less (1)

444-447: Good improvement using a LESS variable.

Changing from hardcoded white to @color-fancy-master-tabs-active-text improves maintainability and consistency with the rest of the codebase. This makes it easier to update the color scheme in the future.

modules/backend/formwidgets/MarkdownEditor.php (2)

1-11: LGTM! Explicit facade imports and namespace updates.

The imports are now using fully-qualified Winter\Storm facades (Html, Markdown) and Backend\Facades\BackendAuth, which aligns with the codebase conventions shown in the relevant code snippets.


92-97: Intentional retention of ACE for MarkdownEditor.

The asset path update to /modules/backend/assets/vendor/ace-codeeditor/build-min.js correctly keeps ACE for the Markdown editor while Monaco is adopted elsewhere. This aligns with the PR discussion noting that Monaco integration for Markdown is deferred.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_ini_ini_js.js (1)

1-88: LGTM! Standard Monaco INI language bundle.

This is a webpack-generated Monaco Editor basic language module for INI files. The bundle correctly exports conf and language bindings following the established Monaco language definition pattern used across other language bundles in this PR.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_mysql_mysql_js.js (1)

1-895: LGTM! Standard Monaco MySQL language bundle.

This is a webpack-generated Monaco Editor basic language module for MySQL. The comprehensive SQL syntax support including keywords, operators, built-in functions, and tokenizer states follows the standard Monaco language definition pattern.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_scss_scss_js.js (1)

1-262: LGTM! Standard Monaco SCSS language bundle.

This is a webpack-generated Monaco Editor basic language module for SCSS. The tokenizer correctly handles SCSS-specific constructs including variables, mixins, functions, control statements, and nested properties.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_php_php_js.js (1)

1-491: LGTM! Standard Monaco PHP language bundle.

This is a webpack-generated Monaco Editor basic language module for PHP with comprehensive support for mixed PHP/HTML content. The tokenizer correctly handles embedded contexts (JavaScript in <script>, CSS in <style>) and PHP block transitions, which is essential for Winter CMS template editing.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_twig_twig_js.js (1)

1-343: LGTM! Standard Monaco Twig language bundle.

This is a webpack-generated Monaco Editor basic language module for Twig. The tokenizer correctly handles Twig-specific delimiters ({# #}, {% %}, {{ }}), verbatim blocks, and embedded HTML/JavaScript/CSS contexts. This is particularly valuable for Winter CMS template editing.

modules/backend/controllers/Preferences.php (1)

34-42: LGTM! Asset loading simplified for preferences.

The constructor correctly removes direct code editor asset loading. The code editor assets are now managed centrally through the ServiceProvider's asset bundle registration, and only the preferences-specific JavaScript remains.

modules/backend/formwidgets/RichEditor.php (1)

1-14: LGTM! Import additions are all used.

All newly added facade imports (Backend, BackendAuth, App, Lang, Request, Config, Event, File) are verified as used within the file, which improves code clarity and follows best practices.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_css_css_js.js (1)

1-204: Vendor bundle looks correct.

This is an auto-generated webpack bundle containing the standard Monaco Editor CSS language definition (v0.34.1). The module correctly exports conf and language for CSS syntax highlighting support.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_xml_xml_js.js (1)

1-127: Vendor bundle looks correct.

Standard Monaco Editor XML language definition bundle with proper tokenizer states and editor configuration.

modules/backend/ServiceProvider.php (3)

94-94: Ace editor bundle retained for backward compatibility.

Adding the ace-codeeditor bundle aligns with the PR description stating that Ace is being retained for RichEditor and MarkdownEditor while Monaco is introduced for the primary code editor.


114-117: Package registration follows established naming convention.

The file exists at the specified path, and the registration pattern using PackageManager::instance()->registerCallback is consistent with other callback registrations in this file (MailManager, CombineAssets, BackendMenu, BackendAuth, and SettingsManager).


56-57: Asset bundle registration in boot() method is correct.

registerAssetBundles() is appropriately placed in the boot() method where all asset bundle callbacks are registered. This is consistent with the pattern in the cms and system modules and aligns with Laravel's service provider lifecycle, where boot() executes after all services are registered.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_yaml_yaml_js.js (1)

1-216: Vendor bundle looks correct.

Standard Monaco Editor YAML language definition with comprehensive tokenization support for YAML-specific constructs including anchors, tags, and flow collections.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_markdown_markdown_js.js (1)

1-222: Vendor bundle looks correct.

Standard Monaco Editor Markdown language definition with support for tables, fenced code blocks, and embedded HTML/CSS/JavaScript content.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_html_html_js.js (1)

1-314: Vendor bundle looks correct.

Standard Monaco Editor HTML language definition with comprehensive support for embedded script/style content, proper handling of empty elements, and type-aware language embedding.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_javascript_javascript_js.js (1)

1-449: Vendor bundle looks correct.

Standard Monaco Editor JavaScript/TypeScript language definitions. The JavaScript module appropriately extends the TypeScript configuration while maintaining its own keyword list without TypeScript-specific keywords.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/node_modules_monaco-editor_esm_vs_basic-languages_typescript_typescript_js.js (1)

1-359: Vendor bundle - LGTM.

This is an auto-generated webpack bundle for Monaco Editor's TypeScript language support (v0.34.1). The static analysis warnings about "variable used before declaration" are false positives caused by webpack's bundling patterns and the module re-export mechanism on line 39. These patterns are valid at runtime.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/133.js (1)

1-2: Minified vendor bundle - LGTM.

This is a minified Monaco Editor HTML language service integration bundle. The static analysis warnings on line 7 are false positives caused by webpack's minification and module bundling patterns. The code correctly implements WorkerManager with idle timeout handling and comprehensive language service adapters.

modules/backend/formwidgets/CodeEditor.php (2)

250-270: Preference migration logic handles backwards compatibility well.

The fallback logic $preferences->editor_enable_folding ?? ($preferences->editor_code_folding !== 'manual') gracefully handles migration from the old string-based editor_code_folding preference to the new boolean editor_enable_folding.

The new preference mappings (showMinimap, bracketColors, showColors) follow the established pattern.


240-244: Asset bundle path updated correctly.

The change from js/build-min.js to js/build/codeeditor.bundle.js aligns with the Monaco Editor integration. Bundle file exists at the expected path, along with associated chunk files and CSS assets.

modules/backend/assets/vendor/ace-codeeditor/codeeditor.js (2)

446-448: Data control attribute correctly differentiated.

The initialization now uses data-control="ace-codeeditor" which properly distinguishes this ACE-based editor from the new Monaco-based data-control="codeeditor". This allows both editors to coexist during the transition period.


453-466: Emmet twig syntax fix retained.

The Emmet extension fix that normalizes twig syntax to html is preserved, ensuring backwards compatibility with existing Twig template editing workflows.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/39.js (1)

1-7: Minified vendor bundle - LGTM.

This is a minified Monaco Editor JSON language service bundle. The static analysis warnings (switch fallthrough, switch declarations, variables used before declaration) are false positives caused by:

  1. The minified JSON tokenizer implementation which legitimately uses switch fallthroughs for lexer state transitions
  2. Webpack's module bundling patterns

These patterns are intentional and valid in the Monaco Editor source.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/257.js (1)

1-7: Minified vendor bundle - LGTM.

This is a minified Monaco Editor CSS language service bundle. It correctly creates a web worker for CSS language features and exposes the standard set of language adapters. The static analysis warnings about variables used before declaration are false positives from webpack bundling patterns.

modules/backend/formwidgets/codeeditor/assets/js/build/codeeditor.bundle/851.js (1)

1-7: TypeScript/JavaScript language service bundle - LGTM.

This minified bundle provides comprehensive TypeScript and JavaScript language support for Monaco, including:

  • Definition and reference navigation via LibFiles handling
  • InlayHints support for parameter and type hints
  • Code actions and formatting
  • Diagnostics with support for onlyVisible mode

The bundle correctly manages TypeScript library files (lib.d.ts, lib.es*.d.ts, etc.) and handles the worker lifecycle with extra libs synchronization.

modules/backend/formwidgets/codeeditor/assets/js/codeeditor.js (7)

61-99: LGTM!

The constructor is well-structured with proper initialization of all state variables, event callbacks, and observer setup. The defensive approach of checking for statusBar before accessing its child elements is good practice.


220-236: LGTM!

Good use of IntersectionObserver for lazy loading and memory management. The editor is properly disposed when out of view and recreated when visible.


241-263: LGTM!

Good approach to force a fixed height to prevent Monaco's resize issues. The jQuery backward compatibility for the Builder plugin is a thoughtful touch.


583-624: LGTM!

The wrap() and unwrap() methods correctly handle the empty selection case and use Monaco's pushEditOperations for atomic edits.


768-810: LGTM!

Good caching strategy for themes. The silent fallback on error is acceptable since theme loading failure is non-critical and the default theme will remain active.


1291-1297: LGTM!

Good memory management by disposing the editor when the tab/window is hidden. Works in conjunction with the IntersectionObserver for comprehensive visibility handling.


1308-1340: LGTM!

Clever use of constrained-editor-plugin to hide preamble content while maintaining correct line numbering. The custom line numbering function correctly adjusts displayed line numbers.

})

})
"use strict";(self.webpackChunk_wintercms_wn_backend_module=self.webpackChunk_wintercms_wn_backend_module||[]).push([[429],{449:function(e,t,i){var n=i(171);(e=>{class t extends e.Singleton{construct(){this.widget=null}listens(){return{"backend.widget.initialized":"onWidgetInitialized"}}onWidgetInitialized(e,t){e===document.getElementById("CodeEditor-formEditorPreview-_editor_preview")&&(this.widget=t,this.enablePreferences())}enablePreferences(){(0,n.M)("change");Object.entries({show_gutter:"showGutter",highlight_active_line:"highlightActiveLine",use_hard_tabs:"!useSoftTabs",display_indent_guides:"displayIndentGuides",show_invisibles:"showInvisibles",show_print_margin:"showPrintMargin",show_minimap:"showMinimap",enable_folding:"codeFolding",bracket_colors:"bracketColors",show_colors:"showColors"}).forEach(([e,t])=>{this.element(e).addEventListener("change",e=>{this.widget.setConfig(t.replace(/^!/,""),/^!/.test(t)?!e.target.checked:e.target.checked)})}),this.element("theme").addEventListener("$change",e=>{this.widget.loadTheme(e.target.value)}),this.element("font_size").addEventListener("$change",e=>{this.widget.setConfig("fontSize",e.target.value)}),this.element("tab_size").addEventListener("$change",e=>{this.widget.setConfig("tabSize",e.target.value)}),this.element("word_wrap").addEventListener("$change",e=>{const{value:t}=e.target;switch(t){case"off":this.widget.setConfig("wordWrap",!1);break;case"fluid":this.widget.setConfig("wordWrap","fluid");break;default:this.widget.setConfig("wordWrap",parseInt(t,10))}}),document.querySelectorAll("[data-switch-lang]").forEach(e=>{e.addEventListener("click",t=>{t.preventDefault();const i=e.dataset.switchLang,n=document.querySelector(`[data-lang-snippet="${i}"]`);n&&(this.widget.setValue(n.textContent.trim()),this.widget.setLanguage(i))})}),this.widget.events.once("create",()=>{const e=new MouseEvent("click");document.querySelector('[data-switch-lang="css"]').dispatchEvent(e)})}element(e){return document.getElementById(`Form-field-Preference-editor_${e}`)}}e.addPlugin("backend.preferences",t)})(window.Snowboard)}},function(e){e.O(0,[810],function(){return t=449,e(e.s=t);var t});e.O()}]); No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if there are source files that generate this bundle and verify build configuration

# Look for webpack config files
echo "=== Webpack Config Files ==="
fd -t f 'webpack\.config\.(js|ts|mjs|cjs)' 

# Check for build scripts in package.json
echo -e "\n=== Build Scripts ==="
fd -t f 'package\.json' --max-depth 3 --exec jq -r '.scripts // {} | select(length > 0) | to_entries[] | select(.key | test("build|bundle|compile|watch")) | "\(.key): \(.value)"' {} \;

# Look for source files in the same directory
echo -e "\n=== Files in modules/backend/assets/js/preferences/ ==="
ls -la modules/backend/assets/js/preferences/ 2>/dev/null || echo "Directory not found"

# Look for .gitignore entries related to build output
echo -e "\n=== .gitignore entries for build output ==="
rg -n 'dist|build|bundle|/assets/' .gitignore 2>/dev/null | head -20

# Check if there's a source version of this file
echo -e "\n=== Looking for source versions ==="
fd -t f 'preferences' modules/backend/assets -E 'node_modules' 

# Check the git history to see if this file was ever different
echo -e "\n=== Git info on the file ==="
git log --oneline -5 -- modules/backend/assets/js/preferences/preferences.js 2>/dev/null || echo "No git history available"

Repository: wintercms/winter

Length of output: 146


🏁 Script executed:

#!/bin/bash
# Simpler check for webpack config and build setup

# Look for webpack config files (faster)
echo "=== Webpack Config Files ==="
find . -maxdepth 3 -name "webpack.config.*" -type f 2>/dev/null | head -10

# Look for package.json and extract build scripts
echo -e "\n=== Build Scripts in package.json ==="
find . -maxdepth 3 -name "package.json" -type f 2>/dev/null | head -5 | while read pf; do
  echo "File: $pf"
  jq '.scripts | to_entries[] | select(.key | test("build|bundle|compile")) | "\(.key): \(.value)"' "$pf" 2>/dev/null
done

# Check .gitignore
echo -e "\n=== .gitignore content (first 50 lines) ==="
head -50 .gitignore 2>/dev/null || echo ".gitignore not found"

# Check directory structure of the assets folder
echo -e "\n=== Files in modules/backend/assets/js/preferences/ ==="
find modules/backend/assets/js/preferences/ -type f 2>/dev/null | head -20

# Look for any source files nearby
echo -e "\n=== Nearby source JavaScript files ==="
find modules/backend/assets/js/ -name "*.js" -type f 2>/dev/null | head -20

Repository: wintercms/winter

Length of output: 1863


🏁 Script executed:

#!/bin/bash
# Check actual build scripts and look for source files

echo "=== ./modules/backend/package.json ==="
cat ./modules/backend/package.json

echo -e "\n=== Looking for src or source directories ==="
find modules/backend -maxdepth 3 -type d \( -name "src" -o -name "source" -o -name "ts" \) 2>/dev/null

echo -e "\n=== Looking for TypeScript files ==="
find modules/backend -name "*.ts" -o -name "*.tsx" 2>/dev/null | head -10

echo -e "\n=== Check if preferences file is truly the only one in that dir ==="
stat modules/backend/assets/js/preferences/preferences.js 2>/dev/null | grep -E "File|Access|Modify|Change"

echo -e "\n=== Look for any .js source (non-minified) in assets ==="
find modules/backend/assets/js -name "*.js" ! -name "*.min.js" -type f 2>/dev/null | head -20

Repository: wintercms/winter

Length of output: 2550


🏁 Script executed:

#!/bin/bash
# Look for laravel-mix config and any preferences source files

echo "=== Looking for webpack.mix.js or laravel-mix config ==="
find . -maxdepth 3 -name "webpack.mix.js" -o -name "mix.js" 2>/dev/null

echo -e "\n=== Looking for ANY source files with 'preferences' in the name ==="
find . -type f \( -name "*preferences*" -o -name "*preference*" \) ! -path "*/node_modules/*" 2>/dev/null

echo -e "\n=== Check root directory for build config ==="
ls -la webpack.* laravel-* mix.* 2>/dev/null || echo "No webpack/mix files in root"

echo -e "\n=== Check modules/backend root for build config ==="
ls -la modules/backend/webpack.* modules/backend/laravel-* modules/backend/mix.* 2>/dev/null || echo "No webpack/mix files in modules/backend"

echo -e "\n=== Looking for vue or .js source files that might compile to preferences.js ==="
find modules/backend -type f \( -name "*.vue" \) ! -path "*/node_modules/*" 2>/dev/null | head -20

Repository: wintercms/winter

Length of output: 570


🏁 Script executed:

#!/bin/bash
# Check recent file modifications to understand context

echo "=== Check when preferences.js was last modified ==="
stat modules/backend/assets/js/preferences/preferences.js 2>/dev/null | grep -E "Modify|Access|File"

echo -e "\n=== Look at the file size ==="
wc -c < modules/backend/assets/js/preferences/preferences.js

echo -e "\n=== Check for any build scripts in package.json ==="
jq '.scripts' ./modules/backend/package.json

echo -e "\n=== Check root package.json for build scripts ==="
jq '.scripts' ./package.json 2>/dev/null | head -30

echo -e "\n=== Check if there's a resources or src directory at repo root ==="
find . -maxdepth 2 -type d \( -name "resources" -o -name "src" \) ! -path "*/node_modules/*" 2>/dev/null

Repository: wintercms/winter

Length of output: 329


Minified webpack bundle without source files must be removed from source control.

This is a webpack-generated bundle (self.webpackChunk_wintercms_wn_backend_module), but critically, no source file exists anywhere in the repository. The package.json includes laravel-mix as a devDependency but defines no build scripts and no webpack configuration file is present.

Committing minified build artifacts to source control causes several problems:

  • Impossible to review: Minified code cannot be meaningfully reviewed for correctness or security.
  • Unmaintainable: Changes must be made in missing source files; this committed artifact is redundant.
  • Merge conflicts: Minified files frequently create difficult-to-resolve conflicts.
  • No build process: Without build configuration, this file cannot be regenerated.

Required actions:

  1. Locate or reconstruct the original source file for this module.
  2. Set up the webpack/laravel-mix build configuration.
  3. Remove modules/backend/assets/js/preferences/preferences.js from source control and add build output to .gitignore.
  4. Ensure the build process generates this file during CI/CD or deployment.
🧰 Tools
🪛 Biome (2.1.2)

[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)


[error] 1-1: This variable is used before its declaration.

The variable is declared here:

(lint/correctness/noInvalidUseBeforeDeclaration)

'yaml_emit_file|yaml_parse|yaml_parse_file|yaml_parse_url|yaz_addinfo|yaz_ccl_conf|yaz_ccl_parse|yaz_close|yaz_connect|yaz_database|'+'yaz_element|yaz_errno|yaz_error|yaz_es|yaz_es_result|yaz_get_option|yaz_hits|yaz_itemorder|yaz_present|yaz_range|yaz_record|yaz_scan|'+'yaz_scan_result|yaz_schema|yaz_search|yaz_set_option|yaz_sort|yaz_syntax|yaz_wait|yp_all|yp_cat|yp_err_string|yp_errno|yp_first|'+'yp_get_default_domain|yp_master|yp_match|yp_next|yp_order|zend_logo_guid|zend_thread_id|zend_version|zip_close|zip_entry_close|'+'zip_entry_compressedsize|zip_entry_compressionmethod|zip_entry_filesize|zip_entry_name|zip_entry_open|zip_entry_read|zip_open|zip_read|'+'ziparchive|ziparchive_addemptydir|ziparchive_addfile|ziparchive_addfromstring|ziparchive_close|ziparchive_deleteindex|'+'ziparchive_deletename|ziparchive_extractto|ziparchive_getarchivecomment|ziparchive_getcommentindex|ziparchive_getcommentname|'+
'ziparchive_getfromindex|ziparchive_getfromname|ziparchive_getnameindex|ziparchive_getstatusstring|ziparchive_getstream|'+'ziparchive_locatename|ziparchive_open|ziparchive_renameindex|ziparchive_renamename|ziparchive_setCommentName|ziparchive_setarchivecomment|'+'ziparchive_setcommentindex|ziparchive_statindex|ziparchive_statname|ziparchive_unchangeall|ziparchive_unchangearchive|'+'ziparchive_unchangeindex|ziparchive_unchangename|zlib_get_coding_type').split('|'));var keywords=lang.arrayToMap(('abstract|and|array|as|break|case|catch|class|clone|const|continue|declare|default|do|else|elseif|enddeclare|endfor|endforeach|endif|'+'endswitch|endwhile|extends|final|for|foreach|function|global|goto|if|implements|interface|instanceof|namespace|new|or|private|protected|'+'public|static|switch|throw|trait|try|use|var|while|xor').split('|'));var languageConstructs=lang.arrayToMap(('die|echo|empty|exit|eval|include|include_once|isset|list|require|require_once|return|print|unset').split('|'));var builtinConstants=lang.arrayToMap(
('true|TRUE|false|FALSE|null|NULL|__CLASS__|__DIR__|../vendor/ace/mode-php.js|__LINE__|__METHOD__|__FUNCTION__|__NAMESPACE__').split('|'));var builtinVariables=lang.arrayToMap(('$GLOBALS|$_SERVER|$_GET|$_POST|$_FILES|$_REQUEST|$_SESSION|$_ENV|$_COOKIE|$php_errormsg|$HTTP_RAW_POST_DATA|'+'$http_response_header|$argc|$argv').split('|'));var builtinFunctionsDeprecated=lang.arrayToMap(('key_exists|cairo_matrix_create_scale|cairo_matrix_create_translate|call_user_method|call_user_method_array|com_addref|com_get|'+'com_invoke|com_isenum|com_load|com_release|com_set|connection_timeout|cubrid_load_from_glo|cubrid_new_glo|cubrid_save_to_glo|'+'cubrid_send_glo|define_syslog_variables|dl|ereg|ereg_replace|eregi|eregi_replace|hw_documentattributes|hw_documentbodytag|'+'hw_documentsize|hw_outputdocument|imagedashedline|maxdb_bind_param|maxdb_bind_result|maxdb_client_encoding|maxdb_close_long_data|'+
('true|TRUE|false|FALSE|null|NULL|__CLASS__|__DIR__|../ace/mode-php.js|__LINE__|__METHOD__|__FUNCTION__|__NAMESPACE__').split('|'));var builtinVariables=lang.arrayToMap(('$GLOBALS|$_SERVER|$_GET|$_POST|$_FILES|$_REQUEST|$_SESSION|$_ENV|$_COOKIE|$php_errormsg|$HTTP_RAW_POST_DATA|'+'$http_response_header|$argc|$argv').split('|'));var builtinFunctionsDeprecated=lang.arrayToMap(('key_exists|cairo_matrix_create_scale|cairo_matrix_create_translate|call_user_method|call_user_method_array|com_addref|com_get|'+'com_invoke|com_isenum|com_load|com_release|com_set|connection_timeout|cubrid_load_from_glo|cubrid_new_glo|cubrid_save_to_glo|'+'cubrid_send_glo|define_syslog_variables|dl|ereg|ereg_replace|eregi|eregi_replace|hw_documentattributes|hw_documentbodytag|'+'hw_documentsize|hw_outputdocument|imagedashedline|maxdb_bind_param|maxdb_bind_result|maxdb_client_encoding|maxdb_close_long_data|'+
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Critical: File path incorrectly placed in PHP builtinConstants array.

The string ../ace/mode-php.js appears in the builtinConstants array, which should only contain PHP magic constants like __CLASS__, __DIR__, __FILE__, etc. This looks like the __FILE__ constant was accidentally replaced during a find-replace operation when updating asset paths.

This will break syntax highlighting and code intelligence for the __FILE__ magic constant in PHP code.

🔎 Proposed fix
-('true|TRUE|false|FALSE|null|NULL|__CLASS__|__DIR__|../ace/mode-php.js|__LINE__|__METHOD__|__FUNCTION__|__NAMESPACE__').split('|'));
+('true|TRUE|false|FALSE|null|NULL|__CLASS__|__DIR__|__FILE__|__LINE__|__METHOD__|__FUNCTION__|__NAMESPACE__').split('|'));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
('true|TRUE|false|FALSE|null|NULL|__CLASS__|__DIR__|../ace/mode-php.js|__LINE__|__METHOD__|__FUNCTION__|__NAMESPACE__').split('|'));var builtinVariables=lang.arrayToMap(('$GLOBALS|$_SERVER|$_GET|$_POST|$_FILES|$_REQUEST|$_SESSION|$_ENV|$_COOKIE|$php_errormsg|$HTTP_RAW_POST_DATA|'+'$http_response_header|$argc|$argv').split('|'));var builtinFunctionsDeprecated=lang.arrayToMap(('key_exists|cairo_matrix_create_scale|cairo_matrix_create_translate|call_user_method|call_user_method_array|com_addref|com_get|'+'com_invoke|com_isenum|com_load|com_release|com_set|connection_timeout|cubrid_load_from_glo|cubrid_new_glo|cubrid_save_to_glo|'+'cubrid_send_glo|define_syslog_variables|dl|ereg|ereg_replace|eregi|eregi_replace|hw_documentattributes|hw_documentbodytag|'+'hw_documentsize|hw_outputdocument|imagedashedline|maxdb_bind_param|maxdb_bind_result|maxdb_client_encoding|maxdb_close_long_data|'+
('true|TRUE|false|FALSE|null|NULL|__CLASS__|__DIR__|__FILE__|__LINE__|__METHOD__|__FUNCTION__|__NAMESPACE__').split('|'));var builtinVariables=lang.arrayToMap(('$GLOBALS|$_SERVER|$_GET|$_POST|$_FILES|$_REQUEST|$_SESSION|$_ENV|$_COOKIE|$php_errormsg|$HTTP_RAW_POST_DATA|'+'$http_response_header|$argc|$argv').split('|'));var builtinFunctionsDeprecated=lang.arrayToMap(('key_exists|cairo_matrix_create_scale|cairo_matrix_create_translate|call_user_method|call_user_method_array|com_addref|com_get|'+'com_invoke|com_isenum|com_load|com_release|com_set|connection_timeout|cubrid_load_from_glo|cubrid_new_glo|cubrid_save_to_glo|'+'cubrid_send_glo|define_syslog_variables|dl|ereg|ereg_replace|eregi|eregi_replace|hw_documentattributes|hw_documentbodytag|'+'hw_documentsize|hw_outputdocument|imagedashedline|maxdb_bind_param|maxdb_bind_result|maxdb_client_encoding|maxdb_close_long_data|'+
🤖 Prompt for AI Agents
In modules/backend/assets/vendor/ace-codeeditor/build-min.js around line 1442,
the builtinConstants array contains an incorrect token "../ace/mode-php.js"
instead of the PHP magic constant "__FILE__"; remove the erroneous path entry
and restore the correct "__FILE__" token in the array so only PHP magic
constants remain (ensure formatting/quoting matches surrounding entries and
update any adjacent concatenation so the split('|') still produces the correct
list).

Comment on lines +2311 to 2314
$(document).render(function(){$('[data-control="ace-codeeditor"]').codeEditor()});+function(exports){if(exports.ace&&typeof exports.ace.require=='function'){var emmetExt=exports.ace.require('ace/ext/emmet')
if(emmetExt&&emmetExt.AceEmmetEditor&&emmetExt.AceEmmetEditor.prototype.getSyntax){var coreGetSyntax=emmetExt.AceEmmetEditor.prototype.getSyntax
emmetExt.AceEmmetEditor.prototype.getSyntax=function(){var $syntax=$.proxy(coreGetSyntax,this)()
return $syntax=='twig'?'html':$syntax};}}}(window)}(window.jQuery); No newline at end of file
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# Search for data-control attributes related to code editors across the codebase
rg -n 'data-control\s*=\s*["\'].*codeeditor' --type-list | grep -E '\.(php|js|html|vue)' -q && echo "Has codeeditor references" || true
rg -n 'data-control\s*=\s*["\'].*codeeditor' -C 2

# Also search for Monaco references to understand migration context
rg -n 'monaco|Monaco' --type-list | head -20

Repository: wintercms/winter

Length of output: 282


🏁 Script executed:

# Search for references to the old 'codeeditor' data attribute without ace prefix
rg -n 'data-control=["\']codeeditor["\']' -C 2

# Search for 'ace-codeeditor' references to confirm new selector usage
rg -n 'ace-codeeditor' -C 1

Repository: wintercms/winter

Length of output: 128


🏁 Script executed:

# Check for JavaScript initialization code that may reference these selectors
rg -n '\$.*codeeditor|\$.*ace-codeeditor|codeEditor\(\)' --type=js -C 3 | head -100

Repository: wintercms/winter

Length of output: 50372


🏁 Script executed:

# Search for old 'codeeditor' selector in data-control attributes specifically (case-insensitive)
rg 'data-control\s*=\s*["\']codeeditor["\']' -i

# Search for all data-control selectors to see what's registered
rg 'data-control\s*=\s*["\']' | grep -E '(ace-|monaco|codeeditor)' | head -20

Repository: wintercms/winter

Length of output: 224


🏁 Script executed:

# Check if there are any form widget definitions that might reference old selectors
fd -e php -e js | xargs rg -l 'codeeditor|code-editor' | head -20

Repository: wintercms/winter

Length of output: 989


🏁 Script executed:

# Simple search for old selector pattern (without ace- prefix)
rg 'data-control.*codeeditor' --no-config | grep -v 'ace-codeeditor'

# Also check the form widget class definition
cat modules/backend/formwidgets/CodeEditor.php | head -50

Repository: wintercms/winter

Length of output: 1965


🏁 Script executed:

# Check the partial template file to see what selector is used
cat modules/backend/formwidgets/codeeditor/partials/_codeeditor.php

Repository: wintercms/winter

Length of output: 2288


🏁 Script executed:

# Verify the Emmet integration code in the source file
sed -n '444,455p' modules/backend/assets/vendor/ace-codeeditor/codeeditor.js

Repository: wintercms/winter

Length of output: 459


Selector mismatch breaks the Ace code editor initialization.

The vendored Ace code attempts to initialize elements with data-control="ace-codeeditor" (lines 2311-2314), but the form widget template at modules/backend/formwidgets/codeeditor/partials/_codeeditor.php still renders data-control="codeeditor". This mismatch prevents the initialization from working. Additionally, other parts of the codebase still reference the old [data-control="codeeditor"] selector (e.g., modules/cms/assets/js/winter.cmspage.js, modules/system/assets/ui/js/drag.value.js), creating potential breakage across multiple features.

The form widget template must be updated to use data-control="ace-codeeditor", and any dependent selectors in related modules must be updated accordingly.

Comment on lines +201 to +235
/**
* Loads a theme via AJAX.
* Supports both tmTheme (XML) and JSON formats.
*/
public function onLoadTheme()
{
$theme = post('theme');

if (empty($theme)) {
throw new ApplicationException('No theme specified');
}
if (!preg_match('/^[a-z\-\_]+$/i', $theme)) {
throw new ApplicationException('Invalid theme name');
}

$themeDir = __DIR__ . '/codeeditor/assets/themes/';

// Try JSON format first (modern), then fall back to tmTheme (legacy)
$jsonPath = $themeDir . $theme . '.json';
$tmThemePath = $themeDir . $theme . '.tmTheme';

if (File::exists($jsonPath)) {
return [
'format' => 'json',
'data' => File::get($jsonPath),
];
} elseif (File::exists($tmThemePath)) {
return [
'format' => 'tmTheme',
'data' => File::get($tmThemePath),
];
}

throw new ApplicationException(sprintf('Theme "%s" not found', $theme));
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find modules/backend/formwidgets/codeeditor/assets/themes/ -type f \( -name "*.json" -o -name "*.tmTheme" \) 2>/dev/null | sort

Repository: wintercms/winter

Length of output: 2650


Theme loading needs case-normalization to work reliably on case-sensitive filesystems.

The onLoadTheme() method validates input appropriately and constructs paths safely, but has a case-sensitivity issue: the regex /^[a-z\-\_]+$/i (with the i flag) accepts mixed-case input like "Monokai" or "THEMES", but all theme files in the directory are lowercase (e.g., monokai.tmTheme, one-dark-pro.json, kr_theme.tmTheme).

On case-sensitive filesystems (Linux), theme loading fails if users provide mixed-case input. On case-insensitive filesystems (Windows/macOS), it works by accident.

Fix: Convert the theme name to lowercase before path construction:

$theme = strtolower(post('theme'));

This ensures consistent behavior across all filesystems.

🤖 Prompt for AI Agents
In modules/backend/formwidgets/CodeEditor.php around lines 201 to 235, the
onLoadTheme() handler accepts mixed-case theme names (regex uses /i) but theme
files are lowercase, causing failures on case-sensitive filesystems; normalize
the input by lowercasing the posted theme before validation/path construction
(e.g., call strtolower on post('theme')) and then proceed with the existing
validation and File::exists checks so paths match actual filenames.

Comment on lines +644 to +649
findAll(search, matchCase) {
const searchString = (search instanceof RegExp) ? search.source : search;
const matches = this.model.findMatches(searchString, true, (search instanceof RegExp), matchCase || false, null, true, 1);

// CODEEDITOR NO CONFLICT
// =================
return matches;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

findAll() only returns one match due to limit parameter.

The findMatches() call has limitResultCount set to 1, which contradicts the method's purpose of finding all matches.

🔎 Proposed fix
         findAll(search, matchCase) {
             const searchString = (search instanceof RegExp) ? search.source : search;
-            const matches = this.model.findMatches(searchString, true, (search instanceof RegExp), matchCase || false, null, true, 1);
+            const matches = this.model.findMatches(searchString, true, (search instanceof RegExp), matchCase || false, null, true);

             return matches;
         }

Note: Omitting the last parameter removes the limit, returning all matches.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
findAll(search, matchCase) {
const searchString = (search instanceof RegExp) ? search.source : search;
const matches = this.model.findMatches(searchString, true, (search instanceof RegExp), matchCase || false, null, true, 1);
// CODEEDITOR NO CONFLICT
// =================
return matches;
}
findAll(search, matchCase) {
const searchString = (search instanceof RegExp) ? search.source : search;
const matches = this.model.findMatches(searchString, true, (search instanceof RegExp), matchCase || false, null, true);
return matches;
}
🤖 Prompt for AI Agents
In modules/backend/formwidgets/codeeditor/assets/js/codeeditor.js around lines
644 to 649, the call to this.model.findMatches sets the last parameter
limitResultCount to 1, causing findAll() to return only one match; remove the
final numeric argument (i.e., omit the limitResultCount parameter) so
findMatches is called without a limit and returns all matches.

Comment on lines +679 to +689
return this.model.pushEditOperations(
this.editor.getSelections(),
[
{
forceMoveMarkers: false,
range: new monaco.Range(found.range.startLineNumber, found.range.startColumn, found.range.endLineNumber, found.range.endColumn),
text: (align) ? this.alignText(replace, search.startColumn) : replace,
},
],
);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Incorrect property access for alignment when search is String/RegExp.

On line 685, search.startColumn is used for alignment, but when search is a String or RegExp (not a Range), this property is undefined, causing the alignment to fail silently.

🔎 Proposed fix
             return this.model.pushEditOperations(
                 this.editor.getSelections(),
                 [
                     {
                         forceMoveMarkers: false,
                         range: new monaco.Range(found.range.startLineNumber, found.range.startColumn, found.range.endLineNumber, found.range.endColumn),
-                        text: (align) ? this.alignText(replace, search.startColumn) : replace,
+                        text: (align) ? this.alignText(replace, found.range.startColumn) : replace,
                     },
                 ],
             );
🤖 Prompt for AI Agents
In modules/backend/formwidgets/codeeditor/assets/js/codeeditor.js around lines
679 to 689, the code uses search.startColumn for alignment which is undefined
when search is a String or RegExp; replace that usage with the actual matched
range column (e.g., found.range.startColumn) so alignment always gets a valid
column value; update the align call to pass found.range.startColumn (or
otherwise guard by checking for a Range and fallback to found.range.startColumn)
instead of search.startColumn.

Comment on lines +1230 to +1255
enableStatusBarActions() {
if (!this.statusBar) {
return;
}

const fullscreen = this.statusBar.querySelector('[data-full-screen]');
fullscreen.addEventListener('click', () => {
if (!this.fullscreen) {
this.element.requestFullscreen({
navigationUI: 'hide',
}).then(() => {
this.fullscreen = true;
fullscreen.classList.add('active');
this.element.addEventListener('fullscreenchange', this.callbacks.fullScreenChange);
// Use Monaco's layout() instead of refresh() to avoid disposing the editor
if (this.editor) {
window.requestAnimationFrame(() => {
this.editor.layout();
});
}
});
} else {
document.exitFullscreen();
}
});
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Click listener on fullscreen button accumulates on editor recreation.

enableStatusBarActions() is called every time createEditor() runs (on visibility changes), but there's no cleanup of the previous click listener on the fullscreen button. This causes multiple listeners to accumulate.

🔎 Proposed fix

Track and clean up the fullscreen listener:

+        // In construct(), add:
+        this.fullscreenClickHandler = null;

         enableStatusBarActions() {
             if (!this.statusBar) {
                 return;
             }

             const fullscreen = this.statusBar.querySelector('[data-full-screen]');
+            
+            // Remove previous listener if exists
+            if (this.fullscreenClickHandler) {
+                fullscreen.removeEventListener('click', this.fullscreenClickHandler);
+            }
+            
+            this.fullscreenClickHandler = () => {
-            fullscreen.addEventListener('click', () => {
                 if (!this.fullscreen) {
                     // ... existing code
                 } else {
                     document.exitFullscreen();
                 }
-            });
+            };
+            
+            fullscreen.addEventListener('click', this.fullscreenClickHandler);
         }

Comment on lines +1419 to +1448
normalizeKeyBinding(keyCode) {
let keyBinding = {
key: null,
ctrl: false,
alt: false,
shift: false,
};

if (typeof keyCode === 'string') {
if (keyCode.startsWith('Shift+Ctrl+')) {
keyBinding.key = keyCode.replace('Shift+Ctrl+', '');
keyBinding.shift = true;
keyBinding.ctrl = true;
} else if (keyCode.startsWith('Shift+Alt+')) {
keyBinding.key = keyCode.replace('Shift+Alt+', '');
keyBinding.shift = true;
keyBinding.alt = true;
} else if (keyCode.startsWith('Ctrl+')) {
keyBinding.key = keyCode.replace('Ctrl+', '');
keyBinding.ctrl = true;
} else if (keyCode.startsWith('Alt+')) {
keyBinding.key = keyCode.replace('Alt+', '');
keyBinding.alt = true;
}
} else {
keyBinding = { ...keyBinding, ...keyCode };
}

return keyBinding;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Incomplete keybinding parsing for some combinations.

The parser doesn't handle:

  • Plain keys without modifiers (e.g., "Escape") — key remains null
  • Shift+ alone without Ctrl/Alt
  • Ctrl+Alt+ combinations
  • Alternative orderings like Ctrl+Shift+
🔎 Proposed fix
         normalizeKeyBinding(keyCode) {
             let keyBinding = {
                 key: null,
                 ctrl: false,
                 alt: false,
                 shift: false,
             };

             if (typeof keyCode === 'string') {
-                if (keyCode.startsWith('Shift+Ctrl+')) {
-                    keyBinding.key = keyCode.replace('Shift+Ctrl+', '');
-                    keyBinding.shift = true;
-                    keyBinding.ctrl = true;
-                } else if (keyCode.startsWith('Shift+Alt+')) {
-                    keyBinding.key = keyCode.replace('Shift+Alt+', '');
-                    keyBinding.shift = true;
-                    keyBinding.alt = true;
-                } else if (keyCode.startsWith('Ctrl+')) {
-                    keyBinding.key = keyCode.replace('Ctrl+', '');
-                    keyBinding.ctrl = true;
-                } else if (keyCode.startsWith('Alt+')) {
-                    keyBinding.key = keyCode.replace('Alt+', '');
-                    keyBinding.alt = true;
-                }
+                const parts = keyCode.split('+');
+                keyBinding.key = parts.pop(); // Last part is the key
+                parts.forEach((mod) => {
+                    const modifier = mod.toLowerCase();
+                    if (modifier === 'ctrl' || modifier === 'cmd') keyBinding.ctrl = true;
+                    else if (modifier === 'alt') keyBinding.alt = true;
+                    else if (modifier === 'shift') keyBinding.shift = true;
+                });
             } else {
                 keyBinding = { ...keyBinding, ...keyCode };
             }

             return keyBinding;
         }
🤖 Prompt for AI Agents
In modules/backend/formwidgets/codeeditor/assets/js/codeeditor.js around lines
1419 to 1448, the normalizeKeyBinding function fails to parse many valid string
keybindings (plain keys like "Escape", single "Shift+Key", "Ctrl+Alt+Key", and
modifier permutations like "Ctrl+Shift+Key"), so replace the current startsWith
chain with a robust parser: split the input string on '+' into parts, iterate
parts to set ctrl/alt/shift flags when matching modifier names
(case-insensitive), and treat the remaining non-modifier part (if any) as the
key; ensure empty or missing key parts result in key = null and preserve
existing behavior for non-string inputs by merging the parsed result into the
default keyBinding object.

}

$this->addJs('/modules/backend/formwidgets/codeeditor/assets/js/build-min.js', 'core');
$this->addJs('/modules/backend/assets/vendor/ace-codeeditor/build-min.js', 'core');
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify the ace-codeeditor asset exists at the new path

# Check if the ace-codeeditor build file exists
if [ -f "modules/backend/assets/vendor/ace-codeeditor/build-min.js" ]; then
  echo "✓ Asset file exists at modules/backend/assets/vendor/ace-codeeditor/build-min.js"
  ls -lh "modules/backend/assets/vendor/ace-codeeditor/build-min.js"
else
  echo "✗ Asset file NOT found at modules/backend/assets/vendor/ace-codeeditor/build-min.js"
  echo "Searching for ace-codeeditor files in the repository..."
  fd -t f "build-min.js" modules/backend/assets/vendor/
fi

# Check if there are any open issues or TODOs tracking the migration of RichEditor to Monaco
echo ""
echo "Checking for TODOs or FIXMEs related to RichEditor and Monaco migration..."
rg -i "TODO|FIXME" modules/backend/formwidgets/RichEditor.php

Repository: wintercms/winter

Length of output: 323


🏁 Script executed:

#!/bin/bash
# Search for tracking of temporary Ace retention in the codebase

echo "=== Searching for Ace/Monaco migration tracking in backend formwidgets ==="
rg -i "temporary|temp|ace.*monaco|monaco.*ace|fixme|todo" modules/backend/formwidgets/ -A 2 -B 2

echo ""
echo "=== Searching in RichEditor.php for any comments about Ace vs Monaco ==="
cat -n modules/backend/formwidgets/RichEditor.php | head -200

echo ""
echo "=== Checking commit messages or PR history for migration context ==="
rg "temporary.*ace|ace.*retained" --max-count=10 -i

Repository: wintercms/winter

Length of output: 50375


🏁 Script executed:

#!/bin/bash
# Read RichEditor.php to check for comments about Ace editor

cat -n modules/backend/formwidgets/RichEditor.php | head -180

# Also search for any comments mentioning Ace, Monaco, or temporary
echo ""
echo "=== Searching for comments about Ace/Monaco in RichEditor.php ==="
rg -i "ace|monaco|editor|temporary" modules/backend/formwidgets/RichEditor.php -B 2 -A 2

Repository: wintercms/winter

Length of output: 11446


🏁 Script executed:

#!/bin/bash
# Search for any migration tracking across the entire codebase

echo "=== Searching for Monaco/Ace migration references ==="
rg -i "monaco|ace.*temporary|temporary.*ace" --max-count=20

echo ""
echo "=== Checking MarkdownEditor for similar Ace loading ==="
rg -A 5 -B 5 "ace-codeeditor" modules/backend/formwidgets/MarkdownEditor.php

echo ""
echo "=== Looking for any issue/TODO tracking in commit messages or PR files ==="
rg -i "TODO|FIXME|issue" modules/backend/formwidgets/ -l | head -10

Repository: wintercms/winter

Length of output: 50374


The Ace editor asset path is correct, but document that this Ace retention is temporary.

The asset file at /modules/backend/assets/vendor/ace-codeeditor/build-min.js exists and loads correctly. However, RichEditor still retains Ace instead of migrating to Monaco, with no inline documentation or tracking indicating this is a temporary measure. Add a comment in the code or create/link a follow-up issue to ensure this temporary retention doesn't become permanent technical debt.

🤖 Prompt for AI Agents
In modules/backend/formwidgets/RichEditor.php around line 157, the code adds the
Ace editor asset but lacks documentation that keeping Ace is a temporary
measure; add a clear inline comment on that line stating that Ace is retained
temporarily pending migration to Monaco, include the rationale and expected
timeline or milestone, and either add a TODO with a tracker/issue ID (or create
one and reference it) so the retention is traceable and won't become permanent
technical debt.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement PRs that implement a new feature or substantial change

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Switch code editor to Monaco

4 participants