diff --git a/src/LiveDevelopment/LiveDevMultiBrowser.js b/src/LiveDevelopment/LiveDevMultiBrowser.js index b84e15290e..93a1ae6740 100644 --- a/src/LiveDevelopment/LiveDevMultiBrowser.js +++ b/src/LiveDevelopment/LiveDevMultiBrowser.js @@ -88,6 +88,7 @@ define(function (require, exports, module) { LiveDevProtocol = require("LiveDevelopment/MultiBrowserImpl/protocol/LiveDevProtocol"), Metrics = require("utils/Metrics"), WorkspaceManager = require("view/WorkspaceManager"), + FileSystem = require("filesystem/FileSystem"), PageLoaderWorkerScript = require("text!LiveDevelopment/BrowserScripts/pageLoaderWorker.js"); // Documents @@ -538,15 +539,34 @@ define(function (require, exports, module) { } } + let _lastPreviewedHTMLFilePath; + async function _resolveLivePreviewDocument() { + let activeDocument = DocumentManager.getCurrentDocument(); + if(livePreviewUrlPinned){ + return jsPromise(DocumentManager.getDocumentForPath(currentPreviewFilePath)); + } + if (activeDocument && activeDocument.getLanguage().getId() === "html") { + _lastPreviewedHTMLFilePath = activeDocument.file.fullPath; + return activeDocument; + } + // if a css/js/non html file is active, we fall back to the last previewed HTML file + let fileExists = await FileSystem.existsAsync(_lastPreviewedHTMLFilePath); + if(!fileExists){ + // the last previewed file may have been deleted + return activeDocument; // can be null + } + return jsPromise(DocumentManager.getDocumentForPath(_lastPreviewedHTMLFilePath)); + } + + ProjectManager.on(ProjectManager.EVENT_PROJECT_OPEN, ()=>{ + _lastPreviewedHTMLFilePath = null; + }); /** * Open a live preview on the current docuemnt. */ async function open() { - let doc = DocumentManager.getCurrentDocument(); - if(livePreviewUrlPinned){ - doc = await jsPromise(DocumentManager.getDocumentForPath(currentPreviewFilePath)); - } + let doc = await _resolveLivePreviewDocument(); // wait for server (StaticServer, Base URL or file:) _prepareServer(doc) diff --git a/src/document/DocumentCommandHandlers.js b/src/document/DocumentCommandHandlers.js index 3943af04d1..9029468d3d 100644 --- a/src/document/DocumentCommandHandlers.js +++ b/src/document/DocumentCommandHandlers.js @@ -59,6 +59,7 @@ define(function (require, exports, module) { NewFileContentManager = require("features/NewFileContentManager"), NodeConnector = require("NodeConnector"), NodeUtils = require("utils/NodeUtils"), + ChangeHelper = require("editor/EditorHelper/ChangeHelper"), _ = require("thirdparty/lodash"); const KernalModeTrust = window.KernalModeTrust; @@ -1243,11 +1244,13 @@ define(function (require, exports, module) { * USER_CANCELED object). */ function handleFileSave(commandData) { - // todo save interceptor - var activeEditor = EditorManager.getActiveEditor(), + const activeEditor = EditorManager.getActiveEditor(), activeDoc = activeEditor && activeEditor.document, - doc = (commandData && commandData.doc) || activeDoc, - settings; + doc = (commandData && commandData.doc) || activeDoc; + let settings; + if(ChangeHelper._onBeforeSave(doc)){ + return $.Deferred().reject().promise(); + } if (doc && !doc.isSaving) { if (doc.isUntitled()) { diff --git a/src/editor/EditorHelper/ChangeHelper.js b/src/editor/EditorHelper/ChangeHelper.js index f010e4b030..d9a18c1e09 100644 --- a/src/editor/EditorHelper/ChangeHelper.js +++ b/src/editor/EditorHelper/ChangeHelper.js @@ -19,6 +19,8 @@ * */ +/*global logger*/ + /** * Editor instance helpers change handling. Only to be used from Editor.js. */ @@ -175,9 +177,15 @@ define(function (require, exports, module) { // Redispatch these CodeMirror key events as Editor events function _onKeyEvent(instance, event) { - if(_keyEventInterceptor && _keyEventInterceptor(self, self._codeMirror, event)){ - // the interceptor processed it, so don't pass it along to CodeMirror' - return; + if(_keyEventInterceptor){ + try { + if(_keyEventInterceptor(self, self._codeMirror, event)){ + // the interceptor processed it, so don't pass it along to CodeMirror' + return; + } + } catch (e) { + logger.reportError(e, "Error in key event interceptor"); + } } self.trigger("keyEvent", self, event); // deprecated self.trigger(event.type, self, event); @@ -254,7 +262,11 @@ define(function (require, exports, module) { self._codeMirror.on("cut", function(cm, e) { // Let interceptor decide what to do with the event (including preventDefault) if (_cutInterceptor) { - return _cutInterceptor(self, cm, e); + try { + return _cutInterceptor(self, cm, e); + } catch (e) { + logger.reportError(e, "Error in cut interceptor"); + } } // Otherwise allow normal cut behavior }); @@ -262,7 +274,11 @@ define(function (require, exports, module) { self._codeMirror.on("copy", function(cm, e) { // Let interceptor decide what to do with the event (including preventDefault) if (_copyInterceptor) { - return _copyInterceptor(self, cm, e); + try { + return _copyInterceptor(self, cm, e); + } catch (e) { + logger.reportError(e, "Error in copy interceptor"); + } } // Otherwise allow normal copy behavior }); @@ -270,7 +286,11 @@ define(function (require, exports, module) { self._codeMirror.on("paste", function(cm, e) { // Let interceptor decide what to do with the event (including preventDefault) if (_pasteInterceptor) { - return _pasteInterceptor(self, cm, e); + try { + return _pasteInterceptor(self, cm, e); + } catch (e) { + logger.reportError(e, "Error in paste interceptor"); + } } // Otherwise allow normal paste behavior }); @@ -316,19 +336,42 @@ define(function (require, exports, module) { let _undoInterceptor = null; let _redoInterceptor = null; + let _saveInterceptor = null; + + function _onBeforeSave(docBeingSaved) { + if(!_saveInterceptor){ + return false; + } + try{ + return _saveInterceptor(docBeingSaved); + } catch (e) { + logger.reportError(e, "Error in save interceptor"); + return false; + } + } function _onBeforeUndo(editor, codeMirror, event) { if(!_undoInterceptor){ return false; } - return _undoInterceptor(editor, codeMirror, event); + try { + return _undoInterceptor(editor, codeMirror, event); + } catch (e) { + logger.reportError(e, "Error in undo interceptor"); + return false; + } } function _onBeforeRedo(editor, codeMirror, event) { if(!_redoInterceptor){ return false; } - return _redoInterceptor(editor, codeMirror, event); + try { + return _redoInterceptor(editor, codeMirror, event); + } catch (e) { + logger.reportError(e, "Error in redo interceptor"); + return false; + } } /** @@ -379,9 +422,18 @@ define(function (require, exports, module) { _keyEventInterceptor = interceptor; } + /** + * Sets the key down/up/press interceptor function in codemirror + * @param {Function} interceptor - Function(editor, cm, event) that returns true to preventDefault + */ + function setSaveInterceptor(interceptor) { + _saveInterceptor = interceptor; + } + // private exports exports._onBeforeUndo =_onBeforeUndo; exports._onBeforeRedo = _onBeforeRedo; + exports._onBeforeSave = _onBeforeSave; // public exports exports.addHelpers = addHelpers; @@ -391,4 +443,5 @@ define(function (require, exports, module) { exports.setCopyInterceptor = setCopyInterceptor; exports.setPasteInterceptor = setPasteInterceptor; exports.setKeyEventInterceptor = setKeyEventInterceptor; + exports.setSaveInterceptor = setSaveInterceptor; }); diff --git a/src/extensionsIntegrated/Phoenix-live-preview/BrowserStaticServer.js b/src/extensionsIntegrated/Phoenix-live-preview/BrowserStaticServer.js index e656669fa6..8eeec7bfc8 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/BrowserStaticServer.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/BrowserStaticServer.js @@ -128,6 +128,8 @@ define(function (require, exports, module) { return Phoenix.isNativeApp || !(Phoenix.browser.desktop.isSafari || Phoenix.browser.mobile.isIos); } + let _lastPreviewedFilePath; + /** * Finds out a {URL,filePath} to live preview from the project. Will return and empty object if the current * file is not previewable. @@ -179,33 +181,24 @@ define(function (require, exports, module) { }); return; } else if(utils.isPreviewableFile(fullPath)){ - const filePath = httpFilePath || path.relative(projectRoot, fullPath); + // this is the case where the user has html/svg/any previewable file as the active document + _lastPreviewedFilePath = fullPath; + } + let fileExists = await FileSystem.existsAsync(_lastPreviewedFilePath); + if(_lastPreviewedFilePath && fileExists){ + // user either has active document as a previewable file or this is the case where + // user switched to a css/js/other file that is not previewable, but we have on old previewable + // file we will just take the _lastPreviewedFilePath as active + const filePath = httpFilePath || path.relative(projectRoot, _lastPreviewedFilePath); let URL = httpFilePath || `${projectRootUrl}${filePath}`; resolve({ URL, filePath: filePath, - fullPath: fullPath, - isMarkdownFile: utils.isMarkdownFile(fullPath), - isHTMLFile: utils.isHTMLFile(fullPath) + fullPath: _lastPreviewedFilePath, + isMarkdownFile: utils.isMarkdownFile(_lastPreviewedFilePath), + isHTMLFile: utils.isHTMLFile(_lastPreviewedFilePath) }); return; - } else { - const currentLivePreviewDetails = LiveDevelopment.getLivePreviewDetails(); - if(currentLivePreviewDetails && currentLivePreviewDetails.liveDocument - && currentLivePreviewDetails.liveDocument.isRelated - && currentLivePreviewDetails.liveDocument.isRelated(fullPath)){ - fullPath = currentLivePreviewDetails.liveDocument.doc.file.fullPath; - const filePath = path.relative(projectRoot, fullPath); - let URL = `${projectRootUrl}${filePath}`; - resolve({ - URL, - filePath: filePath, - fullPath: fullPath, - isMarkdownFile: utils.isMarkdownFile(fullPath), - isHTMLFile: utils.isHTMLFile(fullPath) - }); - return; - } } } resolve({ @@ -588,14 +581,16 @@ define(function (require, exports, module) { if(!url.startsWith(_staticServerInstance.getBaseUrl())) { return Promise.reject("Not serving content as url belongs to another phcode instance: " + url); } - if(utils.isMarkdownFile(path) && currentFile && currentFile.fullPath === path){ + const shouldServeRendered = ((path === _lastPreviewedFilePath) + || (currentFile && currentFile.fullPath === path)); + if(utils.isMarkdownFile(path) && shouldServeRendered){ return _getMarkdown(path); } if(_staticServerInstance){ return _staticServerInstance._getInstrumentedContent(path, url); } return Promise.reject("Cannot get content"); - }; + } /** * See BaseServer#start. Starts listenting to StaticServerDomain events. @@ -712,6 +707,7 @@ define(function (require, exports, module) { } function _projectOpened(_evt, projectRoot) { + _lastPreviewedFilePath = null; navigatorChannel.postMessage({ type: 'PROJECT_SWITCH', projectRoot: projectRoot.fullPath diff --git a/src/extensionsIntegrated/Phoenix-live-preview/NodeStaticServer.js b/src/extensionsIntegrated/Phoenix-live-preview/NodeStaticServer.js index 77fb640a0c..e89fc2dc68 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/NodeStaticServer.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/NodeStaticServer.js @@ -431,7 +431,9 @@ define(function (require, exports, module) { return Promise.reject("Not serving content as url invalid: " + url); } const filePath = _staticServerInstance.urlToPath(url); - if(utils.isMarkdownFile(filePath) && currentFile && currentFile.fullPath === filePath){ + const shouldServeRendered = ((filePath === _lastPreviewedFilePath) + || (currentFile && currentFile.fullPath === path)); + if(utils.isMarkdownFile(filePath) && shouldServeRendered){ return _getMarkdown(filePath); } if(_staticServerInstance){ @@ -580,6 +582,7 @@ define(function (require, exports, module) { } function _projectOpened(_evt, projectRoot) { + _lastPreviewedFilePath = null; liveServerConnector.execPeer('navMessageProjectOpened', { type: 'PROJECT_SWITCH', projectRoot: projectRoot.fullPath @@ -635,6 +638,8 @@ define(function (require, exports, module) { return livePreviewTabs.size > 0; } + let _lastPreviewedFilePath; + /** * Finds out a {URL,filePath} to live preview from the project. Will return and empty object if the current * file is not previewable. @@ -703,33 +708,24 @@ define(function (require, exports, module) { }); return; } else if(utils.isPreviewableFile(fullPath)){ - const relativeFilePath = httpFilePath || path.relative(projectRoot, fullPath); - let URL = httpFilePath || decodeURI(_staticServerInstance.pathToUrl(fullPath)); + // this is the case where the user has html/svg/any previewable file as the active document + _lastPreviewedFilePath = fullPath; + } + let fileExists = await FileSystem.existsAsync(_lastPreviewedFilePath); + if(_lastPreviewedFilePath && fileExists){ + // user either has active document as a previewable file or this is the case where + // user switched to a css/js/other file that is not previewable, but we have on old previewable + // file we will just take the _lastPreviewedFilePath as active + const relativeFilePath = httpFilePath || path.relative(projectRoot, _lastPreviewedFilePath); + let URL = httpFilePath || decodeURI(_staticServerInstance.pathToUrl(_lastPreviewedFilePath)); resolve({ URL, filePath: relativeFilePath, - fullPath: fullPath, - isMarkdownFile: utils.isMarkdownFile(fullPath), - isHTMLFile: utils.isHTMLFile(fullPath) + fullPath: _lastPreviewedFilePath, + isMarkdownFile: utils.isMarkdownFile(_lastPreviewedFilePath), + isHTMLFile: utils.isHTMLFile(_lastPreviewedFilePath) }); return; - } else { - const currentLivePreviewDetails = LiveDevelopment.getLivePreviewDetails(); - if(currentLivePreviewDetails && currentLivePreviewDetails.liveDocument - && currentLivePreviewDetails.liveDocument.isRelated - && currentLivePreviewDetails.liveDocument.isRelated(fullPath)){ - fullPath = currentLivePreviewDetails.liveDocument.doc.file.fullPath; - const relativeFilePath = httpFilePath || path.relative(projectRoot, fullPath); - let URL = httpFilePath || decodeURI(_staticServerInstance.pathToUrl(fullPath)); - resolve({ - URL, - filePath: relativeFilePath, - fullPath: fullPath, - isMarkdownFile: utils.isMarkdownFile(fullPath), - isHTMLFile: utils.isHTMLFile(fullPath) - }); - return; - } } resolve({ URL: getNoPreviewURL(), diff --git a/src/extensionsIntegrated/Phoenix-live-preview/main.js b/src/extensionsIntegrated/Phoenix-live-preview/main.js index 5319ac2ee3..65b95842b3 100644 --- a/src/extensionsIntegrated/Phoenix-live-preview/main.js +++ b/src/extensionsIntegrated/Phoenix-live-preview/main.js @@ -823,13 +823,7 @@ define(function (require, exports, module) { currentLivePreviewURL = newSrc; currentPreviewFile = previewDetails.fullPath; } - const existingPreviewFile = $iframe && $iframe.attr('data-original-path'); - const existingPreviewURL = $iframe && $iframe.attr('data-original-src'); - if(isReload && previewDetails.isNoPreview && existingPreviewURL && - existingPreviewFile && ProjectManager.isWithinProject(existingPreviewFile)) { - currentLivePreviewURL = existingPreviewURL; - currentPreviewFile = existingPreviewFile; - } else if(isReload){ + if(isReload && previewDetails.isHTMLFile){ LiveDevelopment.openLivePreview(); } let relativeOrFullPath= ProjectManager.makeProjectRelativeIfPossible(currentPreviewFile); @@ -852,10 +846,6 @@ define(function (require, exports, module) { $iframe = newIframe; if(_isProjectPreviewTrusted()){ $iframe.attr('src', currentLivePreviewURL); - // we have to save src as the iframe src attribute may have redirected, and we cannot read it as its - // a third party domain once its redirected. - $iframe.attr('data-original-src', currentLivePreviewURL); - $iframe.attr('data-original-path', currentPreviewFile); if(Phoenix.isTestWindow) { window._livePreviewIntegTest.currentLivePreviewURL = currentLivePreviewURL; window._livePreviewIntegTest.urlLoadCount++; diff --git a/src/filesystem/FileSystem.js b/src/filesystem/FileSystem.js index c75ea8ddea..3b5d659b61 100644 --- a/src/filesystem/FileSystem.js +++ b/src/filesystem/FileSystem.js @@ -755,6 +755,9 @@ define(function (require, exports, module) { * @param {function(?string, boolean)} callback */ FileSystem.prototype.existsAsync = function (path) { + if(!path) { + return Promise.resolve(false); + } return this._impl.existsAsync(path); }; diff --git a/tracking-repos.json b/tracking-repos.json index 1f8dc3cab7..694003a4fe 100644 --- a/tracking-repos.json +++ b/tracking-repos.json @@ -1,5 +1,5 @@ { "phoenixPro": { - "commitID": "19d997d0aa98acb3507b3cb19b81c286ebcfd474" + "commitID": "c30d49834ab199d805ce489e9fc858ea0189abeb" } }