diff --git a/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js b/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js index a959721cd4..72b95696fb 100644 --- a/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js +++ b/src/LiveDevelopment/BrowserScripts/LiveDevProtocolRemote.js @@ -455,6 +455,37 @@ redoLivePreviewOperation: true }); } + + // for save + if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === "s") { + e.preventDefault(); + + // to check if user was in between editing text + // in such cases we first finish the editing and then save + const activeElement = document.activeElement; + if (activeElement && + activeElement.hasAttribute("contenteditable") && + activeElement.hasAttribute("data-brackets-id") && + window._LD && + window._LD.finishEditing) { + + window._LD.finishEditing(activeElement); + } + + MessageBroker.send({ + livePreviewEditEnabled: true, + saveCurrentDocument: true + }); + } + + // for preview button (play icon) toggle + if (e.key === 'F8') { + e.preventDefault(); + MessageBroker.send({ + livePreviewEditEnabled: true, + toggleLivePreviewMode: true + }); + } }); }(this)); diff --git a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js index 6c208d6c8b..0b7beceaed 100644 --- a/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js +++ b/src/LiveDevelopment/BrowserScripts/RemoteFunctions.js @@ -33,10 +33,6 @@ 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) { @@ -133,6 +129,7 @@ function RemoteFunctions(config = {}) { if(element && // element should exist element.tagName.toLowerCase() !== "body" && // shouldn't be the body tag element.tagName.toLowerCase() !== "html" && // shouldn't be the HTML tag + !element.closest("[data-phcode-internal-c15r5a9]") && // this attribute is used by phoenix internal elements !_isInsideHeadTag(element)) { // shouldn't be inside the head tag like meta tags and all return true; } @@ -1368,10 +1365,10 @@ function RemoteFunctions(config = {}) { `, selectImageFromComputer: ` - - - - `, + + + + `, downloadImage: ` @@ -1465,6 +1462,7 @@ function RemoteFunctions(config = {}) { _style: function() { this.body = window.document.createElement("div"); + this.body.setAttribute("data-phcode-internal-c15r5a9", "true"); // this is shadow DOM. // we need it because if we add the box directly to the DOM then users style might override it. @@ -1744,6 +1742,7 @@ function RemoteFunctions(config = {}) { _style: function() { this.body = window.document.createElement("div"); + this.body.setAttribute("data-phcode-internal-c15r5a9", "true"); // this is shadow DOM. // we need it because if we add the box directly to the DOM then users style might override it. @@ -1928,6 +1927,7 @@ function RemoteFunctions(config = {}) { _style: function() { this.body = window.document.createElement("div"); + this.body.setAttribute("data-phcode-internal-c15r5a9", "true"); // using shadow dom so that user styles doesn't override it const shadow = this.body.attachShadow({ mode: "open" }); @@ -2297,7 +2297,8 @@ function RemoteFunctions(config = {}) { ImageRibbonGallery.prototype = { _style: function () { this.body = window.document.createElement("div"); - this._shadow = this.body.attachShadow({mode: 'closed'}); + this.body.setAttribute("data-phcode-internal-c15r5a9", "true"); + this._shadow = this.body.attachShadow({ mode: 'open' }); this._shadow.innerHTML = ` @@ -3822,12 +3821,10 @@ 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(); + dismissToastMessage(); cleanupPreviousElementState(); // this should also be there when users are in highlight mode @@ -3850,12 +3847,9 @@ function RemoteFunctions(config = {}) { } // if element is not editable and user clicks on it, then we show a toast notification saying - // that this element is not editable (unless user dismissed it permanently) + // that this element is not editable if (!element.hasAttribute("data-brackets-id")) { - const hideToast = localStorage.getItem('phoenix-hide-dynamic-toast'); - if (!hideToast) { - showToast(config.strings.toastNotEditable); - } + showToastMessage(config.strings.toastNotEditable); } // make sure that the element is actually visible to the user @@ -4431,15 +4425,7 @@ function RemoteFunctions(config = {}) { }); this.rememberedNodes = {}; - - // 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; - } + redrawEverything(); }; function applyDOMEdits(edits) { @@ -4558,6 +4544,7 @@ function RemoteFunctions(config = {}) { dismissAIPromptBox(); dismissNodeInfoBox(); dismissImageRibbonGallery(); + dismissToastMessage(); } let _toastTimeout = null; @@ -4567,19 +4554,14 @@ function RemoteFunctions(config = {}) { * this toast message is used when user tries to edit a non-editable element * @param {String} message - the message to display in the toast */ - function showToast(message) { + function showToastMessage(message) { // clear any existing toast & timer, if there are any - const existingToast = window.document.getElementById('phoenix-toast-notification'); - if (existingToast) { - existingToast.remove(); - } - if (_toastTimeout) { - clearTimeout(_toastTimeout); - } + dismissToastMessage(); // create a new fresh toast container const toast = window.document.createElement('div'); toast.id = 'phoenix-toast-notification'; + toast.setAttribute("data-phcode-internal-c15r5a9", "true"); const shadow = toast.attachShadow({ mode: 'open' }); const styles = ` @@ -4607,27 +4589,6 @@ function RemoteFunctions(config = {}) { animation: slideUp 0.3s ease-out !important; } - .toast-message { - margin-bottom: 6px !important; - } - - .toast-button { - background: none !important; - border: none !important; - color: #A0A0A0 !important; - cursor: pointer !important; - font-size: 12px !important; - font-family: Arial, sans-serif !important; - text-decoration: none !important; - pointer-events: auto !important; - transition: opacity 0.2s !important; - } - - .toast-button:hover { - opacity: 0.8 !important; - text-decoration: underline !important; - } - @keyframes slideUp { from { opacity: 0; @@ -4643,34 +4604,34 @@ function RemoteFunctions(config = {}) { const content = `
${message}
-
`; shadow.innerHTML = `${content}`; window.document.body.appendChild(toast); - // add click handler to "Don't show again" button - const button = shadow.querySelector('.toast-button'); - button.addEventListener('click', () => { - // save to localStorage to never show again and close toast rn - localStorage.setItem('phoenix-hide-dynamic-toast', 'true'); - if (toast && toast.parentNode) { - toast.remove(); - } - if (_toastTimeout) { - clearTimeout(_toastTimeout); - _toastTimeout = null; - } - }); - - // Auto-dismiss after 6 seconds + // Auto-dismiss after 3 seconds _toastTimeout = setTimeout(() => { if (toast && toast.parentNode) { toast.remove(); } _toastTimeout = null; - }, 6000); + }, 3000); + } + + /** + * this function is to dismiss the toast message + * and clear its timeout (if any) + */ + function dismissToastMessage() { + const toastMessage = window.document.getElementById('phoenix-toast-notification'); + if (toastMessage) { + toastMessage.remove(); + } + if (_toastTimeout) { + clearTimeout(_toastTimeout); + } + _toastTimeout = null; } /** diff --git a/src/LiveDevelopment/LivePreviewEdit.js b/src/LiveDevelopment/LivePreviewEdit.js index e6575bdcfd..851e84d094 100644 --- a/src/LiveDevelopment/LivePreviewEdit.js +++ b/src/LiveDevelopment/LivePreviewEdit.js @@ -30,6 +30,8 @@ define(function (require, exports, module) { const LiveDevelopment = require("LiveDevelopment/main"); const CodeMirror = require("thirdparty/CodeMirror/lib/codemirror"); const ProjectManager = require("project/ProjectManager"); + const CommandManager = require("command/CommandManager"); + const Commands = require("command/Commands"); const FileSystem = require("filesystem/FileSystem"); const PathUtils = require("thirdparty/path-utils/path-utils"); const StringMatch = require("utils/StringMatch"); @@ -1265,6 +1267,33 @@ define(function (require, exports, module) { _showFolderSelectionDialog(null); } + /** + * this function is responsible to save the active file (and previewed file, both might be same though) + * when ctrl/cmd + s is pressed in the live preview + */ + function _handleLivePreviewSave() { + // this saves the active file + CommandManager.execute(Commands.FILE_SAVE); + + // we also save the previewed file, (active file might be same as previewed or different) + const currLiveDoc = LiveDevMultiBrowser.getCurrentLiveDoc(); + if (currLiveDoc && currLiveDoc.editor) { + const previewedDoc = currLiveDoc.editor.document; + CommandManager.execute(Commands.FILE_SAVE, { doc: previewedDoc }); + } + } + + /** + * This function is responsible to toggle the live preview Preview mode (play icon) + * this is done when user presses F8 key in the live preview + */ + function _handlePreviewModeToggle() { + const $previewBtn = $("#previewModeLivePreviewButton"); + if ($previewBtn.length > 0) { + $previewBtn.trigger("click"); + } + } + /** * This is the main function that is exported. * it will be called by LiveDevProtocol when it receives a message from RemoteFunctions.js @@ -1289,6 +1318,18 @@ define(function (require, exports, module) { * these are the main properties that are passed through the message */ function handleLivePreviewEditOperation(message) { + // handle save current document in live preview (ctrl/cmd + s) + if (message.saveCurrentDocument) { + _handleLivePreviewSave(); + return; + } + + // toggle live preview mode using F8 key + if (message.toggleLivePreviewMode) { + _handlePreviewModeToggle(); + return; + } + // handle reset image folder selection if (message.resetImageFolderSelection) { _handleResetImageFolderSelection(); diff --git a/src/LiveDevelopment/main.js b/src/LiveDevelopment/main.js index 61ad9dba33..ad343d5880 100644 --- a/src/LiveDevelopment/main.js +++ b/src/LiveDevelopment/main.js @@ -111,7 +111,6 @@ define(function main(require, exports, module) { imageGallery: Strings.LIVE_DEV_MORE_OPTIONS_IMAGE_GALLERY, aiPromptPlaceholder: Strings.LIVE_DEV_AI_PROMPT_PLACEHOLDER, imageGalleryUseImage: Strings.LIVE_DEV_IMAGE_GALLERY_USE_IMAGE, - imageGallerySelectFromComputer: Strings.LIVE_DEV_IMAGE_GALLERY_SELECT_FROM_COMPUTER, imageGallerySelectDownloadFolder: Strings.LIVE_DEV_IMAGE_GALLERY_SELECT_DOWNLOAD_FOLDER, imageGallerySearchPlaceholder: Strings.LIVE_DEV_IMAGE_GALLERY_SEARCH_PLACEHOLDER, imageGallerySearchButton: Strings.LIVE_DEV_IMAGE_GALLERY_SEARCH_BUTTON, @@ -120,9 +119,9 @@ define(function main(require, exports, module) { imageGalleryNoImages: Strings.LIVE_DEV_IMAGE_GALLERY_NO_IMAGES, imageGalleryLoadError: Strings.LIVE_DEV_IMAGE_GALLERY_LOAD_ERROR, imageGalleryClose: Strings.LIVE_DEV_IMAGE_GALLERY_CLOSE, - imageGalleryUpload: Strings.LIVE_DEV_IMAGE_GALLERY_UPLOAD, - toastNotEditable: Strings.LIVE_DEV_TOAST_NOT_EDITABLE, - toastDontShowAgain: Strings.LIVE_DEV_TOAST_DONT_SHOW_AGAIN + imageGallerySelectFromComputer: Strings.LIVE_DEV_IMAGE_GALLERY_SELECT_FROM_COMPUTER, + imageGallerySelectFromComputerTooltip: Strings.LIVE_DEV_IMAGE_GALLERY_SELECT_FROM_COMPUTER_TOOLTIP, + toastNotEditable: Strings.LIVE_DEV_TOAST_NOT_EDITABLE } }; // Status labels/styles are ordered: error, not connected, progress1, progress2, connected. @@ -465,7 +464,6 @@ define(function main(require, exports, module) { config.highlight = PreferencesManager.getViewState("livedevHighlight"); function setLivePreviewEditFeaturesActive(enabled) { - // TODO: @abose here add kernal mode trust check isProUser = enabled; config.isProUser = enabled; if (MultiBrowserLiveDev && MultiBrowserLiveDev.status >= MultiBrowserLiveDev.STATUS_ACTIVE) { diff --git a/src/base-config/keyboard.json b/src/base-config/keyboard.json index 8674ac0619..6469aadec7 100644 --- a/src/base-config/keyboard.json +++ b/src/base-config/keyboard.json @@ -28,9 +28,6 @@ "key": "Ctrl-Shift-R" } ], - "file.previewHighlight": [ - "Ctrl-Shift-C" - ], "file.quit": [ "Ctrl-Q" ], diff --git a/src/command/DefaultMenus.js b/src/command/DefaultMenus.js index 8fec28a26e..31578dad57 100644 --- a/src/command/DefaultMenus.js +++ b/src/command/DefaultMenus.js @@ -230,8 +230,6 @@ define(function (require, exports, module) { menu.addMenuItem(Commands.TOGGLE_WORD_WRAP); menu.addMenuItem(Commands.TOGGLE_RULERS); menu.addMenuDivider(); - menu.addMenuItem(Commands.FILE_LIVE_HIGHLIGHT); - menu.addMenuDivider(); menu.addMenuItem(Commands.VIEW_TOGGLE_PROBLEMS); menu.addMenuItem(Commands.VIEW_TOGGLE_INSPECTION); diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index 5e88c35cb7..547851b0ea 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -72,6 +72,7 @@ define(function (require, exports, module) { panelHTML = require("text!./panel.html"), Dialogs = require("widgets/Dialogs"), DefaultDialogs = require("widgets/DefaultDialogs"), + ProDialogs = require("services/pro-dialogs"), utils = require('./utils'); const StateManager = PreferencesManager.stateManager; @@ -273,35 +274,6 @@ define(function (require, exports, module) { } } - function _showProFeatureDialog() { - const dialog = Dialogs.showModalDialog( - DefaultDialogs.DIALOG_ID_INFO, - Strings.LIVE_PREVIEW_PRO_FEATURE_TITLE, - Strings.LIVE_PREVIEW_PRO_FEATURE_MESSAGE, - [ - { - className: Dialogs.DIALOG_BTN_CLASS_NORMAL, - id: Dialogs.DIALOG_BTN_CANCEL, - text: Strings.CANCEL - }, - { - className: Dialogs.DIALOG_BTN_CLASS_PRIMARY, - id: "subscribe", - text: Strings.LIVE_PREVIEW_PRO_SUBSCRIBE - } - ] - ); - - dialog.done(function (buttonId) { - if (buttonId === "subscribe") { - // TODO: write the implementation here...@abose - console.log("the subscribe button got clicked"); - } - }); - - return dialog; - } - // this function is to check if the live highlight feature is enabled or not function _isLiveHighlightEnabled() { return CommandManager.get(Commands.FILE_LIVE_HIGHLIGHT).getChecked(); @@ -438,7 +410,7 @@ define(function (require, exports, module) { LiveDevelopment.setMode("highlight"); } else if (index === 2) { if (!LiveDevelopment.setMode("edit")) { - _showProFeatureDialog(); + ProDialogs.showProUpsellDialog(ProDialogs.UPSELL_TYPE_LIVE_EDIT); } } else if (item === Strings.LIVE_PREVIEW_EDIT_HIGHLIGHT_ON) { // Don't allow edit highlight toggle if edit features are not active @@ -718,7 +690,7 @@ define(function (require, exports, module) { Strings: Strings, livePreview: Strings.LIVE_DEV_STATUS_TIP_OUT_OF_SYNC, clickToReload: Strings.LIVE_DEV_CLICK_TO_RELOAD_PAGE, - clickToPreview: Strings.LIVE_PREVIEW_MODE_PREVIEW, + clickToPreview: Strings.LIVE_PREVIEW_MODE_TOGGLE_PREVIEW, livePreviewSettings: Strings.LIVE_DEV_SETTINGS, livePreviewConfigureModes: Strings.LIVE_PREVIEW_CONFIGURE_MODES, clickToPopout: Strings.LIVE_DEV_CLICK_POPOUT, @@ -1174,6 +1146,13 @@ define(function (require, exports, module) { $(document).on("click", "#livePreviewModeBtn", function (e) { _handleLPModeBtnClick(e); }); + + $(document).on("keydown", function (e) { + if (e.key === "F8") { + e.preventDefault(); + _handlePreviewBtnClick(); + } + }); } AppInit.appReady(function () { diff --git a/src/nls/root/strings.js b/src/nls/root/strings.js index ed03f8e14b..9db3935857 100644 --- a/src/nls/root/strings.js +++ b/src/nls/root/strings.js @@ -191,7 +191,6 @@ define({ "LIVE_DEV_MORE_OPTIONS_AI": "Edit with AI", "LIVE_DEV_MORE_OPTIONS_IMAGE_GALLERY": "Image Gallery", "LIVE_DEV_IMAGE_GALLERY_USE_IMAGE": "Use this image", - "LIVE_DEV_IMAGE_GALLERY_SELECT_FROM_COMPUTER": "Upload image from computer", "LIVE_DEV_IMAGE_GALLERY_SELECT_DOWNLOAD_FOLDER": "Choose image download folder", "LIVE_DEV_IMAGE_GALLERY_SEARCH_PLACEHOLDER": "Search images\u2026", "LIVE_DEV_IMAGE_GALLERY_SEARCH_BUTTON": "Search", @@ -200,9 +199,9 @@ define({ "LIVE_DEV_IMAGE_GALLERY_NO_IMAGES": "No images found", "LIVE_DEV_IMAGE_GALLERY_LOAD_ERROR": "Failed to load images", "LIVE_DEV_IMAGE_GALLERY_CLOSE": "Close", - "LIVE_DEV_IMAGE_GALLERY_UPLOAD": "Upload", + "LIVE_DEV_IMAGE_GALLERY_SELECT_FROM_COMPUTER_TOOLTIP": "Select an image from your device", + "LIVE_DEV_IMAGE_GALLERY_SELECT_FROM_COMPUTER": "Select from device", "LIVE_DEV_TOAST_NOT_EDITABLE": "Element not editable - generated by script.", - "LIVE_DEV_TOAST_DONT_SHOW_AGAIN": "Don't show again", "LIVE_DEV_IMAGE_FOLDER_DIALOG_TITLE": "Select Folder to Save Image", "LIVE_DEV_IMAGE_FOLDER_DIALOG_DESCRIPTION": "Choose where to download the image:", "LIVE_DEV_IMAGE_FOLDER_DIALOG_PLACEHOLDER": "Type folder path (e.g., assets/images/)", @@ -210,15 +209,13 @@ define({ "LIVE_DEV_IMAGE_FOLDER_DIALOG_REMEMBER": "Don't ask again for this project", "LIVE_DEV_AI_PROMPT_PLACEHOLDER": "Ask Phoenix AI to modify this element...", "LIVE_PREVIEW_CUSTOM_SERVER_BANNER": "Getting preview from your custom server {0}", + "LIVE_PREVIEW_MODE_TOGGLE_PREVIEW": "Toggle Preview Mode (F8)", "LIVE_PREVIEW_MODE_PREVIEW": "Preview Mode", "LIVE_PREVIEW_MODE_HIGHLIGHT": "Highlight Mode", "LIVE_PREVIEW_MODE_EDIT": "Edit Mode", "LIVE_PREVIEW_EDIT_HIGHLIGHT_ON": "Edit Highlights on Hover", "LIVE_PREVIEW_MODE_PREFERENCE": "{0} shows only the webpage, {1} connects the webpage to your code - click on elements to jump to their code and vice versa, {2} provides highlighting along with advanced element manipulation", "LIVE_PREVIEW_CONFIGURE_MODES": "Configure Live Preview Modes", - "LIVE_PREVIEW_PRO_FEATURE_TITLE": "Pro Feature", - "LIVE_PREVIEW_PRO_FEATURE_MESSAGE": "This is a Pro feature. Subscribe to Phoenix Pro to keep using this feature.", - "LIVE_PREVIEW_PRO_SUBSCRIBE": "Subscribe", "LIVE_DEV_DETACHED_REPLACED_WITH_DEVTOOLS": "Live Preview was canceled because the browser's developer tools were opened", "LIVE_DEV_DETACHED_TARGET_CLOSED": "Live Preview was canceled because the page was closed in the browser",