From 2b8bdcf87cef7725c3185a66d4b88d3eb844c818 Mon Sep 17 00:00:00 2001 From: Anna de Azevedo Maciel Date: Sat, 10 Oct 2026 14:18:13 -0300 Subject: [PATCH 1/3] refactor: made builder class for modal object --- docs/API-Reference/JSUtils/Session.md | 2 +- docs/API-Reference/language/CodeInspection.md | 29 +++++-- docs/API-Reference/project/ProjectManager.md | 14 ++++ docs/API-Reference/widgets/NotificationUI.md | 8 +- package-lock.json | 32 +++++-- phoenix-builder-mcp/package-lock.json | 3 + src-mdviewer/package-lock.json | 8 +- src/JSUtils/ModalBuilder.js | 81 ++++++++++++++++++ src/JSUtils/SelectorStrategy.js | 70 ++++++++++++++++ src/editor/CSSInlineEditor.js | 54 ++---------- src/main.js | 84 +++++++------------ 11 files changed, 261 insertions(+), 124 deletions(-) create mode 100644 src/JSUtils/ModalBuilder.js create mode 100644 src/JSUtils/SelectorStrategy.js diff --git a/docs/API-Reference/JSUtils/Session.md b/docs/API-Reference/JSUtils/Session.md index 82b4037c70..bc38f2b52b 100644 --- a/docs/API-Reference/JSUtils/Session.md +++ b/docs/API-Reference/JSUtils/Session.md @@ -243,7 +243,7 @@ the "currentIndex" property index of the hint the cursor is on, may be ### session.getJavascriptText() ⇒ String Get the javascript text of the file open in the editor for this Session. -For a javascript file, this is just the text of the file. For an HTML file, +For a javascript file, this is just the text of the file. For an HTML/PHP file, this will be only the text in the script tags. This is so that we can pass just the javascript text to tern, and avoid confusing it with HTML tags, since it only knows how to parse javascript. diff --git a/docs/API-Reference/language/CodeInspection.md b/docs/API-Reference/language/CodeInspection.md index 49c0e9d862..4927459778 100644 --- a/docs/API-Reference/language/CodeInspection.md +++ b/docs/API-Reference/language/CodeInspection.md @@ -111,17 +111,11 @@ Scrolls to the problem line -## run(providerName) -Run inspector applicable to current document. Updates status bar indicator and refreshes error list in -bottom panel. Does not run if inspection is disabled or if a providerName is given and does not -match the current doc's provider name. +## run() +Run inspectors for the current document and publish results from the latest unchanged scan. +Does not scan while inspection is disabled. Keeps valid fixes available until a current scan replaces them. **Kind**: global function - -| Param | Type | Description | -| --- | --- | --- | -| providerName | string | name of the provider that is requesting a run | - ## toggleEnabled(enabled, doNotSave) @@ -134,6 +128,23 @@ Enable or disable all inspection. | enabled | boolean | Enabled state. If omitted, the state is toggled. | | doNotSave | boolean | true if the preference should not be saved to user settings. This is generally for events triggered by project-level settings. | + + +## \_fixProblem(fixID) +Apply one fix if its document and text still match the displayed inspection results. + +**Kind**: global function + +| Param | Type | Description | +| --- | --- | --- | +| fixID | string | ID assigned to the displayed fix. | + + + +## \_fixAllProblems() +Apply all displayed fixes only to the document version that was inspected. + +**Kind**: global function ## Error : Object diff --git a/docs/API-Reference/project/ProjectManager.md b/docs/API-Reference/project/ProjectManager.md index 7406d91fa2..3d4be8d7a9 100644 --- a/docs/API-Reference/project/ProjectManager.md +++ b/docs/API-Reference/project/ProjectManager.md @@ -235,6 +235,20 @@ path lies outside the project, or if it doesn't exist. | --- | --- | --- | | entry | File \| Directory | File or Directory to show | + + +## isInFileTree(fullPath) ⇒ boolean +Whether the file tree currently has a node for the path. A file written outside the +FileSystem API is not in the tree until the watcher or a refresh adds it, and a file in a +folder that was never expanded is not in it either; `showInTree` expands the folders first. + +**Kind**: global function +**Returns**: boolean - true if the tree has the node + +| Param | Type | Description | +| --- | --- | --- | +| fullPath | string | Absolute path of a file or folder | + ## openProject([path]) ⇒ $.Promise diff --git a/docs/API-Reference/widgets/NotificationUI.md b/docs/API-Reference/widgets/NotificationUI.md index 3222a1d9bb..699038dd1a 100644 --- a/docs/API-Reference/widgets/NotificationUI.md +++ b/docs/API-Reference/widgets/NotificationUI.md @@ -119,7 +119,7 @@ let notification1 = NotificationUI.createFromTemplate( | title | string | The title for the notification. | | template | string \| Element | A string template or HTML Element to use as the dialog HTML. | | [elementID] | String | optional id string if provided will show the notification pointing to the element. If no element is specified, it will be managed as a generic notification. | -| [options] | Object | optional, supported * options are: * `allowedPlacements` - Optional String array with values restricting where the notification will be shown. Values can be a mix of `['top', 'bottom', 'left', 'right']` * `autoCloseTimeS` - Time in seconds after which the notification should be auto closed. Default is never. * `dismissOnClick` - when clicked, the notification is closed. Default is true(dismiss). * `toastStyle` - To style the toast notification for error, warning, info etc. Can be one of `NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.*` or your own css class name. | +| [options] | Object | optional, supported * options are: * `allowedPlacements` - Optional String array with values restricting where the notification will be shown. Values can be a mix of `['top', 'bottom', 'left', 'right']` * `autoCloseTimeS` - Time in seconds after which the notification should be auto closed. Default is never. The countdown pauses while the mouse is over the notification. * `dismissOnClick` - when clicked, the notification is closed. Default is true(dismiss). * `toastStyle` - To style the toast notification for error, warning, info etc. Can be one of `NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.*` or your own css class name. | @@ -146,7 +146,7 @@ let notification1 = NotificationUI.createToastFromTemplate( "Title here", | --- | --- | --- | | title | string | The title for the notification. | | template | string \| Element | A string template or HTML Element to use as the dialog HTML. | -| [options] | Object | optional, supported * options are: * `autoCloseTimeS` - Time in seconds after which the notification should be auto closed. Default is never. * `dismissOnClick` - when clicked, the notification is closed. Default is true(dismiss). * `toastStyle` - To style the toast notification for error, warning, info etc. Can be one of `NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.*` or your own css class name. * `instantOpen` - To instantly open the popup without any open animation delays | +| [options] | Object | optional, supported * options are: * `autoCloseTimeS` - Time in seconds after which the notification should be auto closed. Default is never. The countdown pauses while the mouse is over the notification. * `dismissOnClick` - when clicked, the notification is closed. Default is true(dismiss). * `toastStyle` - To style the toast notification for error, warning, info etc. Can be one of `NotificationUI.NOTIFICATION_STYLES_CSS_CLASS.*` or your own css class name. * `instantOpen` - To instantly open the popup without any open animation delays | @@ -168,7 +168,7 @@ NotificationUI.showToastOn(document.getElementById("my-panel"), "Hello!", { | --- | --- | --- | | containerOrSelector | Element \| string | A DOM element or CSS selector for the parent container. The container should have `position: relative` or `absolute` so the toast is positioned correctly. | | template | string \| Element | HTML string or DOM Element for the toast content. | -| [options] | Object | optional, supported options: * `autoCloseTimeS` - Time in seconds after which the toast auto-closes. Default is 5. * `dismissOnClick` - If true, clicking the toast dismisses it. Default is true. | +| [options] | Object | optional, supported options: * `autoCloseTimeS` - Time in seconds after which the toast auto-closes. Default is 5. The countdown pauses while the mouse is over the toast. * `dismissOnClick` - If true, clicking the toast dismisses it. Default is true. | @@ -188,7 +188,7 @@ NotificationUI.showHUD("fa-solid fa-magnifying-glass-plus", "110%"); | --- | --- | --- | | iconClass | string | Font Awesome class string for the icon (e.g. "fa-solid fa-magnifying-glass-plus"). | | label | string | Text to display below the icon (e.g. "110%"). | -| [options] | Object | optional, supported options: * `autoCloseTimeS` - Time in seconds after which the HUD auto-closes. Default is 1. | +| [options] | Object | optional, supported options: * `autoCloseTimeS` - Time in seconds after which the HUD auto-closes. Default is 1. The countdown pauses while the mouse is over the HUD. | diff --git a/package-lock.json b/package-lock.json index b4e819d156..74a494b85b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "phoenix", - "version": "5.5.3-0", + "version": "5.5.4-0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "phoenix", - "version": "5.5.3-0", + "version": "5.5.4-0", "hasInstallScript": true, "dependencies": { "@bugsnag/js": "^7.18.0", @@ -1278,6 +1278,7 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, + "peer": true, "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -1299,7 +1300,8 @@ "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", - "dev": true + "dev": true, + "peer": true }, "node_modules/@types/normalize-package-data": { "version": "2.4.4", @@ -1368,6 +1370,7 @@ "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.304.tgz", "integrity": "sha512-Wq9d4qFYslYQBIan+riXotdyurtREc6v+HWdAwV6Y2JSDI9eJ1dkWm/fzYb2rCGgGnM3baiYuSK4Z6OLNKLzDw==", "license": "MIT", + "peer": true, "workspaces": [ "addons/*" ] @@ -1390,6 +1393,7 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", "dev": true, + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -4452,6 +4456,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, + "peer": true, "dependencies": { "@eslint/eslintrc": "^1.3.0", "@humanwhocodes/config-array": "^0.9.2", @@ -6151,6 +6156,7 @@ "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, + "peer": true, "dependencies": { "glob-watcher": "^5.0.3", "gulp-cli": "^2.2.0", @@ -10401,6 +10407,7 @@ "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "peer": true, "bin": { "prettier": "bin/prettier.cjs" }, @@ -12495,6 +12502,7 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", "dev": true, + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -14200,6 +14208,7 @@ "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", "dev": true, + "peer": true, "requires": { "@types/linkify-it": "^5", "@types/mdurl": "^2" @@ -14221,7 +14230,8 @@ "version": "17.0.8", "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.8.tgz", "integrity": "sha512-YofkM6fGv4gDJq78g4j0mMuGMkZVxZDgtU0JRdx6FgiJDG+0fY0GKVolOV8WqVmEhLCXkQRjwDdKyPxJp/uucg==", - "dev": true + "dev": true, + "peer": true }, "@types/normalize-package-data": { "version": "2.4.4", @@ -14273,7 +14283,8 @@ "@xterm/xterm": { "version": "6.1.0-beta.304", "resolved": "https://registry.npmjs.org/@xterm/xterm/-/xterm-6.1.0-beta.304.tgz", - "integrity": "sha512-Wq9d4qFYslYQBIan+riXotdyurtREc6v+HWdAwV6Y2JSDI9eJ1dkWm/fzYb2rCGgGnM3baiYuSK4Z6OLNKLzDw==" + "integrity": "sha512-Wq9d4qFYslYQBIan+riXotdyurtREc6v+HWdAwV6Y2JSDI9eJ1dkWm/fzYb2rCGgGnM3baiYuSK4Z6OLNKLzDw==", + "peer": true }, "accepts": { "version": "1.3.7", @@ -14289,7 +14300,8 @@ "version": "8.15.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", - "dev": true + "dev": true, + "peer": true }, "acorn-jsx": { "version": "5.3.2", @@ -16736,6 +16748,7 @@ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.19.0.tgz", "integrity": "sha512-SXOPj3x9VKvPe81TjjUJCYlV4oJjQw68Uek+AM0X4p+33dj2HY5bpTZOgnQHcG2eAm1mtCU9uNMnJi7exU/kYw==", "dev": true, + "peer": true, "requires": { "@eslint/eslintrc": "^1.3.0", "@humanwhocodes/config-array": "^0.9.2", @@ -18089,6 +18102,7 @@ "resolved": "https://registry.npmjs.org/gulp/-/gulp-4.0.2.tgz", "integrity": "sha512-dvEs27SCZt2ibF29xYgmnwwCYZxdxhQ/+LFWlbAW8y7jt68L/65402Lz3+CKy0Ov4rOs+NERmDq7YlZaDqUIfA==", "dev": true, + "peer": true, "requires": { "glob-watcher": "^5.0.3", "gulp-cli": "^2.2.0", @@ -21545,7 +21559,8 @@ "prettier": { "version": "3.2.5", "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", - "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==" + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", + "peer": true }, "pretty-hrtime": { "version": "1.0.3", @@ -23226,7 +23241,8 @@ "version": "4.9.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "dev": true + "dev": true, + "peer": true }, "typical": { "version": "7.3.0", diff --git a/phoenix-builder-mcp/package-lock.json b/phoenix-builder-mcp/package-lock.json index e19671cbc0..64359b6072 100644 --- a/phoenix-builder-mcp/package-lock.json +++ b/phoenix-builder-mcp/package-lock.json @@ -370,6 +370,7 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", + "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -574,6 +575,7 @@ "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.9.tgz", "integrity": "sha512-Eaw2YTGM6WOxA6CXbckaEvslr2Ne4NFsKrvc0v97JD5awbmeBLO5w9Ho9L9kmKonrwF9RJlW6BxT1PVv/agBHQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=16.9.0" } @@ -1144,6 +1146,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/src-mdviewer/package-lock.json b/src-mdviewer/package-lock.json index de8bd05c37..83a12e084e 100644 --- a/src-mdviewer/package-lock.json +++ b/src-mdviewer/package-lock.json @@ -1533,6 +1533,7 @@ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-11.1.2.tgz", "integrity": "sha512-opLQzEVriiH1uUQ4Kctsd49bRoFDXGGSC4GUqj7pGyxM3RehRhvTlZJc1FL/Flew2p5uwxa1tUDWKzI4wNM8pg==", "license": "Apache-2.0", + "peer": true, "dependencies": { "@chevrotain/cst-dts-gen": "11.1.2", "@chevrotain/gast": "11.1.2", @@ -1596,6 +1597,7 @@ "resolved": "https://registry.npmjs.org/cytoscape/-/cytoscape-3.33.1.tgz", "integrity": "sha512-iJc4TwyANnOGR1OmWhsS9ayRS3s+XQ185FmuHObThD+5AeJCakAAbWv8KimMTt08xCCLNgneQwFp+JRJOr9qGQ==", "license": "MIT", + "peer": true, "engines": { "node": ">=0.10" } @@ -1996,6 +1998,7 @@ "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-3.0.0.tgz", "integrity": "sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==", "license": "ISC", + "peer": true, "engines": { "node": ">=12" } @@ -2731,6 +2734,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -2913,7 +2917,8 @@ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", "dev": true, - "license": "MIT" + "license": "MIT", + "peer": true }, "node_modules/tapable": { "version": "2.3.0", @@ -3011,6 +3016,7 @@ "integrity": "sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", diff --git a/src/JSUtils/ModalBuilder.js b/src/JSUtils/ModalBuilder.js new file mode 100644 index 0000000000..6f8f2b7b6b --- /dev/null +++ b/src/JSUtils/ModalBuilder.js @@ -0,0 +1,81 @@ +define(function() { + const Item = Object.freeze({ + modal: "modal", + modalContent: "content", + buttonsContainer: "buttons" + }); + + class ModalBuilder { + constructor(document) { + this.document = document; + this.modal = this.document.createElement('div'); + this.modalContent = this.document.createElement('div'); + this.buttonsContainer = this.document.createElement('div'); + } + + addStyle(item, style) { + switch (item) { + case Item.modal: + Object.assign(this.modal.style, style); + break; + case Item.modalContent: + Object.assign(this.modalContent.style, style); + break; + case Item.buttonsContainer: + Object.assign(this.buttonsContainer.style, style); + break; + + default: + break; + } + return this; + } + + addTitle(title, color) { + this.title = this.document.createElement('h2'); + this.title.textContent = title; + this.title.style.color = color; + return this; + } + + addMessage(message) { + this.modalMessage = document.createElement('p'); + message.split('\n').forEach((part, index, array) => { + this.modalMessage.appendChild(document.createTextNode(part)); + if (index < array.length - 1) { // Don't add a break on the last element + this.modalMessage.appendChild(document.createElement('br')); + } + }); + return this; + } + + addButton(title) { + const button = this.document.createElement('button'); + button.textContent = title; + this.buttonsContainer.appendChild(button); + return button; + } + + addButton(title, style) { + const button = this.addButton(title); + Object.assign(button.style, style); + return button; + } + + addButton(title, style, onclick) { + const button = this.addButton(title, style); + button.onclick = onclick; + return button; + } + + build() { + this.modalContent.appendChild(this.title); + this.modalContent.appendChild(this.message); + this.modalContent.appendChild(this.buttonsContainer); + this.modal.appendChild(this.modalContent); + return this.modal; + } + } + + return ModalBuilder; +}); diff --git a/src/JSUtils/SelectorStrategy.js b/src/JSUtils/SelectorStrategy.js new file mode 100644 index 0000000000..59a17aeb45 --- /dev/null +++ b/src/JSUtils/SelectorStrategy.js @@ -0,0 +1,70 @@ +define(function (require) { + const Strings = require('strings'); + const HTMLUtils = require('language/HTMLUtils'); + + const strategies = []; + + const TagSelectorStrategy = { + isMatch: (tokenType) => + tokenType === HTMLUtils.TAG_NAME || tokenType === HTMLUtils.CLOSING_TAG, + + resolve: (tagInfo) => ({ + selectorName: tagInfo.tagName, + reason: null + }) + }; + + const ClassSelectorStrategy = { + isMatch: (tokenType, attrName) => + (tokenType === HTMLUtils.ATTR_NAME || tokenType === HTMLUtils.ATTR_VALUE) && attrName === "class", + + resolve: (tagInfo) => { + var attributeValue = tagInfo.attr.value; + if (!/\S/.test(attributeValue)) { + return { selectorName: "", reason: Strings.ERROR_CSSQUICKEDIT_CLASSNOTFOUND }; + } + + var startIndex = attributeValue.substr(0, tagInfo.position.offset).lastIndexOf(" "); + var endIndex = attributeValue.indexOf(" ", tagInfo.position.offset); + let selectorName = "." + + attributeValue.substring( + startIndex === -1 ? 0 : startIndex + 1, + endIndex === -1 ? attributeValue.length : endIndex + ); + + if (selectorName === ".") { + return { selectorName: "", reason: Strings.ERROR_CSSQUICKEDIT_BETWEENCLASSES }; + } + return { selectorName, reason: null }; + } + }; + const IdSelectorStrategy = { + isMatch: (tokenType, attrName) => + (tokenType === HTMLUtils.ATTR_NAME || tokenType === HTMLUtils.ATTR_VALUE) && attrName === "id", + + resolve: (tagInfo) => { + var trimmedVal = tagInfo.attr.value.trim(); + if (trimmedVal) { + return { selectorName: "#" + trimmedVal, reason: null}; + } + return { selectorName: "", reason: Strings.ERROR_CSSQUICKEDIT_IDNOTFOUND }; + } + }; + + const ErrorSelectorStrategy = { + isMatch: () => true, + resolve: () => { return { selectorName: "", reason: Strings.ERROR_CSSQUICKEDIT_UNSUPPORTEDATTR }; } + }; + + strategies.push(TagSelectorStrategy); + strategies.push(ClassSelectorStrategy); + strategies.push(IdSelectorStrategy); + strategies.push(ErrorSelectorStrategy); + + function getStrategy(tagInfo) { + const strategy = strategies.find(s => s.isMatch(tagInfo.position.tokenType, tagInfo.attr.name)); + return strategy ? strategy : null; + } + + return getStrategy; +}); diff --git a/src/editor/CSSInlineEditor.js b/src/editor/CSSInlineEditor.js index ba9614b01a..15b2de2880 100644 --- a/src/editor/CSSInlineEditor.js +++ b/src/editor/CSSInlineEditor.js @@ -56,56 +56,12 @@ define(function (require, exports, module) { * @private */ function _getSelectorName(editor, pos) { - var tagInfo = HTMLUtils.getTagInfo(editor, pos), - selectorName = "", - reason; - - if (tagInfo.position.tokenType === HTMLUtils.TAG_NAME || tagInfo.position.tokenType === HTMLUtils.CLOSING_TAG) { - // Type selector - selectorName = tagInfo.tagName; - } else if (tagInfo.position.tokenType === HTMLUtils.ATTR_NAME || - tagInfo.position.tokenType === HTMLUtils.ATTR_VALUE) { - if (tagInfo.attr.name === "class") { - // Class selector. We only look for the class name - // that includes the insertion point. For example, if - // the attribute is: - // class="error-dialog modal hide" - // and the insertion point is inside "modal", we want ".modal" - var attributeValue = tagInfo.attr.value; - if (/\S/.test(attributeValue)) { - var startIndex = attributeValue.substr(0, tagInfo.position.offset).lastIndexOf(" "); - var endIndex = attributeValue.indexOf(" ", tagInfo.position.offset); - selectorName = "." + - attributeValue.substring( - startIndex === -1 ? 0 : startIndex + 1, - endIndex === -1 ? attributeValue.length : endIndex - ); - - // If the insertion point is surrounded by space between two classnames, selectorName is "." - if (selectorName === ".") { - selectorName = ""; - reason = Strings.ERROR_CSSQUICKEDIT_BETWEENCLASSES; - } - } else { - reason = Strings.ERROR_CSSQUICKEDIT_CLASSNOTFOUND; - } - } else if (tagInfo.attr.name === "id") { - // ID selector - var trimmedVal = tagInfo.attr.value.trim(); - if (trimmedVal) { - selectorName = "#" + trimmedVal; - } else { - reason = Strings.ERROR_CSSQUICKEDIT_IDNOTFOUND; - } - } else { - reason = Strings.ERROR_CSSQUICKEDIT_UNSUPPORTEDATTR; - } - } + var tagInfo = HTMLUtils.getTagInfo(editor, pos); + + const strategies = require('../JSUtils/SelectorStrategy.js'); + const activeStrategy = strategies.find(s => s.isMatch(tagInfo.position.tokenType, tagInfo.attr.name)); - return { - selectorName: selectorName, - reason: reason - }; + return activeStrategy.extract(tagInfo); } /** diff --git a/src/main.js b/src/main.js index 16ba27da5f..02f52675b0 100644 --- a/src/main.js +++ b/src/main.js @@ -130,6 +130,7 @@ if(!Phoenix.isNativeApp) { */ require.config({ paths: { + "modal_builder": "src/JSUtils/ModalBuilder", "text": "thirdparty/text/text", "i18n": "thirdparty/i18n/i18n", @@ -205,30 +206,11 @@ function applyStyles(element, styles) { Object.assign(element.style, styles); } function confirmReload(title, message) { - // vanilla js elements as we dont know if anything else is available in crash scenario - const modal = document.createElement('div'); - const modalContent = document.createElement('div'); - const modalTitle = document.createElement('h2'); - const modalMessage = document.createElement('p'); - const buttonsContainer = document.createElement('div'); // Container for buttons - const copyButton = document.createElement('button'); - const getHelpButton = document.createElement('button'); - const reloadButton = document.createElement('button'); + const modalBuilder = new (require('modal_builder')())(document); + + modalBuilder.addTitle(title, 'red'); + modalBuilder.addMessage(message); - // Set content - modalTitle.textContent = title; - message.split('\n').forEach((part, index, array) => { - modalMessage.appendChild(document.createTextNode(part)); - if (index < array.length - 1) { // Don't add a break on the last element - modalMessage.appendChild(document.createElement('br')); - } - }); - copyButton.textContent = 'Copy Error'; - getHelpButton.textContent = 'Get Help'; - // In native apps, we can't reload because AES trust ring won't survive - must quit and restart manually - reloadButton.textContent = Phoenix.isNativeApp ? 'Quit App' : 'Restart App'; - // Styling for visibility - // Define common styles for buttons const buttonStyles = { padding: '10px 20px', marginRight: '10px', // Space between buttons @@ -243,8 +225,30 @@ function confirmReload(title, message) { const getHelpButtonStyles = {backgroundColor: '#4CAF50'}; const reloadButtonStyles = {backgroundColor: '#4CAF50'}; - modalTitle.style.color = 'red'; - applyStyles(modal, { + modalBuilder.addButton( + 'Copy Error', + {...buttonStyles, ...copyButtonStyles}, + function() { + navigator.clipboard.writeText(message).then(() => { + alert('Error Message copied!'); + }, (err) => { + console.error('Failed to copy text: ', err); + }); + } + ); + modalBuilder.addButton( + 'Get Help', + {...buttonStyles, ...getHelpButtonStyles}, + function() { + Phoenix.app.openURLInDefaultBrowser("https://github.com/phcode-dev/phoenix/discussions"); + } + ); + const reloadButton = modalBuilder.addButton( + Phoenix.isNativeApp ? 'Quit App' : 'Restart App', + {...buttonStyles, ...reloadButtonStyles} + ); + + modalBuilder.addStyle('modal', { color: 'black', position: 'fixed', top: '0', @@ -258,7 +262,7 @@ function confirmReload(title, message) { alignItems: 'center' }); - applyStyles(modalContent, { + modalBuilder.addStyle('content', { backgroundColor: 'white', padding: '20px', borderRadius: '10px', @@ -266,37 +270,13 @@ function confirmReload(title, message) { boxShadow: '0 4px 8px rgba(0,0,0,0.2)' }); - applyStyles(buttonsContainer, { + modalBuilder.addStyle('buttons', { marginTop: '20px', display: 'flex', justifyContent: 'space-around' }); - // Apply styles to buttons - applyStyles(copyButton, {...buttonStyles, ...copyButtonStyles}); - applyStyles(getHelpButton, {...buttonStyles, ...getHelpButtonStyles}); - applyStyles(reloadButton, {...buttonStyles, ...reloadButtonStyles}); - - // Event listeners for buttons - copyButton.onclick = function() { - navigator.clipboard.writeText(message).then(() => { - alert('Error Message copied!'); - }, (err) => { - console.error('Failed to copy text: ', err); - }); - }; - getHelpButton.onclick = function() { - Phoenix.app.openURLInDefaultBrowser("https://github.com/phcode-dev/phoenix/discussions"); - }; - - // Append children - modalContent.appendChild(modalTitle); - modalContent.appendChild(modalMessage); - buttonsContainer.appendChild(copyButton); - buttonsContainer.appendChild(getHelpButton); - buttonsContainer.appendChild(reloadButton); - modalContent.appendChild(buttonsContainer); - modal.appendChild(modalContent); + const modal = modalBuilder.build(); // Append modal to the body document.body.appendChild(modal); From dde41351724b703804e37dfab08e5af9532555cb Mon Sep 17 00:00:00 2001 From: Monalisa Date: Tue, 13 Oct 2026 19:06:44 -0300 Subject: [PATCH 2/3] Refactor the configuration using the Adapter pattern --- src/LiveDevelopment/main.js | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index 203e41845e..ca424a2bbc 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -324,12 +324,29 @@ define(function main(require, exports, module) { MultiBrowserLiveDev.updateConfig(config); } + // Adapter - Refactoring + const ElementHighlightPreferenceAdapter = { + toLivePreviewConfig() { + const prefValue = PreferencesManager.get( + CONSTANTS.PREFERENCE_PROJECT_ELEMENT_HIGHLIGHT + ); + + return { + elemHighlights: prefValue || CONSTANTS.HIGHLIGHT_HOVER + }; + } + }; + // this function is responsible to update element highlight config // called from live preview extension when preference changes function updateElementHighlightConfig() { - const prefValue = PreferencesManager.get(CONSTANTS.PREFERENCE_PROJECT_ELEMENT_HIGHLIGHT); const config = MultiBrowserLiveDev.getConfig(); - config.elemHighlights = prefValue || CONSTANTS.HIGHLIGHT_HOVER; + + Object.assign( + config, + ElementHighlightPreferenceAdapter.toLivePreviewConfig() + ); + MultiBrowserLiveDev.updateConfig(config); } From 97edf43491a142030370e9e717ad528585255fa5 Mon Sep 17 00:00:00 2001 From: Monalisa Date: Tue, 13 Oct 2026 19:11:36 -0300 Subject: [PATCH 3/3] Refactor ruler creation using the Factory pattern --- src/editor/EditorOptionHandlers.js | 44 ++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 15 deletions(-) diff --git a/src/editor/EditorOptionHandlers.js b/src/editor/EditorOptionHandlers.js index bbdf3609b5..1aa48e2d46 100644 --- a/src/editor/EditorOptionHandlers.js +++ b/src/editor/EditorOptionHandlers.js @@ -104,32 +104,46 @@ define(function (require, exports, module) { }; } + // Factory - Refactoring + const RulerOptionsFactory = { + create: function (rulerColumns, rulerColors, defaultColor) { + return rulerColumns.map(function (column, index) { + return { + color: rulerColors[index] ? rulerColors[index] : defaultColor, + column: column, + lineStyle: "solid !important" + }; + }); + } + }; + function _createRulers(editor) { const rulerColumns = PreferencesManager.get(PREFERENCES_EDITOR_RULERS) || []; const rulerColors = PreferencesManager.get(PREFERENCES_EDITOR_RULER_COLORS) || []; const rulersEnabled = PreferencesManager.get(PREFERENCES_EDITOR_RULERS_ENABLED); - if( !rulersEnabled || !rulerColumns.length || !editor){ + + if (!rulersEnabled || !rulerColumns.length || !editor) { return; } - if(!_currentTheme){ + if (!_currentTheme) { _currentTheme = ThemeManager.getCurrentTheme(); } - if(!_currentTheme){ - // theme manager can return a null current theme at startup before themes load! + + if (!_currentTheme) { + // O tema pode ainda não estar carregado na inicialização. return; } - const defaultColor = (_currentTheme && _currentTheme.dark) ? "#333333" : "#e0e0e0"; - - if(!editor._codeMirror.getOption("rulers")){ - let rulerOptions = []; - for(let i=0; i