From 7c23e8e7ac5cd10a3e1dcb35e667f883c06f0976 Mon Sep 17 00:00:00 2001 From: nightwing Date: Mon, 18 Dec 2017 00:20:17 +0400 Subject: [PATCH] autosave on blur --- plugins/c9.ide.editors/tabmanager.js | 2 +- plugins/c9.ide.save/autosave.js | 216 ++++++++++++--------------- plugins/c9.ide.save/autosave_test.js | 123 +++++++++------ plugins/c9.ide.ui/lib/page.js | 2 + plugins/c9.ide.ui/lib_apf.js | 9 +- 5 files changed, 181 insertions(+), 171 deletions(-) diff --git a/plugins/c9.ide.editors/tabmanager.js b/plugins/c9.ide.editors/tabmanager.js index fd0be714..ef663ffe 100644 --- a/plugins/c9.ide.editors/tabmanager.js +++ b/plugins/c9.ide.editors/tabmanager.js @@ -630,7 +630,7 @@ define(function(require, module, exports) { if (!loaded || tab.document.meta.preview) return; - var lastTab = focussedTab; + var lastTab = e.lastTab || focussedTab; if (!focussedTab || focussedTab.pane == tab.pane && focussedTab != tab) focusTab(tab, true, true); diff --git a/plugins/c9.ide.save/autosave.js b/plugins/c9.ide.save/autosave.js index 539591e2..9ae008c9 100644 --- a/plugins/c9.ide.save/autosave.js +++ b/plugins/c9.ide.save/autosave.js @@ -1,122 +1,117 @@ define(function(require, exports, module) { main.consumes = [ - "Plugin", "c9", "ui", "layout", "tooltip", - "anims", "menus", "tabManager", "save", - "preferences.experimental" + "Plugin", "c9", "settings", "tabManager", "preferences", "save", "apf" ]; main.provides = ["autosave"]; return main; function main(options, imports, register) { var c9 = imports.c9; - var Plugin = imports.Plugin; + var apf = imports.apf; var save = imports.save; - var tooltip = imports.tooltip; var tabs = imports.tabManager; - var experimental = imports["preferences.experimental"]; + var prefs = imports.preferences; + var Plugin = imports.Plugin; + var settings = imports.settings; /***** Initialization *****/ var plugin = new Plugin("Ajax.org", main.consumes); - var INTERVAL = 60000; var CHANGE_TIMEOUT = 500; var SLOW_CHANGE_TIMEOUT = options.slowChangeTimeout || 30000; var SLOW_SAVE_THRESHOLD = 100 * 1024; // 100KB - var docChangeTimeout = null; - var btnSave, autosave = true, saveInterval; - var enabled = options.testing - || experimental.addExperiment("autosave", false, "Files/Auto-Save"); + var docChangeTimeout; + var autosave; - var loaded = false; function load() { - if (loaded || !enabled) return false; - loaded = true; - - // when we're back online we'll trigger an autosave if enabled - c9.on("stateChange", function(e) { - if (e.state & c9.STORAGE && !(e.last & c9.STORAGE)) - check(); - }, plugin); - - save.getElement("btnSave", function(btn) { - btnSave = btn; - transformButton(); - }); - - tabs.on("tabCreate", function(e) { - var tab = e.tab; - tab.document.undoManager.on("change", function(e) { - if (!autosave || !tab.path) - return; - - clearTimeout(docChangeTimeout); - docChangeTimeout = setTimeout(function() { - saveTab(tab); - }, tab.document.meta.$slowSave - ? SLOW_CHANGE_TIMEOUT - : CHANGE_TIMEOUT); - }, plugin); - }, plugin); - - tabs.on("tabDestroy", function(e) { - if (!e.tab.path) - return; - - if (tabs.getTabs().length == 1) - btnSave.hide(); - - saveTab(e.tab); - }, plugin); - - save.on("beforeWarn", function(e) { - if (autosave && !e.tab.document.meta.newfile) { - saveTab(e.tab); - return false; + prefs.add({ + "File": { + position: 150, + "Save": { + position: 100, + "Enable Auto-Save On Blur": { + type: "checkbox", + position: 100, + path: "user/general/@autosave" + } + } } }, plugin); - } - - function transformButton() { - if (!btnSave) return; - if (btnSave.autosave === autosave) return; - if (autosave) { - // Transform btnSave - btnSave.setAttribute("caption", ""); - btnSave.setAttribute("margin", "0 20"); - btnSave.removeAttribute("tooltip"); - btnSave.removeAttribute("command"); - apf.setStyleClass(btnSave.$ext, "btnSave"); - - tooltip.add(btnSave, { - message: "Changes to your file are automatically saved.
\ - View all your changes through the Revision History pane. \ - Rollback to a previous state, or make comparisons.", - width: "250px", - hideonclick: true - }, plugin); - } - else { - - } - - btnSave.autosave = autosave; + settings.setDefaults("user/general", [["autosave", false]]); + settings.on("read", onSettingChange, plugin); + settings.on("user/general", onSettingChange, plugin); + save.on("beforeWarn", function(e) { + if (autosave && saveTab(e.tab)) + return false; + }, plugin); } /***** Helpers *****/ - - function check() { - if (!autosave) return; - - var pages = tabs.getTabs(); - for (var tab, i = 0, l = pages.length; i < l; i++) { - if ((tab = pages[i]).document.changed && tab.path) - saveTab(tab); + + function onSettingChange() { + autosave = settings.getBool("user/general/@autosave"); + if (autosave) + enable(); + else + disable(); + } + + function enable() { + apf.on("movefocus", scheduleCheck); + tabs.on("tabAfterActivate", scheduleCheck, plugin); + window.addEventListener("blur", scheduleCheck); + } + + function disable() { + apf.off("movefocus", scheduleCheck); + tabs.off("tabAfterActivate", scheduleCheck); + window.removeEventListener("blur", scheduleCheck); + } + + function scheduleCheck(e) { + if (docChangeTimeout) + return; + var tab; + var fromElement = e.fromElement; + var toElement = e.toElement; + if (e.type == "blur") { + tab = tabs.focussedTab; } + else if (fromElement) { + var fakePage = fromElement.$fake; + if (toElement && (toElement == fakePage || fromElement == toElement.$fake)) { + fakePage = fromElement.$prevFake || toElement.$prevFake; + if (fakePage) + return; + } + + tab = fromElement.cloud9tab || fakePage && fakePage.cloud9tab; + if (!tab || !tab.path) + return; + while (toElement) { + if (/window|menu|item/.test(toElement.localName)) + return; + toElement = toElement.parentNode; + } + } + else if (e.lastTab) { + tab = e.lastTab; + } + if (!tab || !tab.path) + return; + + docChangeTimeout = setTimeout(function() { + docChangeTimeout = null; + var activeElement = apf.document.activeElement; + var nodeName = activeElement && activeElement.localName; + // do nothing if the tab is still focused, or is a clone of the focussed tab + if (nodeName === "page" && tabs.focussedTab && tabs.focussedTab.path === tab.path) + return; + saveTab(tab); + }); } function saveTab(tab, force) { @@ -133,22 +128,16 @@ define(function(require, exports, module) { || doc.meta.newfile || doc.meta.nofs || doc.meta.error - || doc.meta.$saving)) + || doc.meta.$saving + || doc.meta.preview + || !doc.hasValue())) return; - - var value = doc.value; - var slow = value.length > SLOW_SAVE_THRESHOLD; - if (slow && !doc.meta.$slowSave) { - doc.meta.$slowSave = true; - return; - } - doc.meta.$slowSave = slow; - + save.save(tab, { silentsave: true, - timeout: 1, - value: value }, function() {}); + + return true; } /***** Lifecycle *****/ @@ -156,27 +145,20 @@ define(function(require, exports, module) { plugin.on("load", function() { load(); }); - plugin.on("enable", function() { - autosave = true; - transformButton(); - }); - plugin.on("disable", function() { - autosave = false; - transformButton(); - }); plugin.on("unload", function() { - if (saveInterval) - clearInterval(saveInterval); - - loaded = false; + window.removeEventListener("blur", scheduleCheck); + autosave = false; + if (docChangeTimeout) { + clearTimeout(docChangeTimeout); + docChangeTimeout = null; + } }); /***** Register and define API *****/ /** * Implements auto save for Cloud9. When the user enables autosave - * the contents of files are automatically saved about 500ms after the - * change is made. + * the contents of files are automatically saved when the editor is blurred * @singleton **/ plugin.freezePublicAPI({ }); diff --git a/plugins/c9.ide.save/autosave_test.js b/plugins/c9.ide.save/autosave_test.js index 70bb4dba..b4381962 100644 --- a/plugins/c9.ide.save/autosave_test.js +++ b/plugins/c9.ide.save/autosave_test.js @@ -2,8 +2,7 @@ "use client"; -require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], - function (architect, chai, baseProc) { +require(["lib/chai/chai"], function (chai) { var expect = chai.expect; document.body.appendChild(document.createElement("div")) @@ -16,7 +15,6 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], debug: true, hosted: true, local: false, - davPrefix: "/" }, "plugins/c9.core/ext", @@ -37,7 +35,7 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], "plugins/c9.ide.editors/undomanager", { packagePath: "plugins/c9.ide.editors/editors", - defaultEditor: "texteditor" + defaultEditor: "ace" }, "plugins/c9.ide.editors/editor", "plugins/c9.ide.editors/tabmanager", @@ -48,17 +46,15 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], "plugins/c9.ide.save/save", { packagePath: "plugins/c9.ide.save/autosave", - testing: true }, { - packagePath: "plugins/c9.vfs.client/vfs_client" + packagePath: "plugins/c9.vfs.client/vfs_client_mock", + storage: false }, - "plugins/c9.vfs.client/endpoint", - "plugins/c9.ide.auth/auth", "plugins/c9.core/api", { packagePath: "plugins/c9.fs/fs", - baseProc: baseProc + cli: true }, "plugins/c9.fs/fs.cache.xml", @@ -67,27 +63,15 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], provides: [], setup: main } - ], architect); + ]); function main(options, imports, register) { + var settings = imports.settings var tabs = imports.tabManager; var fs = imports.fs; var save = imports.save; var autosave = imports.autosave; - function countEvents(count, expected, done) { - if (count == expected) - done(); - else - throw new Error("Wrong Event Count: " - + count + " of " + expected); - } - - expect.html.setConstructor(function(tab) { - if (typeof tab == "object") - return tab.pane.aml.getPage("editor::" + tab.editorType).$ext; - }); - function changeTab(path, done) { var tab = tabs.findTab(path); tabs.focusTab(tab); @@ -98,41 +82,90 @@ require(["lib/architect/architect", "lib/chai/chai", "/vfs-root"], return tab; } + function createAndChangeTab(path, options, done) { + if (!done) { + done = options; + options = {}; + } + fs.writeFile(path, path, function(err) { + if (err) throw err; + + options.path = path; + tabs.open(options, function() { + changeTab(path, done); + }); + }); + } + describe('autosave', function() { this.timeout(5000); - before(function(done) { + beforeEach(function(done) { tabs.once("ready", function() { - tabs.getPanes()[0].focus(); - var path = "/autosave1.txt"; - fs.writeFile(path, path, function(err) { - if (err) throw err; - - tabs.openFile(path, function() { - setTimeout(done, 50); - }); + tabs.setState(null, function() { + tabs.getPanes()[0].focus(); + done(); }); }); - - bar.$ext.style.background = "rgba(220, 220, 220, 0.93)"; - bar.$ext.style.position = "fixed"; - bar.$ext.style.left = "20px"; - bar.$ext.style.right = "20px"; - bar.$ext.style.bottom = "20px"; - bar.$ext.style.height = "150px"; - - document.body.style.marginBottom = "180px"; }); - it('should automatically save a tab that is changed', function(done) { - var path = "/autosave1.txt"; - changeTab(path, function(tab) { + it("should not autosave when restoring state", function(done) { + settings.set("user/general/@autosave", false); + var pane = tabs.getPanes()[0].hsplit(true); + + prepareTabs(testRestoreState); + + function prepareTabs(callback) { + createAndChangeTab("/autosave1.txt", function(tab) { + expect(tab.document.changed).to.ok; + createAndChangeTab("/__proto__", function() { + createAndChangeTab("/

", function() { + createAndChangeTab("/__lookupSetter__", { pane: pane }, function() { + callback(); + }); + }); + }); + }); + } + function testRestoreState() { + settings.set("user/general/@autosave", true); + expect(tabs.getTabs().length).to.equal(4); + var state = tabs.getState(null, true); + tabs.setState(null, function() { + expect(tabs.getTabs().length).to.equal(0); + setTimeout(function() { + tabs.setState(state, function() { + expect(tabs.getTabs().length).to.equal(4); + expect(tabs.getTabs()[0].document.changed).to.ok; + setTimeout(function() { + tabs.setState(null, function() { + save.off("afterSave", preventSave); + done(); + }); + }); + }); + }); + }); + } + + function preventSave() { + done(new Error("Save is called")); + } + save.once("afterSave", preventSave); + }); + + it("should automatically save a tab that is changed when editor is blurred", function(done) { + settings.set("user/general/@autosave", true); + var path = "/autosave2.txt"; + createAndChangeTab(path, function(tab) { expect(tab.document.changed).to.ok; - + createAndChangeTab("/__proto__", function() { + }); save.once("afterSave", function() { fs.readFile(path, function(err, data) { if (err) throw err; expect(data).to.equal("test" + path); + expect(tab.document.changed).to.not.ok; fs.unlink(path, function() { done(); diff --git a/plugins/c9.ide.ui/lib/page.js b/plugins/c9.ide.ui/lib/page.js index 04fabb7f..6e66fac9 100644 --- a/plugins/c9.ide.ui/lib/page.js +++ b/plugins/c9.ide.ui/lib/page.js @@ -393,6 +393,7 @@ apf.page = function(struct, tagName) { if (this.relPage) { this.relPage.$ext.style.display = ""; this.parentNode.$setStyleClass(this.relPage.$ext, "curpage"); + this.relPage.$prevFake = this.relPage.$fake; this.relPage.$fake = this; @@ -604,6 +605,7 @@ apf.page = function(struct, tagName) { if (page && page.type == _self.id) { page.relPage = _self; if (page.$active) { + _self.$prevFake = _self.$fake; _self.$fake = page; page.$activate(); } diff --git a/plugins/c9.ide.ui/lib_apf.js b/plugins/c9.ide.ui/lib_apf.js index a21f623d..35b5b931 100644 --- a/plugins/c9.ide.ui/lib_apf.js +++ b/plugins/c9.ide.ui/lib_apf.js @@ -10966,16 +10966,9 @@ apf.window = function(){ (apf.window.activeElement = amlNode).focus(true, e); this.$settingFocus = null; - - apf.dispatchEvent("movefocus", { - toElement: amlNode - }); - - - - + apf.dispatchEvent("movefocus", e); }; this.$blur = function(amlNode) {