From 3a1c0f958c191bb40755b7d83938ea6dfa192221 Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 21 Nov 2025 16:54:57 +0530 Subject: [PATCH 1/3] feat: login integration for live preview edit --- src/LiveDevelopment/main.js | 92 ++++++++++++++++-- .../Phoenix-live-preview/main.js | 96 +++---------------- 2 files changed, 99 insertions(+), 89 deletions(-) diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index 3819c467d2..9f3f437bd9 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -48,14 +48,14 @@ define(function main(require, exports, module) { EditorManager = require("editor/EditorManager"); - // this is responsible to make the advanced live preview features active or inactive - // @abose (make the first value true when its a paid user, everything rest is handled automatically) - let isProUser = window.KernalModeTrust ? true : false; - // when isFreeTrialUser is true isProUser should also be true - // when its false, isProUser can be true/false doesn't matter - let isFreeTrialUser = true; + const KernalModeTrust = window.KernalModeTrust; + + // this will later be assigned its correct values once entitlementsManager loads + let isProUser = false; + let isFreeTrialUser = false; const EVENT_LIVE_HIGHLIGHT_PREF_CHANGED = "liveHighlightPrefChange"; + const PREFERENCE_LIVE_PREVIEW_MODE = "livePreviewMode"; // state manager key to track image gallery selected state, by default we keep this as selected // if this is true we show the image gallery when an image element is clicked @@ -316,6 +316,70 @@ define(function main(require, exports, module) { return false; } + // default mode means on first load for pro user we have edit mode + // for free user we have highlight mode + function _getDefaultMode() { + return isProUser ? "edit" : "highlight"; + } + + // to set that mode in the preferences + function _initializeMode() { + if (isFreeTrialUser) { + PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, "edit"); + return; + } + + const savedMode = PreferencesManager.get(PREFERENCE_LIVE_PREVIEW_MODE) || _getDefaultMode(); + + if (savedMode === "highlight" && isProUser) { + PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, "edit"); + } else if (savedMode === "edit" && !isProUser) { + PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, "highlight"); + } + } + + // this is called everytime there is a change in entitlements + async function _updateProUserStatus() { + if (!KernalModeTrust) { + return; + } + + try { + const entitlement = await KernalModeTrust.EntitlementsManager.getLiveEditEntitlement(); + const isPaidSub = await KernalModeTrust.EntitlementsManager.isPaidSubscriber(); + + isProUser = entitlement.activated || isPaidSub; + isFreeTrialUser = await KernalModeTrust.EntitlementsManager.isInProTrial(); + + config.isProUser = isProUser; + exports.isProUser = isProUser; + exports.isFreeTrialUser = isFreeTrialUser; + + _initializeMode(); + + if (MultiBrowserLiveDev.status >= MultiBrowserLiveDev.STATUS_ACTIVE) { + MultiBrowserLiveDev.updateConfig(JSON.stringify(config)); + MultiBrowserLiveDev.registerHandlers(); + } + } catch (error) { + console.error("Error updating pro user status:", error); + isProUser = false; + isFreeTrialUser = false; + } + } + + function setMode(mode) { + if (mode === "edit" && !exports.isProUser) { + return false; + } + PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, mode); + return true; + } + + function getCurrentMode() { + return PreferencesManager.get(PREFERENCE_LIVE_PREVIEW_MODE) || _getDefaultMode(); + } + /** Initialize LiveDevelopment */ AppInit.appReady(function () { params.parse(); @@ -330,6 +394,15 @@ define(function main(require, exports, module) { _loadStyles(); _updateHighlightCheckmark(); + // init pro user status and listen for changes + if (KernalModeTrust) { + _updateProUserStatus(); + KernalModeTrust.EntitlementsManager.on( + KernalModeTrust.EntitlementsManager.EVENT_ENTITLEMENTS_CHANGED, + _updateProUserStatus + ); + } + // update styles for UI status _status = [ { tooltip: Strings.LIVE_DEV_STATUS_TIP_NOT_CONNECTED, style: "warning" }, @@ -385,6 +458,11 @@ define(function main(require, exports, module) { } }); + PreferencesManager.definePreference(PREFERENCE_LIVE_PREVIEW_MODE, "string", _getDefaultMode(), { + description: StringUtils.format(Strings.LIVE_PREVIEW_MODE_PREFERENCE, "'preview'", "'highlight'", "'edit'"), + values: ["preview", "highlight", "edit"] + }); + config.highlight = PreferencesManager.getViewState("livedevHighlight"); function setLivePreviewEditFeaturesActive(enabled) { @@ -441,4 +519,6 @@ define(function main(require, exports, module) { exports.getLivePreviewDetails = MultiBrowserLiveDev.getLivePreviewDetails; exports.hideHighlight = MultiBrowserLiveDev.hideHighlight; exports.dismissLivePreviewBoxes = MultiBrowserLiveDev.dismissLivePreviewBoxes; + exports.setMode = setMode; + exports.getCurrentMode = getCurrentMode; }); diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index 2e5eda7d59..5e88c35cb7 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -87,25 +87,9 @@ define(function (require, exports, module) { const PREVIEW_TRUSTED_PROJECT_KEY = "preview_trusted"; const PREVIEW_PROJECT_README_KEY = "preview_readme"; - // live preview mode pref - const PREFERENCE_LIVE_PREVIEW_MODE = "livePreviewMode"; - // holds the dropdown instance let $dropdown = null; - - /** - * Get the appropriate default mode based on whether edit features are active - * @returns {string} "highlight" if edit features inactive, "edit" if active - */ - function _getDefaultMode() { - return LiveDevelopment.isProUser ? "edit" : "highlight"; - } - - // define the live preview mode preference - PreferencesManager.definePreference(PREFERENCE_LIVE_PREVIEW_MODE, "string", _getDefaultMode(), { - description: StringUtils.format(Strings.LIVE_PREVIEW_MODE_PREFERENCE, "'preview'", "'highlight'", "'edit'"), - values: ["preview", "highlight", "edit"] - }); + const PREFERENCE_LIVE_PREVIEW_MODE = "livePreviewMode"; // live preview element highlights preference (whether on hover or click) const PREFERENCE_PROJECT_ELEMENT_HIGHLIGHT = "livePreviewElementHighlights"; @@ -372,35 +356,13 @@ define(function (require, exports, module) { } } - /** - * init live preview mode from saved preferences - */ function _initializeMode() { - // when user is on free trial we just push the edit mode to them every time they open/reload Phoenix - if(LiveDevelopment.isFreeTrialUser) { - PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, "edit"); - _LPEditMode(); - $previewBtn.removeClass('selected'); - _updateModeButton("edit"); - return; - } - - const savedMode = PreferencesManager.get(PREFERENCE_LIVE_PREVIEW_MODE) || _getDefaultMode(); - const isEditFeaturesActive = LiveDevelopment.isProUser; + const currentMode = LiveDevelopment.getCurrentMode(); - // If user has edit mode saved but edit features are not active, default to highlight - let effectiveMode = savedMode; - if (savedMode === "edit" && !isEditFeaturesActive) { - effectiveMode = "highlight"; - // Update the preference to reflect the actual mode being used - PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, "highlight"); - } - - // apply the effective mode - if (effectiveMode === "highlight") { + if (currentMode === "highlight") { _LPHighlightMode(); $previewBtn.removeClass('selected'); - } else if (effectiveMode === "edit" && isEditFeaturesActive) { + } else if (currentMode === "edit") { _LPEditMode(); $previewBtn.removeClass('selected'); } else { @@ -408,7 +370,7 @@ define(function (require, exports, module) { $previewBtn.addClass('selected'); } - _updateModeButton(effectiveMode); + _updateModeButton(currentMode); } function _showModeSelectionDropdown(event) { @@ -425,9 +387,7 @@ define(function (require, exports, module) { items.push(Strings.LIVE_PREVIEW_EDIT_HIGHLIGHT_ON); } - const rawMode = PreferencesManager.get(PREFERENCE_LIVE_PREVIEW_MODE) || _getDefaultMode(); - // this is to take care of invalid values in the pref file - const currentMode = ["preview", "highlight", "edit"].includes(rawMode) ? rawMode : _getDefaultMode(); + const currentMode = LiveDevelopment.getCurrentMode(); $dropdown = new DropdownButton.DropdownButton("", items, function(item, index) { if (item === Strings.LIVE_PREVIEW_MODE_PREVIEW) { @@ -472,19 +432,13 @@ define(function (require, exports, module) { // handle the option selection $dropdown.on("select", function (e, item, index) { - // here we just set the preference - // as the preferences listener will automatically handle the required changes if (index === 0) { - PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, "preview"); + LiveDevelopment.setMode("preview"); } else if (index === 1) { - PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, "highlight"); + LiveDevelopment.setMode("highlight"); } else if (index === 2) { - if (!isEditFeaturesActive) { - // when the feature is not active we need to show a dialog to the user asking - // them to subscribe to pro + if (!LiveDevelopment.setMode("edit")) { _showProFeatureDialog(); - } else { - PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, "edit"); } } else if (item === Strings.LIVE_PREVIEW_EDIT_HIGHLIGHT_ON) { // Don't allow edit highlight toggle if edit features are not active @@ -492,8 +446,8 @@ define(function (require, exports, module) { return; } // Toggle between hover and click - const currentMode = PreferencesManager.get(PREFERENCE_PROJECT_ELEMENT_HIGHLIGHT); - const newMode = currentMode !== "click" ? "click" : "hover"; + const currMode = PreferencesManager.get(PREFERENCE_PROJECT_ELEMENT_HIGHLIGHT); + const newMode = currMode !== "click" ? "click" : "hover"; PreferencesManager.set(PREFERENCE_PROJECT_ELEMENT_HIGHLIGHT, newMode); return; // Don't dismiss highlights for this option } @@ -1268,32 +1222,8 @@ define(function (require, exports, module) { // init live preview mode from saved preferences _initializeMode(); // listen for pref changes - PreferencesManager.on("change", PREFERENCE_LIVE_PREVIEW_MODE, function () { - // Get the current preference value directly - const newMode = PreferencesManager.get(PREFERENCE_LIVE_PREVIEW_MODE); - const isEditFeaturesActive = LiveDevelopment.isProUser; - - // If user tries to set edit mode but edit features are not active, default to highlight - let effectiveMode = newMode; - if (newMode === "edit" && !isEditFeaturesActive) { - effectiveMode = "highlight"; - // Update the preference to reflect the actual mode being used - PreferencesManager.set(PREFERENCE_LIVE_PREVIEW_MODE, "highlight"); - return; // Return to avoid infinite loop - } - - if (effectiveMode === "highlight") { - _LPHighlightMode(); - $previewBtn.removeClass('selected'); - } else if (effectiveMode === "edit" && isEditFeaturesActive) { - _LPEditMode(); - $previewBtn.removeClass('selected'); - } else { - _LPPreviewMode(); - $previewBtn.addClass('selected'); - } - - _updateModeButton(effectiveMode); + PreferencesManager.on("change", "livePreviewMode", function () { + _initializeMode(); }); // Handle element highlight preference changes from this extension From 0fa18d163bdee0dbc8e501fc64ebececc45aedd4 Mon Sep 17 00:00:00 2001 From: Pluto Date: Fri, 21 Nov 2025 17:14:46 +0530 Subject: [PATCH 2/3] fix: remove paid sub check and completely rely on live edit entitlements --- src/LiveDevelopment/main.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index 9f3f437bd9..61ad9dba33 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -346,9 +346,8 @@ define(function main(require, exports, module) { try { const entitlement = await KernalModeTrust.EntitlementsManager.getLiveEditEntitlement(); - const isPaidSub = await KernalModeTrust.EntitlementsManager.isPaidSubscriber(); - isProUser = entitlement.activated || isPaidSub; + isProUser = entitlement.activated; isFreeTrialUser = await KernalModeTrust.EntitlementsManager.isInProTrial(); config.isProUser = isProUser; From 2e4e7ff0f0aaba8730853485e52a488d3b3e1a7f Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 22 Nov 2025 01:14:07 +0530 Subject: [PATCH 3/3] fix: major bug that used to recreate highlighting and box on every keystroke --- .../BrowserScripts/RemoteFunctions.js | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 6d2aa2e40d..6c208d6c8b 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -33,6 +33,10 @@ function RemoteFunctions(config = {}) { // we need this so that we can remove click styling from the previous element when a new element is clicked let previouslyClickedElement = null; + // this is needed so that when user starts typing we can dismiss all the boxes and highlights + // now with this variable we check if its a first keystroke on an element or a subsequent keystroke + let _uiHiddenDuringTyping = false; + var req, timeout; var animateHighlight = function (time) { if(req) { @@ -1577,10 +1581,6 @@ function RemoteFunctions(config = {}) { create: function() { this.remove(); // remove existing box if already present - if(!config.isProUser) { - return; - } - // this check because when there is no element visible to the user, we don't want to show the box // for ex: when user clicks on a 'x' button and the button is responsible to hide a panel // then clicking on that button shouldn't show the more options box @@ -3822,6 +3822,9 @@ function RemoteFunctions(config = {}) { * @param {Element} element - The DOM element to select */ function _selectElement(element) { + // user selected a new element, we need to reset this variable + _uiHiddenDuringTyping = false; + dismissNodeMoreOptionsBox(); dismissAIPromptBox(); dismissNodeInfoBox(); @@ -3931,12 +3934,7 @@ function RemoteFunctions(config = {}) { event.stopPropagation(); event.stopImmediatePropagation(); - // when in click mode, only select dynamic elements (without data-brackets-id) directly - // as for static elements, the editor will handle selection via highlight message - if (!shouldShowHighlightOnHover() && !element.hasAttribute("data-brackets-id")) { - _selectElement(element); - } - + _selectElement(element); activateHoverLock(); } } @@ -4021,7 +4019,10 @@ function RemoteFunctions(config = {}) { var foundValidElement = false; for (i = 0; i < nodes.length; i++) { if(isElementInspectable(nodes[i], true) && nodes[i].tagName !== "BR") { - _selectElement(nodes[i]); + // only call _selectElement if it's a different element to avoid unnecessary box recreation + if (previouslyClickedElement !== nodes[i]) { + _selectElement(nodes[i]); + } foundValidElement = true; break; } @@ -4431,8 +4432,14 @@ function RemoteFunctions(config = {}) { this.rememberedNodes = {}; - // update highlight after applying diffs - redrawEverything(); + // when user starts typing in the editor we hide all the boxes and highlights + // _uiHiddenDuringTyping variable keeps track if its a first keystroke or subsequent + // so that we don't end up calling dismiss/hide kinda functions multiple times + if (!_uiHiddenDuringTyping) { + dismissUIAndCleanupState(); + hideHighlight(); + _uiHiddenDuringTyping = true; + } }; function applyDOMEdits(edits) {