diff --git a/configs/client-default.js b/configs/client-default.js index 7756c0fc..c7088501 100644 --- a/configs/client-default.js +++ b/configs/client-default.js @@ -317,7 +317,10 @@ module.exports = function(options) { "plugins/c9.ide.language/tooltip", "plugins/c9.ide.language/jumptodef", "plugins/c9.ide.language/worker_util_helper", - "plugins/c9.ide.language.generic/generic", + { + packagePath: "plugins/c9.ide.language.generic/generic", + mode_completer: options.ssh, + }, "plugins/c9.ide.language.css/css", "plugins/c9.ide.language.html/html", "plugins/c9.ide.language.javascript/javascript", @@ -720,12 +723,6 @@ module.exports = function(options) { packagePath: "plugins/c9.cli.bridge/bridge_commands", basePath: workspaceDir }, - { - packagePath: "plugins/c9.ide.help.support/support", - baseurl: options.ideBaseUrl, - userSnapApiKey: options.support.userSnapApiKey, - screenshotSupport: true - }, { packagePath: "plugins/c9.ide.help/help", staticPrefix: staticPrefix + "/plugins/c9.ide.help" @@ -812,13 +809,28 @@ module.exports = function(options) { "plugins/c9.ide.test.mocha/mocha", + // git integration v2 + // { + // packagePath: "plugins/c9.ide.scm/scm.commit", + // staticPrefix: staticPrefix + "/plugins/c9.ide.scm" + // }, + // "plugins/c9.ide.scm/scm", + // "plugins/c9.ide.scm/scm.branches", + // "plugins/c9.ide.scm/dialog.localchanges", + // "plugins/c9.ide.scm/scm.log", + // "plugins/c9.ide.scm/git", + // "plugins/c9.ide.scm/diff.split", + // "plugins/c9.ide.scm/diff.unified", + + // // git integration v1 + "plugins/c9.ide.scm/v1/scm", + "plugins/c9.ide.scm/v1/scmpanel", + "plugins/c9.ide.scm/v1/detail", + "plugins/c9.ide.scm/v1/log", + "plugins/c9.ide.scm/v1/git", + "plugins/c9.ide.scm/v1/editor", + // git integration - "plugins/c9.ide.scm/scm", - "plugins/c9.ide.scm/scmpanel", - "plugins/c9.ide.scm/detail", - "plugins/c9.ide.scm/log", - "plugins/c9.ide.scm/git", - "plugins/c9.ide.scm/editor", "plugins/c9.ide.scm/mergetool" ]; @@ -907,8 +919,12 @@ module.exports = function(options) { }); } - if (options.platform !== "win32") - plugins.push("plugins/c9.ide.language.codeintel/codeintel"); + if (options.platform !== "win32") { + plugins.push({ + packagePath: "plugins/c9.ide.language.codeintel/codeintel", + preinstalled: hosted && !options.ssh, + }); + } return plugins; }; diff --git a/docs/CODING_STANDARDS.md b/docs/CODING_STANDARDS.md index 25bc3028..8318a955 100644 --- a/docs/CODING_STANDARDS.md +++ b/docs/CODING_STANDARDS.md @@ -716,6 +716,10 @@ Checking for branch naming consistency is part of the review process and the tea Generally, releasing changes affecting several services is a smell so this can help you identify possible issues. +You can now look for all PRs which made it in like so (api in this case): + + git log --oneline --first-parent SHA..origin/master | grep -v bump | grep api- + Other Resources =============== diff --git a/node_modules/ace/lib/ace/edit_session/folding.js b/node_modules/ace/lib/ace/edit_session/folding.js index fbab03fa..4e67fe45 100644 --- a/node_modules/ace/lib/ace/edit_session/folding.js +++ b/node_modules/ace/lib/ace/edit_session/folding.js @@ -772,7 +772,7 @@ function Folding() { this.removeFold(fold); else this.expandFold(fold); - return; + return fold; } var range = this.getFoldWidgetRange(row, true); @@ -781,7 +781,7 @@ function Folding() { fold = this.getFoldAt(range.start.row, range.start.column, 1); if (fold && range.isEqual(fold.range)) { this.removeFold(fold); - return; + return fold; } } diff --git a/node_modules/ace/lib/ace/ext/whitespace.js b/node_modules/ace/lib/ace/ext/whitespace.js index 7f39b092..04074988 100644 --- a/node_modules/ace/lib/ace/ext/whitespace.js +++ b/node_modules/ace/lib/ace/ext/whitespace.js @@ -127,10 +127,20 @@ exports.trimTrailingSpace = function(session, options) { var lines = doc.getAllLines(); var min = options && options.trimEmpty ? -1 : 0; - var cursors = session.selection.rangeCount - ? session.selection.ranges.map(function(x) { return x.cursor; }) - : [session.selection.getCursor()]; - var ci = options && options.keepCursorPosition ? 0 : -1; + var cursors = [], ci = -1; + if (options && options.keepCursorPosition) { + if (session.selection.rangeCount) { + session.selection.rangeList.ranges.forEach(function(x, i, ranges) { + var next = ranges[i + 1]; + if (next && next.cursor.row == x.cursor.row) + return; + cursors.push(x.cursor); + }); + } else { + cursors.push(session.selection.getCursor()); + } + ci = 0; + } var cursorRow = cursors[ci] && cursors[ci].row; for (var i = 0, l=lines.length; i < l; i++) { @@ -138,8 +148,8 @@ exports.trimTrailingSpace = function(session, options) { var index = line.search(/\s+$/); if (i == cursorRow) { - if (index < cursors[ci].column) - index = min; + if (index < cursors[ci].column && index > min) + index = cursors[ci].column; ci++; cursorRow = cursors[ci] ? cursors[ci].row : -1; } diff --git a/node_modules/ace/lib/ace/ext/whitespace_test.js b/node_modules/ace/lib/ace/ext/whitespace_test.js index 419ae74f..422d346c 100644 --- a/node_modules/ace/lib/ace/ext/whitespace_test.js +++ b/node_modules/ace/lib/ace/ext/whitespace_test.js @@ -1,13 +1,14 @@ if (typeof process !== "undefined") { require("amd-loader"); - require("../test/mockdom"); } define(function(require, exports, module) { "use strict"; +require("../multi_select"); var assert = require("assert"); var EditSession = require("../edit_session").EditSession; +var UndoManager = require("../undomanager").UndoManager; var whitespace = require("./whitespace"); // Execution ORDER: test.setUpSuite, setUp, testFn, tearDown, test.tearDownSuite @@ -111,8 +112,90 @@ module.exports = { assert.equal(indent.length, 1); next(); - } + }, + "test trimTrailingSpace": function(next) { + var session = new EditSession([ + "a", + "\t b \t", + " ", + "\t", + "\t\tx\t\t", + " ", + " " + ]); + session.setUndoManager(new UndoManager()); + + function testOne(value, options) { + console.log(JSON.stringify(session.getValue())) + + whitespace.trimTrailingSpace(session, options); + assert.equal(value, session.getValue()); + session.markUndoGroup(); + session.getUndoManager().undo(); + } + + testOne("a\n\t b\n \n\t\n\t\tx\n \n ") + + testOne("a\n\t b\n\n\n\t\tx\n\n", { + trimEmpty: true + }); + + session.selection.fromJSON([{ + start: {row:2,column:3}, + end: {row:4,column:4} + }]); + testOne("a\n\t b\n\n\n\t\tx\t\n\n", { + keepCursorPosition: true, + trimEmpty: true + }); + + session.selection.fromJSON([{ + start: {row:2,column:3}, + end: {row:4,column:4}, + isBackwards: true + }]); + testOne("a\n\t b\n \n\n\t\tx\n\n", { + keepCursorPosition: true, + trimEmpty: true + }); + + session.selection.$initRangeList(); + session.selection.fromJSON([{ + start: {row:2, column:3}, + end: {row:2,column:3} + }, { + start: {row:1, column:1}, + end: {row:1, column:1} + }, { + start: {row:2,column:2}, + end: {row:2,column:2} + }, { + start: {row:0,column:5}, + end: {row:0,column:5}, + isBackwards:false + }, { + start: {row:6,column:1}, + end: {row:6,column:1}, + isBackwards:false + }]); + testOne("a\n\t b\n \n\n\t\tx\n\n ", { + trimEmpty: true, + keepCursorPosition: true + }); + + session.setValue("some text"); + session.selection.fromJSON([{ + start: {row:0,column:4}, + end: {row:0,column:4} + }]); + testOne("some text", { + keepCursorPosition: true, + trimEmpty: true + }); + + next(); + }, }; }); diff --git a/node_modules/ace/lib/ace/line_widgets.js b/node_modules/ace/lib/ace/line_widgets.js index 6eaffc88..83b0af94 100644 --- a/node_modules/ace/lib/ace/line_widgets.js +++ b/node_modules/ace/lib/ace/line_widgets.js @@ -214,7 +214,7 @@ function LineWidgets(session) { if (!w.coverGutter) { w.el.style.zIndex = 3; } - if (!w.pixelHeight) { + if (w.pixelHeight == null) { w.pixelHeight = w.el.offsetHeight; } if (w.rowCount == null) { diff --git a/node_modules/ace/lib/ace/mode/diff.js b/node_modules/ace/lib/ace/mode/diff.js index 72671a27..f3c46a1b 100644 --- a/node_modules/ace/lib/ace/mode/diff.js +++ b/node_modules/ace/lib/ace/mode/diff.js @@ -38,7 +38,7 @@ var FoldMode = require("./folding/diff").FoldMode; var Mode = function() { this.HighlightRules = HighlightRules; - this.foldingRules = new FoldMode(["diff", "index", "\\+{3}", "@@|\\*{5}"], "i"); + this.foldingRules = new FoldMode(["diff", "@@|\\*{5}"], "i"); }; oop.inherits(Mode, TextMode); diff --git a/node_modules/ace/lib/ace/mode/folding/diff.js b/node_modules/ace/lib/ace/mode/folding/diff.js index 23c0e2d0..54185ad1 100644 --- a/node_modules/ace/lib/ace/mode/folding/diff.js +++ b/node_modules/ace/lib/ace/mode/folding/diff.js @@ -61,7 +61,7 @@ oop.inherits(FoldMode, BaseFoldMode); } if (row == start.row + 1) return; - return Range.fromPoints(start, {row: row - 1, column: line.length}); + return new Range(start.row, start.column, row - 1, line.length); }; }).call(FoldMode.prototype); diff --git a/node_modules/ace_tree/lib/ace_tree/data_provider.js b/node_modules/ace_tree/lib/ace_tree/data_provider.js index 4709fe1c..9bc095e0 100644 --- a/node_modules/ace_tree/lib/ace_tree/data_provider.js +++ b/node_modules/ace_tree/lib/ace_tree/data_provider.js @@ -49,7 +49,7 @@ var DataProvider = function(root) { }; this.open = - this.expand = function(node, deep, silent, justLoaded) { + this.expand = function(node, deep, silent) { if (typeof deep != "number") deep = deep ? 100 : 0; if (!node) @@ -69,7 +69,7 @@ var DataProvider = function(root) { this.collapse(node, null, true); node.status = "loaded"; if (!err) - this.expand(node, null, false, true); + this.expand(node, null, false); }.bind(this)); this.setOpen(node, true); return; @@ -95,9 +95,6 @@ var DataProvider = function(root) { } } - if (justLoaded) - node.justLoaded = true; - this.rows = items.length; silent || this._signal("expand", node); }; @@ -417,7 +414,7 @@ DataProvider.variableHeightRowMixin = function() { var items = this.visibleItems; var top = 0, index = 0, l = items.length; while (index < l) { - var height = this.getItemHeight(items[index].height, index); + var height = this.getItemHeight(items[index], index); top += height; index++; if (top >= offset) { @@ -445,7 +442,7 @@ DataProvider.variableHeightRowMixin = function() { var items = this.visibleItems; var startH = 0, index = 0, l = items.length; while (index < l) { - var height = this.getItemHeight(items[index].height, index); + var height = this.getItemHeight(items[index], index); startH += height; index++; if (startH >= top) { diff --git a/node_modules/ace_tree/lib/ace_tree/edit.js b/node_modules/ace_tree/lib/ace_tree/edit.js index 4dcc71e4..a76f115f 100644 --- a/node_modules/ace_tree/lib/ace_tree/edit.js +++ b/node_modules/ace_tree/lib/ace_tree/edit.js @@ -157,11 +157,12 @@ var EditableTree = function(tree) { this.ace.commands.bindKeys({ "Esc": function(ace) { ace.treeEditor.endRename(true); - ace.treeEditor.tree.focus(); }, "Enter": function(ace) { ace.treeEditor.endRename(); - ace.treeEditor.tree.focus(); + }, + "ctrl-s|cmd-s": function(ace) { + ace.treeEditor.endRename(); }, "Tab": function(ace) { ace.treeEditor.editNext(1); @@ -249,17 +250,26 @@ var EditableTree = function(tree) { this._destroyEditor = function() { if (this.lastDomNode) { this.lastDomNode.style.color = ""; - this.lastDomNode + this.lastDomNode; } this.ace.off("blur", this._onBlur); this.tree.renderer.off("afterRender", this._onAfterRender); - this.ace.blur(); - this.ace.destroy(); - if (this.ace.wrapper.parentNode) - this.ace.wrapper.parentNode.removeChild(this.ace.wrapper); + var ace = this.ace; this.ace = null; + this.$lastAce = ace; + ace.renderer.freeze(); + setTimeout(function() { + // doing this after timeout to allow rename event focus something else + var wasFocused = ace.isFocused(); + ace.destroy(); + if (ace.wrapper.parentNode) + ace.wrapper.parentNode.removeChild(ace.wrapper); + if (wasFocused) + this.tree.focus(); + this.$lastAce = null; + }.bind(this)); }; this.findNextEditPoint = function(dir, node, col, keepColumn) { @@ -384,6 +394,7 @@ var EditableTree = function(tree) { this.tree._emit("rename", { node: node, value: val, + oldValue: this.origVal, column: this.column }); this.tree.provider._signal("change"); diff --git a/node_modules/ace_tree/lib/ace_tree/layer/cells.js b/node_modules/ace_tree/lib/ace_tree/layer/cells.js index 9a25c4f8..c0eeffeb 100644 --- a/node_modules/ace_tree/lib/ace_tree/layer/cells.js +++ b/node_modules/ace_tree/lib/ace_tree/layer/cells.js @@ -51,7 +51,7 @@ var Cells = function(parentEl) { this.$renderRow(html, datarow, vsize, hsize, row); } - if (firstRow === 0 && lastRow === 0) { + if (firstRow <= 0 && lastRow <= 0) { this.renderPlaceHolder(provider, html, config); } @@ -130,7 +130,7 @@ var Cells = function(parentEl) { provider.renderRow(row, html, config); } - if (firstRow === 0 && lastRow === 0) { + if (firstRow <= 0 && lastRow <= 0) { this.renderPlaceHolder(provider, html, config); } @@ -187,7 +187,7 @@ var Cells = function(parentEl) { } else if (provider.getEmptyMessage) { html.push( "
", - provider.getEmptyMessage(), + escapeHTML(provider.getEmptyMessage()), "
" ); } diff --git a/node_modules/ace_tree/lib/ace_tree/layer/heading.js b/node_modules/ace_tree/lib/ace_tree/layer/heading.js index 834155fd..60256532 100644 --- a/node_modules/ace_tree/lib/ace_tree/layer/heading.js +++ b/node_modules/ace_tree/lib/ace_tree/layer/heading.js @@ -68,6 +68,7 @@ function ColumnHeader(parentEl, renderer) { } col.pixelWidth = 0; }, this); + columns.fixedWidth = fixedWidth; columns.$fixedWidth = fixedWidth + "px"; columns.width = null; provider.columns = columns; diff --git a/node_modules/architect-build/build_support/mini_require.js b/node_modules/architect-build/build_support/mini_require.js index 004cde85..c04e9e77 100644 --- a/node_modules/architect-build/build_support/mini_require.js +++ b/node_modules/architect-build/build_support/mini_require.js @@ -427,7 +427,7 @@ var host = location.protocol + "//" + location.hostname + (location.port ? ":" + var loadScript = function(path, id, callback) { if (!config.useCache) return loadScriptWithTag(path, id, callback); - if (!/https?:/.test(path)) + if (!/^https?:/.test(path)) path = host + path; var cb = function(e, val, deps) { if (e) return processLoadQueue({ id: id, path: path }); @@ -498,10 +498,12 @@ function checkCache() { }).then(function(keys) { baseUrl = host + config.baseUrl; var val = keys.map(function(r) { - var url = r.url; - if (url.startsWith(baseUrl)) - url = url.slice(baseUrl.length); - return r.headers.get("etag") + " " + url; + var url = r.url; + if (url.startsWith(baseUrl)) + url = url.slice(baseUrl.length); + else if (/^\w+:/.test(url)) + return ""; + return r.headers.get("etag") + " " + url; }).join("\n") + "\n"; if (val.length == 1) { ideCachePromiss = null; @@ -510,7 +512,7 @@ function checkCache() { return new Promise(function(resolve) { var checked = 0; var buffer = ""; - var toDelete = [] + var toDelete = []; post("/static/__check__", val, function(t) { var e = t.slice(checked); checked = t.length; @@ -539,6 +541,22 @@ function checkCache() { return ideCachePromiss; } +require.clearCache = function(callback) { + ideCachePromiss = window.caches.open("ide").then(function(ideCache_) { + ideCache = ideCache_; + return ideCache.keys(); + }).then(function(keys) { + var toDelete = keys.map(function(i) { + ideCache.delete(i); + }); + Promise.all(toDelete).then(function() { + callback && callback(); + }, function(e) { + callback && callback(e); + }); + }); +} + function post(path, val, progress, cb) { var xhr = new window.XMLHttpRequest(); xhr.open("POST", path, true); diff --git a/node_modules/c9/format-user-analytics.js b/node_modules/c9/format-user-analytics.js index d420e4d2..579975f1 100644 --- a/node_modules/c9/format-user-analytics.js +++ b/node_modules/c9/format-user-analytics.js @@ -23,7 +23,9 @@ define(function(require, exports, module) { name: user.fullname || user.name, pricingPlan: user.premium ? "Premium" : "Free", referredBy: user.referrer, - region: user.region + region: user.region, + usertype: user.usertype, + purpose: user.purpose, }; return traits; diff --git a/node_modules/c9/is-notfound.js b/node_modules/c9/is-notfound.js new file mode 100644 index 00000000..dcc8edba --- /dev/null +++ b/node_modules/c9/is-notfound.js @@ -0,0 +1,7 @@ +"use strict"; + + +module.exports = function isNotFound(err) { + if (err && err.code === 404) return true; + return false; +}; \ No newline at end of file diff --git a/node_modules/c9/rest_client.js b/node_modules/c9/rest_client.js index 8f42b1d0..0aec2fff 100644 --- a/node_modules/c9/rest_client.js +++ b/node_modules/c9/rest_client.js @@ -36,7 +36,7 @@ function RestClient(host, port, config) { var headers = _.extend({ "Accept": "application/json", "Content-Type": "application/json", - "Content-Length": payload.length + "Content-Length": Buffer.byteLength(payload) }, config.headers || {}); var options = { @@ -44,7 +44,11 @@ function RestClient(host, port, config) { port: port, path: path, method: method, - headers: headers + headers: headers, + timeout: config.timeout || 60 * 1000, + pool: config.pool || { + maxSockets: 100000, + }, }; if (config.username) options.auth = config.username + ":" + config.password; diff --git a/node_modules/c9/rest_client_test.js b/node_modules/c9/rest_client_test.js new file mode 100644 index 00000000..a0f07779 --- /dev/null +++ b/node_modules/c9/rest_client_test.js @@ -0,0 +1,55 @@ +#!/usr/bin/env node + +/*global describe it before after beforeEach afterEach */ +"use strict"; + +"use server"; +"use mocha"; + +require("c9/inline-mocha")(module); +require("amd-loader"); + +var assert = require("assert"); +var http = require("http"); +var findFreePort = require("netutil").findFreePort; +var RestClient = require("./rest_client"); + +describe(__filename, function() { + + var port; + var MIN_API_PORT = 18500; + var MAX_API_PORT = MIN_API_PORT + 1000; + + beforeEach(function(next) { + findFreePort(MIN_API_PORT, MAX_API_PORT, 'localhost', function(err, _port) { + port = _port; + next(err); + }); + }); + + it("should send correct content length", function(next) { + var server = http.createServer(function(req, res) { + var body = ""; + req.on("data", function(d) { + body += d; + }); + + req.on("end", function() { + JSON.parse(body); + res.end("OK"); + }); + + }); + server.listen(port, function() { + var client = new RestClient("localhost", port, {}); + + // send body with "strange" unicode character + var body = {"cloneFromScm":"https://github.com/saasbook/ruby­-calisthenics"}; + client.request("POST", "/", body, function(err, res) { + assert(!err, err); + assert.equal(res, "OK"); + server.close(next); + }); + }); + }); +}); \ No newline at end of file diff --git a/node_modules/vfs-local/localfs.js b/node_modules/vfs-local/localfs.js index dcad436c..6885fefa 100644 --- a/node_modules/vfs-local/localfs.js +++ b/node_modules/vfs-local/localfs.js @@ -200,7 +200,9 @@ module.exports = function setup(fsOptions) { // Extending the API extend: extend, unextend: unextend, - use: use + use: use, + + workspaceDir: fsOptions.projectDir }); function wrapDomain(api) { @@ -1030,7 +1032,16 @@ module.exports = function setup(fsOptions) { if (!exists || options.overwrite || isSamePath) { // Rename the file fs.rename(frompath, topath, function (err) { - if (err) return callback(err); + if (err) { + if (err.code == 'ENOENT' && options.mkdirP != false) { + options.mkdirP = false; + return mkdirP(dir, {}, function(err) { + if (err) return callback(err); + rename(path, options, callback); + }); + } + return callback(err); + } // Rename metadata if (options.metadata !== false) { @@ -1551,7 +1562,7 @@ module.exports = function setup(fsOptions) { // todo add resize event proc.emit("data", {rows: rows, cols: cols}); - if (!tmuxWarned) { + if (!tmuxWarned && !isWin) { if (/v0\.([123456789]\..*|10\.(0|1|2[0-7]))/.test(process.version)) { proc.emit("data", { message: "Wrong Node.js version: " + process.version, @@ -1997,15 +2008,20 @@ module.exports = function setup(fsOptions) { }; this.destroy = function(){ - pty.destroy.apply(pty, arguments); + return pty.destroy.apply(pty, arguments); }; this.end = function(){ - pty.end.apply(pty, arguments); + return pty.end.apply(pty, arguments); }; this.write = function() { - pty.write.apply(pty, arguments); + return pty.write.apply(pty, arguments); + }; + + this.resize = function() { + if (!exited) + return pty.resize.apply(pty, arguments); }; // this.acknowledgeWrite = function(callback) { diff --git a/node_modules/vfs-socket/worker.js b/node_modules/vfs-socket/worker.js index fbf197a3..ed7a282b 100644 --- a/node_modules/vfs-socket/worker.js +++ b/node_modules/vfs-socket/worker.js @@ -437,6 +437,8 @@ function Worker(vfs) { var keys = Object.keys(meta || {}); for (var i = 0, l = keys.length; i < l; i++) { var key = keys[i]; + if (meta[key] == undefined) + continue; switch (key) { case "stream": token.stream = storeStream(meta.stream); break; case "process": token.process = storeProcess(meta.process); break; diff --git a/package.json b/package.json index 3d1ffc0d..49301eb3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "c9", "description": "New Cloud9 Client", - "version": "3.1.1903", + "version": "3.1.2055", "author": "Ajax.org B.V. ", "private": true, "main": "bin/c9", @@ -55,24 +55,24 @@ "c9" ], "c9plugins": { - "c9.ide.language": "#f03e5ca0c8", + "c9.ide.language": "#ebc064ef16", "c9.ide.language.css": "#be07d72209", - "c9.ide.language.generic": "#92210f5a48", + "c9.ide.language.generic": "#3949510863", "c9.ide.language.html": "#22fdc74869", "c9.ide.language.html.diff": "#24f3608d26", - "c9.ide.language.javascript": "#d1a479805c", + "c9.ide.language.javascript": "#e626169643", "c9.ide.language.javascript.immediate": "#c8b1e5767a", "c9.ide.language.javascript.eslint": "#4de5457db1", "c9.ide.language.javascript.tern": "#b55d0069bb", - "c9.ide.language.javascript.infer": "#325ac6880e", + "c9.ide.language.javascript.infer": "#18acb93a3a", "c9.ide.language.jsonalyzer": "#4b329741b1", - "c9.ide.language.codeintel": "#046d39603b", - "c9.ide.collab": "#11a0d3c5ce", + "c9.ide.language.codeintel": "#2b18c5ccb1", + "c9.ide.collab": "#728ad2bde2", "c9.ide.local": "#10eb45842a", "c9.ide.find": "#e33fbaed2f", "c9.ide.find.infiles": "#c0a13737ef", - "c9.ide.find.replace": "#8cbce45290", - "c9.ide.run.debug": "#7d6da73fc7", + "c9.ide.find.replace": "#810ebf8bfb", + "c9.ide.run.debug": "#ef40edcc3f", "c9.automate": "#47e2c429c9", "c9.ide.ace.emmet": "#6dc4585e02", "c9.ide.ace.gotoline": "#a8ff07c8f4", @@ -80,24 +80,24 @@ "c9.ide.ace.repl": "#4b88a85b7b", "c9.ide.ace.split": "#0ae0151c78", "c9.ide.ace.statusbar": "#3aab0b67e0", - "c9.ide.ace.stripws": "#8885016b9e", + "c9.ide.ace.stripws": "#042a9936e3", "c9.ide.behaviors": "#db32109ebc", "c9.ide.closeconfirmation": "#cee4674141", - "c9.ide.configuration": "#528234d97d", + "c9.ide.configuration": "#a936df26bb", "c9.ide.dialog.wizard": "#7667ec79a8", "c9.ide.fontawesome": "#781602c5d8", "c9.ide.format": "#5ec97fb083", - "c9.ide.help.support": "#af5c4055b2", + "c9.ide.help.support": "#932fbb3743", "c9.ide.imgeditor": "#612e75ef4f", "c9.ide.immediate": "#19758abe08", - "c9.ide.installer": "#0fde9f0067", + "c9.ide.installer": "#1232d4e179", "c9.ide.language.python": "#330b80e3b2", "c9.ide.language.go": "#6ce1c7a7ef", "c9.ide.mount": "#4c39359b87", - "c9.ide.navigate": "#1fbb7cd53b", + "c9.ide.navigate": "#0b7ec7936c", "c9.ide.newresource": "#981a408a7b", "c9.ide.openfiles": "#2ae85a9e33", - "c9.ide.preview": "#70bd68740c", + "c9.ide.preview": "#5f5fff0185", "c9.ide.preview.browser": "#897177be7f", "c9.ide.preview.markdown": "#c3174d86e0", "c9.ide.pubsub": "#a85fb27eca", @@ -105,18 +105,18 @@ "c9.ide.recentfiles": "#7c099abf40", "c9.ide.remote": "#301d2ab519", "c9.ide.processlist": "#2b12cd1bdd", - "c9.ide.run": "#485f06bd22", + "c9.ide.run": "#6bd4996a4e", "c9.ide.run.build": "#0598fff697", "c9.ide.run.debug.xdebug": "#9956689819", "c9.ide.save": "#4cda35bfdb", - "c9.ide.scm": "#dc87315012", - "c9.ide.terminal.monitor": "#1a4092ede2", - "c9.ide.test": "#a282ec1619", + "c9.ide.scm": "#637a68cd04", + "c9.ide.terminal.monitor": "#affa33572f", + "c9.ide.test": "#102942ae4e", "c9.ide.test.mocha": "#fc053b23d2", "c9.ide.theme.flat": "#81dadeee55", "c9.ide.threewaymerge": "#229382aa0b", "c9.ide.undo": "#b028bcb4d5", - "c9.ide.upload": "#0bd010d3dc", + "c9.ide.upload": "#a3da59803d", "c9.ide.welcome": "#5b86c44e92", "c9.ide.guide": "#8ab966f344" } diff --git a/plugins/c9.cli.publish/publish.js b/plugins/c9.cli.publish/publish.js index d3efede7..3c8551c9 100644 --- a/plugins/c9.cli.publish/publish.js +++ b/plugins/c9.cli.publish/publish.js @@ -653,6 +653,10 @@ define(function(require, exports, module) { p = "/" + normalizePath(Path.relative(cwd, p)); excludeMap[p] = 1; }); + // keep installer in both packed and unpacked form + if (json.installer) + excludeMap["/" + normalizePath(Path.relative(cwd, json.installer))] = 0; + copy(cwd, cwd + "/.c9/.build", { exclude: function(name, parent) { if (excludeRe.test(name)) diff --git a/plugins/c9.core/ext.js b/plugins/c9.core/ext.js index d8a1e866..5768c28a 100644 --- a/plugins/c9.core/ext.js +++ b/plugins/c9.core/ext.js @@ -577,7 +577,7 @@ define(function(require, exports, module) { function setAPIKey(apikey){ // Validate Key - if (!apikey || !apikey.match(/[\w+]{27}=/)) + if (!apikey || !apikey.match(/^.{27}=$/)) throw new Error("Invalid API key"); return { diff --git a/plugins/c9.error/error_handler.js b/plugins/c9.error/error_handler.js index 1c46ee68..4e3fe2af 100644 --- a/plugins/c9.error/error_handler.js +++ b/plugins/c9.error/error_handler.js @@ -115,6 +115,7 @@ function plugin(options, imports, register) { var accept = req.headers.accept || ''; if (statusCode == 500) { + console.error(err && err.stack); emitter.emit("internalServerError", { err: err, req: req diff --git a/plugins/c9.error/raygun.connect.js b/plugins/c9.error/raygun.connect.js index 512be225..922eae90 100644 --- a/plugins/c9.error/raygun.connect.js +++ b/plugins/c9.error/raygun.connect.js @@ -90,6 +90,7 @@ function plugin(options, imports, register) { var customData = _.clone(raygun.customData || {}); if (req.user) { customData.user = { + augment: err.augment, id: req.user.id, name: req.user.name, email: req.user.email diff --git a/plugins/c9.error/raygun.js b/plugins/c9.error/raygun.js index 6a51ce0b..ca753d4a 100644 --- a/plugins/c9.error/raygun.js +++ b/plugins/c9.error/raygun.js @@ -25,8 +25,8 @@ function plugin(options, imports, register) { }; var clients = { - error: new raygun.Client().init({ apiKey: options.keys.error }), - warning: new raygun.Client().init({ apiKey: options.keys.warning }) + error: new raygun.Client().init({ apiKey: options.keys.error, useHumanStringForObject: false }), + warning: new raygun.Client().init({ apiKey: options.keys.warning, useHumanStringForObject: false }) }; var customClients = options.customClients || {}; diff --git a/plugins/c9.fs/fs.cache.xml.js b/plugins/c9.fs/fs.cache.xml.js index 34b693b2..7922f9a0 100644 --- a/plugins/c9.fs/fs.cache.xml.js +++ b/plugins/c9.fs/fs.cache.xml.js @@ -82,6 +82,9 @@ define(function(require, exports, module) { node.status = "pending"; return; } + var parentPath = e.path; + if (!parentPath.endsWith("/")) + parentPath += "/"; // update cache if (!node) { if (!showHidden && isFileHidden(e.path)) @@ -92,97 +95,116 @@ define(function(require, exports, module) { orphans[e.path] = node; orphan = true; } + node.$lastReadT = Date.now(); // Indicate this directory has been fully read model.setAttribute(node, "status", "loaded"); - var wasOpen = startUpdate(node); - node.children = null; - var existing = node.map || {}; - node.map = {}; - - // Fill Parent - var ondisk = {}, toAppend = []; + var ondisk = Object.create(null); + var toRemove = []; + var toCreate = []; + var orphanAppand = []; + var existing = node.map || (node.map = Object.create(null)); e.result[1].forEach(function(stat) { if (!stat.name || !showHidden && isFileHidden(stat.name)) return; - var name = stat.name; - var path = (e.path + "/" + name).replace("//", "/"); + var path = parentPath + name; ondisk[name] = 1; - // if (existing[name]) return; + if (orphans[path]) { - toAppend.push(path); + if (existing[name]) + delete orphans[path]; + orphanAppand.push(path); } - createNode(path, stat, existing[name], true); + + if (existing[name]) + updateNodeStat(path, stat, existing[name]); + else + toCreate.push(stat); }); - for (var name in existing) { - if (!ondisk[name]) { - // onreaddir can be called before copied nodes are written to disk - // in this case we don't want to lose "predicted" state - if (existing[name] && existing[name].status === "predicted") - node.map[name] = existing[name]; - else { - delete existing[name]; - - emit("remove", { - path: e.path + "/" + name, - node: existing[name], - parent: node - }); - } - } - } + Object.keys(existing).forEach(function(name) { + // onreaddir can be called before copied nodes are written to disk + // in this case we don't want to lose "predicted" state + if (existing[name] && existing[name].status === "predicted") + ondisk[name] = 1; + if (!ondisk[name]) + toRemove.push(name); + }); + + if (!toCreate.length && !toRemove.length && !orphanAppand.length) + return; + + var wasOpen = startUpdate(node); + node.children = null; + + // Fill Parent + toCreate.forEach(function(stat) { + createNode(parentPath + stat.name, stat, null, true); + }); + + toRemove.forEach(function(name) { + var currentNode = existing[name]; + delete existing[name]; + emit("remove", { + path: parentPath + name, + node: currentNode, + parent: node + }); + }); emit("readdir", { path : e.path, parent : node, orphan: orphan }); endUpdate(node, wasOpen); - toAppend.forEach(function(path) { + orphanAppand.forEach(function(path) { emit("orphan-append", {path: path}); }); } fs.on("afterReaddir", onreaddir, plugin); function onstat(e) { - var stat; + if (e.error) return; - if (!e.error) { - // update cache - var there = true; - var node = findNode(e.path); - var parent = findNode(dirname(e.path)); - - if (!showHidden && isFileHidden(e.path)) - return; + // update cache + var stat = e.result[1]; + + var there = true; + var node = findNode(e.path); + var parent = findNode(dirname(e.path)); + + if (!showHidden && isFileHidden(e.path)) + return; - if (!node) { - if (!parent) - return; - there = false; + if (!node) { + if (!parent) + return; + there = false; + } + + if (there != !!stat) { + if (there) { + if (!node.link) + deleteNode(node); } - - if (there != !!e.result[1]) { - if (there) { - if (!node.link) - deleteNode(node); - } - else { - stat = e.result[1]; - if (typeof stat != "object") - stat = null; - createNode(e.path, stat); - } - } - else if (there) { - stat = e.result[1]; + else { if (typeof stat != "object") stat = null; - createNode(e.path, stat, node); + createNode(e.path, stat); } } + else if (there) { + if (typeof stat != "object") + stat = null; + if (!stat && node) + return; + if (stat && node) + updateNodeStat(e.path, stat, node); + else + createNode(e.path, stat, node); + } } fs.on("afterStat", onstat, plugin); fs.on("afterReadFile", function(e) { @@ -295,14 +317,15 @@ define(function(require, exports, module) { // Validation var toNode = findNode(newPath); - deleteNode(node, true); - if (toNode) - deleteNode(toNode, true); - - createNode(newPath, null, node); // Move node - recurPathUpdate(node, oldPath, newPath); + if (!toNode) { + deleteNode(node, true); + createNode(newPath, null, node); // Move node + recurPathUpdate(node, oldPath, newPath); + } e.undo = function(){ + if (toNode) + return; if (!parent) { var tmpParent = node; while (node.parent && tmpParent.parent.status == "pending") @@ -318,6 +341,12 @@ define(function(require, exports, module) { recurPathUpdate(node, newPath, oldPath); }; e.confirm = function() { + if (toNode) { + deleteNode(toNode, true); + createNode(newPath, null, node); // Move node + recurPathUpdate(node, oldPath, newPath); + } + if (node.status === "predicted") node.status = "loaded"; }; @@ -511,14 +540,8 @@ define(function(require, exports, module) { updateNode = orphans[path]; delete orphans[path]; } - var original_stat; - if (stat && stat.link) { - original_stat = stat; - stat = stat.linkStat; - } var parts = path.split("/"); - var name = parts[parts.length - 1]; var node = model.root.map[parts[0] == "~" ? "~" : ""]; if (!node) { node = orphans[parts[0]]; @@ -537,7 +560,7 @@ define(function(require, exports, module) { var map = node.map; if (!map) { - map = node.map = {}; + map = node.map = Object.create(null); } parent = node; node = map[p]; @@ -545,6 +568,11 @@ define(function(require, exports, module) { modified.push(parent); if (i !== parts.length - 1) { node = {label: p, path: subPath, status: "pending", isFolder: true}; + // TODO filter hidden files in getChildren instead. + if (!showHidden && isFileHidden(p)) { + orphans[node.path] = path; + return; + } } else if (updateNode) { deleteNode(updateNode, true); node = updateNode; @@ -564,8 +592,32 @@ define(function(require, exports, module) { node = {label: parts[parts.length - 1], path: path}; orphans[path] = node; } + + updateNodeStat(path, stat, node); + + node.children = null; + + if (!updating) { + if (!modified.length) + modified.push(parent); + var wasOpen = startUpdate(modified[0]); + modified.forEach(function(n) { + if (n != model.root) + n.children = null; + }); + endUpdate(modified[0], wasOpen); + } + model._signal("createNode", node); + return node; + } + + function updateNodeStat(path, stat, node) { node.path = path; - + var original_stat; + if (stat && stat.link) { + original_stat = stat; + stat = stat.linkStat; + } if (stat) { var isFolder = stat && /(directory|folder)$/.test(stat.mime); if (isFolder) { @@ -589,25 +641,13 @@ define(function(require, exports, module) { delete node.isFolder; } - if (node.isFolder && !node.map) - node.map = {}; - else if (!node.isFolder && node.map) + if (node.isFolder && !node.map) { + node.map = Object.create(null); + node.children = null; + } else if (!node.isFolder && node.map) { delete node.map; - - node.children = null; - - if (!updating) { - if (!modified.length) - modified.push(parent); - var wasOpen = startUpdate(modified[0]); - modified.forEach(function(n) { - if (n != model.root) - n.children = null; - }); - endUpdate(modified[0], wasOpen); + node.children = null; } - model._signal("createNode", node); - return node; } function deleteNode(node, silent) { @@ -670,7 +710,7 @@ define(function(require, exports, module) { map: {} }; var root = {}; - root.map = {}; + root.map = Object.create(null); root.map[""] = model.projectDir; model.setRoot(root); // fs.readdir("/", function(){}); @@ -894,7 +934,12 @@ define(function(require, exports, module) { * @param {Function} progress * @param {Function} done */ - loadNodes: loadNodes + loadNodes: loadNodes, + + /** + * @ignore + */ + isFileHidden: isFileHidden }); register(null, { diff --git a/plugins/c9.fs/proc_test.js b/plugins/c9.fs/proc_test.js index db68c57e..96be5406 100644 --- a/plugins/c9.fs/proc_test.js +++ b/plugins/c9.fs/proc_test.js @@ -37,7 +37,7 @@ require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) describe('proc', function() { describe('spawn()', function() { - this.timeout(4000); + this.timeout(10000); it("should spawn a child process", function(done) { var args = ["-e", "process.stdin.pipe(process.stdout);try{process.stdin.resume()}catch(e) {};"]; @@ -66,8 +66,8 @@ require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) }); }); - //should test the kill() method - which is broken now - //Another test - see that cwd defaults to the root vfs dir when resolve is set to true + // should test the kill() method - which is broken now + // Another test - see that cwd defaults to the root vfs dir when resolve is set to true }); describe('execFile()', function() { this.timeout(10000); @@ -101,8 +101,19 @@ require(["lib/architect/architect", "lib/chai/chai"], function (architect, chai) }); }); - //should test the kill() method - which is broken now - //Another test - see that cwd defaults to the root vfs dir when resolve is set to true + it('should pass stdout and stderr', function(done) { + proc.execFile("node", { + args: ["-v"] + }, function(e, stdout, stderr) { + expect(stdout[0]).to.equal("v"); + expect(stderr).to.equal(""); + expect(e).to.not.ok; + done(); + }); + }); + + // should test the kill() method - which is broken now + // Another test - see that cwd defaults to the root vfs dir when resolve is set to true }); describe('pty()', function() { this.timeout(30000); diff --git a/plugins/c9.ide.ace/ace.js b/plugins/c9.ide.ace/ace.js index ff712657..b2da9718 100644 --- a/plugins/c9.ide.ace/ace.js +++ b/plugins/c9.ide.ace/ace.js @@ -1444,7 +1444,7 @@ define(function(require, exports, module) { else if (/^{/.test(firstLine)) { syntax = "json"; } - else if (/\.(bashrc|inputrc)$/.test(path)) { + else if (/\.(bash|inputrc|profile|zsh)/.test(path)) { syntax = "sh"; } else if (/\.(git(attributes|config|ignore)|npmrc)$/.test(path)) { diff --git a/plugins/c9.ide.dialog.common/alert_internal.js b/plugins/c9.ide.dialog.common/alert_internal.js index 572c48ad..98744ee1 100644 --- a/plugins/c9.ide.dialog.common/alert_internal.js +++ b/plugins/c9.ide.dialog.common/alert_internal.js @@ -26,6 +26,8 @@ define(function(require, module, exports) { /***** Methods *****/ function show(title, header, msg, onhide, options) { + options = options || {}; + metrics.increment("dialog.error"); return plugin.queue(function(){ @@ -37,13 +39,15 @@ define(function(require, module, exports) { else { plugin.title = title; } - plugin.heading = options && options.isHTML ? header : util.escapeXml(header); - plugin.body = options && options.isHTML ? msg : (util.escapeXml(msg) || "") + plugin.heading = options.isHTML ? header : util.escapeXml(header); + plugin.body = options.isHTML ? msg : (util.escapeXml(msg) || "") .replace(/\n/g, "
") .replace(/(https?:\/\/[^\s]*\b)/g, "$1"); + plugin.getElement("ok").setCaption(options.yes || "OK"); + plugin.update([ - { id: "dontshow", visible: options && options.showDontShow } + { id: "dontshow", visible: options.showDontShow } ]); plugin.once("hide", function(){ diff --git a/plugins/c9.ide.dialog.common/question.js b/plugins/c9.ide.dialog.common/question.js index 7e3be92e..23d08df1 100644 --- a/plugins/c9.ide.dialog.common/question.js +++ b/plugins/c9.ide.dialog.common/question.js @@ -54,7 +54,7 @@ define(function(require, module, exports) { var gotYesNo = false; plugin.once("hide", function(){ - !gotYesNo && cancel && onNo(false, true, metadata); + !gotYesNo && cancel && onNo && onNo(false, true, metadata); }); plugin.update([ @@ -63,22 +63,22 @@ define(function(require, module, exports) { { id: "yestoall", visible: all, onclick: function(){ gotYesNo = true; plugin.hide(); - onYes(true, metadata); + onYes && onYes(true, metadata); }}, { id: "notoall", visible: all, onclick: function(){ gotYesNo = true; plugin.hide(); - onNo(true, false, metadata); + onNo && onNo(true, false, metadata); }}, { id: "yes", onclick: function(){ gotYesNo = true; plugin.hide(); - onYes(false, metadata); + onYes && onYes(false, metadata); }}, { id: "no", onclick: function(){ gotYesNo = true; plugin.hide(); - onNo(false, false, metadata); + onNo && onNo(false, false, metadata); }} ]); }, options.queue === false); diff --git a/plugins/c9.ide.dialog.common/upsell.js b/plugins/c9.ide.dialog.common/upsell.js index 0200a9ba..1b5af324 100644 --- a/plugins/c9.ide.dialog.common/upsell.js +++ b/plugins/c9.ide.dialog.common/upsell.js @@ -27,14 +27,14 @@ define(function(require, module, exports) { options = {isHTML: true}; return plugin.queue(function(){ - var all = options.all; var cancel = options.cancel; - var showDontAsk = options.showDontAsk; var metadata = options.metadata; title = title || "This is a Premium feature"; - header = header || "Get Premium Support Now!"; - msg = msg || 'Help is just a few clicks away. Check out our amazing premium plans.' + header = header || "Upgrade to Premium Now!"; + onYes = onYes || function() {}; + onNo = onNo || function() {}; + msg = msg || 'A better, faster, more versatile Cloud9 is just a click away. Check out our amazing premium plans.'; plugin.title = title; plugin.heading = options && options.isHTML ? header : util.escapeXml(header); @@ -73,7 +73,12 @@ define(function(require, module, exports) { plugin.freezePublicAPI({ /** - * + * @param {Function} onYes Callback for when user clicks the 'yes' button + * @param {Function} onNo Callback for when the user clicks the 'no' button + * @param {String} [title] Title for the dialog + * @param {String} [header] Header for the dialog body + * @param {String} [msg] Message to show the user. + * @param {Object} [options] Miscellaneous options */ show: show }); diff --git a/plugins/c9.ide.dialog.file/file.xml b/plugins/c9.ide.dialog.file/file.xml index 590bd4f5..dd4b341e 100644 --- a/plugins/c9.ide.dialog.file/file.xml +++ b/plugins/c9.ide.dialog.file/file.xml @@ -27,7 +27,7 @@ @@ -46,7 +46,7 @@ diff --git a/plugins/c9.ide.dialog.login/login.xml b/plugins/c9.ide.dialog.login/login.xml index 82b954e8..aff22bd0 100644 --- a/plugins/c9.ide.dialog.login/login.xml +++ b/plugins/c9.ide.dialog.login/login.xml @@ -28,7 +28,7 @@ @@ -41,7 +41,7 @@ diff --git a/plugins/c9.ide.editors/tab.js b/plugins/c9.ide.editors/tab.js index 5457b4c6..d1692979 100644 --- a/plugins/c9.ide.editors/tab.js +++ b/plugins/c9.ide.editors/tab.js @@ -291,9 +291,9 @@ define(function(require, module, exports) { load(); }); - plugin.on("beforeUnload", function(){ + plugin.on("beforeUnload", function(e){ if (!plugin.meta.$closing) { - if (close()) + if (close(e && e.animate === false)) return false; } }); diff --git a/plugins/c9.ide.editors/tabmanager.js b/plugins/c9.ide.editors/tabmanager.js index da270a71..caf097d3 100644 --- a/plugins/c9.ide.editors/tabmanager.js +++ b/plugins/c9.ide.editors/tabmanager.js @@ -1283,7 +1283,7 @@ define(function(require, module, exports) { // Or keep tab until the new one is loaded else { - previewTab.unload(); + previewTab.unload({ animate: false }); } } diff --git a/plugins/c9.ide.info/info.js b/plugins/c9.ide.info/info.js index c3ad0b6b..5df32577 100644 --- a/plugins/c9.ide.info/info.js +++ b/plugins/c9.ide.info/info.js @@ -74,12 +74,15 @@ define(function(require, exports, module) { plugin.freezePublicAPI({ /** * Returns the logged in user. + * + * @param [callback] * @return {Object} The currently user */ getUser: getUser, /** * Return the active workspace. + * * @return {Object} The currently active workspace */ getWorkspace: getWorkspace, diff --git a/plugins/c9.ide.keys/commands.js b/plugins/c9.ide.keys/commands.js index 0cab430e..5cae4001 100644 --- a/plugins/c9.ide.keys/commands.js +++ b/plugins/c9.ide.keys/commands.js @@ -96,9 +96,18 @@ define(function(require, exports, module) { } function getHotkey(command) { + if (!commands[command] || !commands[command].bindKey) + return ""; return commands[command].bindKey[platform]; } + function getPrettyHotkey(command) { + var key = getHotkey(command); + if (platform == "mac") + key = apf.hotkeys.toMacNotation(key); + return key; + } + var markDirty = lang.delayedCall(function(){ emit("update"); }, 500); @@ -557,6 +566,14 @@ define(function(require, exports, module) { */ getHotkey: getHotkey, + /** + * returns result of getHotkey formatted for displaying in menus + * + * @param {String} name the name of the command. + * @return {String} + */ + getPrettyHotkey: getPrettyHotkey, + /** * Executes the action tied to a command. This method will call * the `isAvailable` method for a command and will not execute if diff --git a/plugins/c9.ide.keys/panel.js b/plugins/c9.ide.keys/panel.js index a2d1cf0f..8f102d52 100644 --- a/plugins/c9.ide.keys/panel.js +++ b/plugins/c9.ide.keys/panel.js @@ -79,6 +79,9 @@ define(function(require, exports, module) { // @TODO this is probably not sufficient layout.on("resize", function(){ tree.resize() }, plugin); + var key = commands.getPrettyHotkey("commands"); + txtFilter.setAttribute("initial-message", key); + tree.textInput = txtFilter.ace.textInput; txtFilter.ace.commands.addCommands([ diff --git a/plugins/c9.ide.layout.classic/layout.js b/plugins/c9.ide.layout.classic/layout.js index 50d4c6de..250fbf9f 100644 --- a/plugins/c9.ide.layout.classic/layout.js +++ b/plugins/c9.ide.layout.classic/layout.js @@ -333,15 +333,6 @@ define(function(require, exports, module) { amlNode.$ext.className += " c9btn"; menus.addItemByPath("File/~", new apf.divider(), 1000000, plugin); - - if (!c9.local) { - menus.addItemByPath("Cloud9/~", new apf.divider(), 2000000, plugin); - menus.addItemByPath("Cloud9/Quit Cloud9", new apf.item({ - onclick: function(){ - location.href = "http://c9.io"; - } - }), 2000100, plugin); - } menus.addItemByPath("View/~", new apf.divider(), 9999, plugin); diff --git a/plugins/c9.ide.layout.classic/less/btn-switcher.less b/plugins/c9.ide.layout.classic/less/btn-switcher.less new file mode 100644 index 00000000..74e4a5cc --- /dev/null +++ b/plugins/c9.ide.layout.classic/less/btn-switcher.less @@ -0,0 +1,52 @@ +.btn-switcher { + .user-select(none); + -webkit-display: flex; + display: flex; + -webkit-align-items: center; + align-items: center; + cursor: default; + font-size: @preview-chooser-font-size; + font-weight: @preview-chooser-font-weight; + color: ; + font-family: @preview-chooser-font-family; + -webkit-font-smoothing: auto; + -moz-osx-font-smoothing: auto; + background: @preview-chooser-background; + border-radius: 0 2px 2px 0; + box-shadow: @preview-chooser-box-shadow; + cursor: pointer; + border: 1px solid @textbox-border-color; + border-left : 0; + border-radius: 0 3px 3px 0; + position : relative; + + padding: 4px 17px 4px 8px; + box-sizing: border-box; +} +.btn-switcherOver{ + background: @preview-chooser-over-background; +} +.btn-switcherDown{ + background: @preview-chooser-active-background; +} +.btn-switcher svg{ + vertical-align: middle; + margin: -2px 4px 0 0; +} + +.btn-switcher span{ + display : block; + position : absolute; + right : 6px; + top : 11px; + width : 5px; + height : 5px; + background: url("@{image-path}/@{preview-chooser-arrow}") no-repeat; + opacity: 0.6; +} + +.btn-switcherIcon { + background-repeat: no-repeat; + padding-left: 25px; + background-position: 5px 50% +} \ No newline at end of file diff --git a/plugins/c9.ide.layout.classic/less/codebox.less b/plugins/c9.ide.layout.classic/less/codebox.less index 129aeee6..357253c1 100644 --- a/plugins/c9.ide.layout.classic/less/codebox.less +++ b/plugins/c9.ide.layout.classic/less/codebox.less @@ -18,6 +18,9 @@ color : @textbox-disabled-color; background: @textbox-disabled-background; } +.has_apf .searchTxt.tb_console.ace_searchboxDisabled .sbtb_middle .input { + color : @textbox-disabled-color; +} .ace_one-line .ace_scroller.ace_scroll-left { box-shadow: none; } @@ -40,10 +43,6 @@ outline : none; text-overflow : ellipsis; } -.tb_consoleFocus .sbtb_middle, -.tb_consoleOver .sbtb_middle, -.tb_console .sbtb_middle:hover { -} .topborder .sbtb_middle{ border-width : 1px 0 0 0; @@ -63,15 +62,13 @@ padding : 0; } -.tb_console.tb_textboxInitial .sbtb_middle { - padding-right : 0 !important; + +.ace_editor .tb_textboxInitialMsg { + text-indent: 5px; + text-shadow: none; } -.tb_console.tb_textboxInitial .input { - font-size : 10px; -} - -.tb_console.tb_textboxInitial .ace_cursor{ +.ace_initialMsg:not(.ace_focus) .ace_cursor { display : none; } diff --git a/plugins/c9.ide.layout.classic/less/main.less b/plugins/c9.ide.layout.classic/less/main.less index 7c158e95..a2a4dd5d 100644 --- a/plugins/c9.ide.layout.classic/less/main.less +++ b/plugins/c9.ide.layout.classic/less/main.less @@ -47,6 +47,7 @@ BODY.noInput *{ @import "less/button.less"; @import "less/btn_console.less"; @import "less/btn_console_open.less"; +@import "less/btn-switcher.less"; @import "less/c9-divider-double.less"; @import "less/c9-divider-hor.less"; @import "less/c9-divider.less"; diff --git a/plugins/c9.ide.layout.classic/less/searchbox.less b/plugins/c9.ide.layout.classic/less/searchbox.less index 23147036..00f7ef2c 100644 --- a/plugins/c9.ide.layout.classic/less/searchbox.less +++ b/plugins/c9.ide.layout.classic/less/searchbox.less @@ -36,7 +36,7 @@ height: auto; position: relative; } -.searchTxt.tb_textboxInitial .sbtb_middle .input { +.searchTxt.tb_textboxInitial .sbtb_middle .input, .tb_textboxInitialMsg { color: @textbox-initial-color !important; text-shadow: @textbox-initial-text-shadow; font-family: @find-textbox-font-family; diff --git a/plugins/c9.ide.layout.classic/skins.xml b/plugins/c9.ide.layout.classic/skins.xml index 4fe050bc..6e78f388 100644 --- a/plugins/c9.ide.layout.classic/skins.xml +++ b/plugins/c9.ide.layout.classic/skins.xml @@ -859,4 +859,16 @@ + + + +
+ - +
+
+
+
\ No newline at end of file diff --git a/plugins/c9.ide.layout.classic/themes/default-flat-dark.less b/plugins/c9.ide.layout.classic/themes/default-flat-dark.less index 9ba1ada6..98a1e73b 100644 --- a/plugins/c9.ide.layout.classic/themes/default-flat-dark.less +++ b/plugins/c9.ide.layout.classic/themes/default-flat-dark.less @@ -1711,7 +1711,7 @@ @form-bar-border-bottom: 1px solid black; @form-bar-box-shadow: 0 1px @border-highlight; -@panel-settings-changes-top: 46px; +@panel-settings-changes-top: 53px; /*******/ diff --git a/plugins/c9.ide.layout.classic/themes/default-flat-light.less b/plugins/c9.ide.layout.classic/themes/default-flat-light.less index bc630707..74aa8f16 100644 --- a/plugins/c9.ide.layout.classic/themes/default-flat-light.less +++ b/plugins/c9.ide.layout.classic/themes/default-flat-light.less @@ -1066,8 +1066,8 @@ @textbox-border-color: darken(#dedede, @darken-chrome); @textbox-initial-color: darken(#A5A5A5, @darken-chrome); @textbox-initial-text-shadow: 0 1px 0 darken(#FFFFFF, @darken-chrome); -@textbox-disabled-color: gray; -@textbox-disabled-background: darken(rgb(216, 216, 216), @darken-chrome); +@textbox-disabled-color: #C5C5C5; +@textbox-disabled-background: #F1F1F1; // Textbox - Simple @tbsimple-border: 1px solid #DEDEDE; @@ -1706,9 +1706,9 @@ @diff-toolbar-buttons-background: #F9F9F9; @diff-toolbar-buttons-border: 1px solid @border-highlight-dark; -@form-bar-padding: 10px 50px 10px 10px; +@form-bar-padding: 10px 10px 0 10px; @form-bar-background: @menu-button-active-background; -@form-bar-border-bottom: 1px solid black; +@form-bar-border-bottom: 1px solid #DEDEDE; @form-bar-box-shadow: 0 1px @border-highlight; -@panel-settings-changes-top: 46px; \ No newline at end of file +@panel-settings-changes-top: 53px; \ No newline at end of file diff --git a/plugins/c9.ide.layout.classic/themes/flat-light.less b/plugins/c9.ide.layout.classic/themes/flat-light.less index 351b97ee..82317527 100644 --- a/plugins/c9.ide.layout.classic/themes/flat-light.less +++ b/plugins/c9.ide.layout.classic/themes/flat-light.less @@ -470,6 +470,9 @@ body .runner-form-header{ .cbblack.cbcontainerDisabled.cbcontainerChecked .checkbox{ background-position: 0 -142px; } + .cbblack.cbcontainerDisabled.cbcontainerDisabled .checkbox{ + background-position: 0 0; + } .session_btn .tab_middle:before { background-position : 0 -126px; -webkit-mask-position : 0 -95px; diff --git a/plugins/c9.ide.login/login.js b/plugins/c9.ide.login/login.js index eb9ad2df..fc8e68f0 100644 --- a/plugins/c9.ide.login/login.js +++ b/plugins/c9.ide.login/login.js @@ -40,6 +40,15 @@ define(function(require, exports, module) { }); auth.on("relogin", onReLogin); + + if (!c9.local) { + menus.addItemByPath("Cloud9/~", new apf.divider(), 2000000, plugin); + menus.addItemByPath("Cloud9/Quit Cloud9", new apf.item({ + onclick: function(){ + signout(); + } + }), 2000100, plugin); + } } /***** Methods *****/ diff --git a/plugins/c9.ide.panels/panel.js b/plugins/c9.ide.panels/panel.js index ee7be651..f1245f4b 100644 --- a/plugins/c9.ide.panels/panel.js +++ b/plugins/c9.ide.panels/panel.js @@ -147,6 +147,14 @@ define(function(require, module, exports) { mnuItem.setAttribute("hotkey", "{commands.commandManager." + options.name + "}"); + + + if (button && button.setAttribute) { + var key = commands.getPrettyHotkey(options.name); + button.setAttribute("tooltip", options.name + + (key ? " (" + key + ")" : "")); + } + return command; } @@ -382,6 +390,11 @@ define(function(require, module, exports) { */ get button(){ return button; }, + /** + * + */ + get active(){ return panels.isActive(plugin.name); }, + _events: [ /** * Fires when the panel is drawn. diff --git a/plugins/c9.ide.terminal/aceterm/input.js b/plugins/c9.ide.terminal/aceterm/input.js index 18289eb8..424afcbd 100644 --- a/plugins/c9.ide.terminal/aceterm/input.js +++ b/plugins/c9.ide.terminal/aceterm/input.js @@ -221,7 +221,7 @@ define(function(require, exports, module) { }]); ace.onPaste = function(text) { - this.send(text.replace(/\r\n/g, "\n")); + this.send(text.replace(/\r\n?|\n/g, this.session.term.convertEol ? "\n" : "\r")); }; ace.setKeyboardHandler(this); diff --git a/plugins/c9.ide.tree/tree.js b/plugins/c9.ide.tree/tree.js index f130e2b4..34187998 100644 --- a/plugins/c9.ide.tree/tree.js +++ b/plugins/c9.ide.tree/tree.js @@ -3,7 +3,7 @@ define(function(require, exports, module) { "Panel", "c9", "util", "fs", "settings", "ui", "menus", "panels", "commands", "tabManager", "fs.cache", "watcher", "preferences", "clipboard", "dialog.alert", "dialog.fileremove", - "dialog.fileoverwrite", "dialog.error", "layout" + "dialog.fileoverwrite", "dialog.error", "layout", "dialog.question" ]; main.provides = ["tree"]; return main; @@ -24,6 +24,7 @@ define(function(require, exports, module) { var watcher = imports.watcher; var prefs = imports.preferences; var alert = imports["dialog.alert"].show; + var question = imports["dialog.question"].show; var fsCache = imports["fs.cache"]; var confirmRemove = imports["dialog.fileremove"].show; var confirmRename = imports["dialog.fileoverwrite"].show; @@ -34,7 +35,7 @@ define(function(require, exports, module) { var TreeEditor = require("ace_tree/edit"); var markup = require("text!./tree.xml"); - var basename = require("path").basename; + var join = require("path").join; var dirname = require("path").dirname; var staticPrefix = options.staticPrefix; @@ -52,7 +53,7 @@ define(function(require, exports, module) { }); var emit = plugin.getEmitter(); - var container, winFilesViewer; //UI elements + var container, winFilesViewer; // UI elements var showHideScrollPos, scrollTimer; var tree; @@ -404,15 +405,10 @@ define(function(require, exports, module) { emit("expand", { path: id }); - if (node.justLoaded) { - delete node.justLoaded; - return; - } - // Only save if we are not loading the tree if (!refreshing || loadedSettings != -1) { if (!node.isRoot) { - var refresh = !refreshing && node.status == "loaded"; + var refresh = !refreshing && node.status == "loaded" && Date.now() - node.$lastReadT > 500; watcher.watch(id, refresh); // watch children @@ -424,8 +420,10 @@ define(function(require, exports, module) { }); } - changed = true; - settings.save(); + if (!updateSingleDirectoryChain(true, node)) { + changed = true; + settings.save(); + } } }, plugin); @@ -451,13 +449,29 @@ define(function(require, exports, module) { }); } - changed = true; - settings.save(); + if (!updateSingleDirectoryChain(false, node)) { + changed = true; + settings.save(); + } }, plugin); - - function abortNoStorage() { - if (!c9.has(c9.STORAGE)) - return false; + + function updateSingleDirectoryChain(isExpand, node) { + if (!node.children || node.children.length !== 1) + return; + var child = node.children[0]; + if (!child || !child.isFolder || child.$depth > 0xff) + return; + if (fsCache.isFileHidden(child.path)) + return; + if (isExpand && !child.isOpen) { + expandNode(child); + return true; + } + else if (!isExpand && child.isOpen) { + updateSingleDirectoryChain(false, child); + delete expandedList[child.path]; + return true; + } } // Rename @@ -479,34 +493,58 @@ define(function(require, exports, module) { } var node = e.node; - var name = e.value; + var name = e.value.trim(); // check for a path with the same name, which is not allowed to rename to: var path = node.path; - var newpath = path.replace(/[^\/]+$/, name); + var newpath = join(path, "..", name); // No point in renaming when the name is the same - if (basename(path) == name) + if (path == newpath) return; - - // Returning false from this function will cancel the rename. We do this - // when the name to which the file is to be renamed contains invalid - // characters - if (/[\\\/\n\r]/.test(name)) { - // todo is this still needed? + + var m = /([\0\\\n\r])/.exec(name) || c9.platform == "win32" && /([\\:*?"<>|])/.exec(name); + if (m) { showError( - "Could not rename to '" + ui.htmlentities(name) - + "'. Names can only contain alfanumeric characters, space, . (dot)" - + ", - and _ (underscore). Use the terminal to rename to other names." + "Invalid character '" + m[0] + "' in '" + name + "'" ); return false; } - fs.rename(path, newpath, {}, function(err, success) { }); + // renaming to hidden file can be confusing if one doesn't know about hidden files + if (fsCache.isFileHidden(newpath) && !settings.getBool("user/projecttree/@showhidden")) { + settings.set("user/projecttree/@showhidden", true); + changed = true; + fsCache.showHidden = true; + refresh(true, function(){}); + } - emit("rename", { path: newpath, oldpath: path }); + if (dirname(newpath) != dirname(path)) { + tree.edit.$lastAce && tree.edit.$lastAce.blur(); // TODO this shouldn't be needed when apf focus works + question( + "Confirm move to a new folder", + "move '" + e.oldValue + "' to \n" + + "'" + dirname(newpath) + "'?", + "", + doRename + ); + } else { + doRename(); + } - return false; + function doRename() { + fs.rename(path, newpath, {}, function(err, success) { + if (err) { + var message = err.message; + if (err.code == "EEXIST") + message = "File " + path + " already exists."; + return showError(message); + } + if (dirname(newpath) != dirname(path)) + expandAndSelect(newpath); + }); + emit("rename", { path: newpath, oldpath: path }); + } }, plugin); // Context Menu @@ -977,7 +1015,7 @@ define(function(require, exports, module) { } else { var node = fsCache.findNode(path); - if (node) //Otherwise orphan-append will pick it up + if (node) // Otherwise orphan-append will pick it up expandNode(node); } @@ -1045,14 +1083,14 @@ define(function(require, exports, module) { if (typeof node == "string") node = fsCache.findNode(node, "refresh"); + if (node && !node.isFolder) + node = node.parent; if (node && node.status === "loaded") { tree.provider.setAttribute(node, "status", "pending"); node.children = null; } }); - //c9.dispatchEvent("track_action", { type: "reloadtree" }); - loadProjectTree(false, function(err) { var expandedNodes = Object.keys(expandedList); expandedList = {}; @@ -1067,7 +1105,7 @@ define(function(require, exports, module) { callback(err); tree.provider.on("changeScrollTop", scrollHandler); - emit("refreshComplete") + emit("refreshComplete"); }); } @@ -1100,6 +1138,7 @@ define(function(require, exports, module) { function expandAndSelect(path_or_node) { var node = findNode(path_or_node); expand(node, function(){ + refreshing = false; tree.select(node); scrollToSelection(); }); @@ -1212,10 +1251,12 @@ define(function(require, exports, module) { } function select(path_or_node) { + refreshing = false; tree.select(findNode(path_or_node)); } function selectList(list) { + refreshing = false; tree.selection.setSelection(list.map(function(n) { return findNode(n); })); @@ -1293,6 +1334,9 @@ define(function(require, exports, module) { callback(err, data); }); + var node = fsCache.findNode(newpath, "expand"); + if (node) + expandAndSelect(node); }); } diff --git a/plugins/c9.ide.ui/codebox.js b/plugins/c9.ide.ui/codebox.js index 698e6fca..504a0aab 100644 --- a/plugins/c9.ide.ui/codebox.js +++ b/plugins/c9.ide.ui/codebox.js @@ -62,7 +62,7 @@ apf.codebox = function(struct, tagName) { this.value = ""; this.$draw = function(){ - //Build Main Skin + // Build Main Skin this.$ext = this.$getExternal(); this.$input = this.$getLayoutNode("main", "input", this.$ext); this.$button = this.$getLayoutNode("main", "button", this.$ext); @@ -87,28 +87,28 @@ apf.codebox = function(struct, tagName) { this.$editor = this.ace = ace; ace.renderer.setPadding(2); this.ace.codebox = this; - - ace.on("focus", function() { - dom.removeCssClass(ace.codebox.$ext, "tb_textboxInitial"); - - if (ace.renderer.initialMessageNode) { + + var checkInitial = function() { + var value = ace.getValue(); + if (value && ace.renderer.initialMessageNode) { + dom.removeCssClass(ace.container, "ace_initialMsg"); ace.renderer.scroller.removeChild(ace.renderer.initialMessageNode); ace.renderer.initialMessageNode = null; } - }); + else if (!value && !ace.renderer.initialMessageNode) { + dom.addCssClass(ace.container, "ace_initialMsg"); + var el = document.createElement("div"); + el.className = "tb_textboxInitialMsg"; + el.textContent = ace.codebox["initial-message"] || ""; + ace.renderer.initialMessageNode = el; + ace.renderer.scroller.appendChild(ace.renderer.initialMessageNode); + } + + }; + ace.on("input", checkInitial); - function onBlur() { - if (ace.$isFocused || ace.session.getValue()) - return; - dom.addCssClass(ace.codebox.$ext, "tb_textboxInitial"); - - if (ace.renderer.initialMessageNode) - return; - ace.renderer.initialMessageNode = document.createTextNode(ace.codebox["initial-message"] || ""); - ace.renderer.scroller.appendChild(ace.renderer.initialMessageNode); - } - ace.on("blur", onBlur); - setTimeout(onBlur, 100); + setTimeout(checkInitial, 100); + // todo should we do this here? // ace.on("resize", function(){apf.layout.forceResize();}); @@ -181,6 +181,7 @@ apf.codebox = function(struct, tagName) { new MultiSelect(editor); editor.session.setUndoManager(new UndoManager()); + editor.setOption("indentedSoftWrap", false); editor.setHighlightActiveLine(false); editor.setShowPrintMargin(false); editor.renderer.setShowGutter(false); diff --git a/plugins/c9.ide.ui/menus.js b/plugins/c9.ide.ui/menus.js index 2db45cc9..0c06c306 100644 --- a/plugins/c9.ide.ui/menus.js +++ b/plugins/c9.ide.ui/menus.js @@ -947,8 +947,10 @@ define(function(require, exports, module) { } function show(x, y, type) { - if (type == "context") - y++; + if (type == "context") { + x += 2; + y += 2; + } lastCoords = { x : x, y : y }; aml.display(x, y); } @@ -967,7 +969,7 @@ define(function(require, exports, module) { checkItems.call(this, e); }, "onitemclick" : function(e) { - emit("itemclick", { value : e.value }); + emit("itemclick", { value : e.value, item: e.relatedNode }); } }); aml.cloud9menu = plugin; diff --git a/plugins/c9.ide.ui/ui.js b/plugins/c9.ide.ui/ui.js index d45b7f5b..9ea5b344 100644 --- a/plugins/c9.ide.ui/ui.js +++ b/plugins/c9.ide.ui/ui.js @@ -124,7 +124,7 @@ define(function(require, module, exports) { "list", "tab", "textbox", "textarea", "radiobutton", "checkbox", "page", "splitter", "hsplitbox", "vsplitbox", "group", "img", "label", "spinner", "dropdown", "BindingColumnRule", "datagrid", "hbox", "vbox", "colorbox", - "frame", "password", "modalwindow", "filler", "splitbutton"].forEach(function(tag) { + "frame", "password", "modalwindow", "filler", "splitbutton", "codebox"].forEach(function(tag) { plugin[tag] = function(struct) { return new apf[tag](struct); }; diff --git a/plugins/c9.ide.ui/widgets.list.js b/plugins/c9.ide.ui/widgets.list.js index 397e4105..928982b9 100644 --- a/plugins/c9.ide.ui/widgets.list.js +++ b/plugins/c9.ide.ui/widgets.list.js @@ -34,7 +34,7 @@ define(function(require, exports, module) { var acetree; var model; var redirectEvents; - var filterRoot; + var fRoot; var meta = {}; var dataType = options.model ? "object" : options.dataType; var excludedEvents = { @@ -251,6 +251,16 @@ define(function(require, exports, module) { */ get enableDragdrop(){ return acetree.getOption("enableDragDrop"); }, set enableDragdrop(value){ acetree.setOption("enableDragDrop", value); }, + /** + * + */ + get enableVariableHeight(){ return model.getItemHeight; }, + set enableVariableHeight(value){ + if (!value) throw new Error("Unable to remove variable height"); + + var variableHeightRowMixin = model.constructor.variableHeightRowMixin; + variableHeightRowMixin.apply(model); + }, /** * */ @@ -296,18 +306,20 @@ define(function(require, exports, module) { set filterKeyword(value){ model.keyword = value; if (!model.keyword) { - filterRoot = null; + fRoot = null; model.reKeyword = null; model.setRoot(model.cachedRoot); } else { model.reKeyword = new RegExp("(" + util.escapeRegExp(model.keyword) + ")", 'i'); - filterRoot = search.treeSearch( - model.cachedRoot.items || model.cachedRoot, + fRoot = search.treeSearch( + model.filterRoot + ? model.filterRoot.items || model.filterRoot + : model.cachedRoot.items || model.cachedRoot, model.keyword, model.filterCaseInsensitive, - null, null, model.indexProperty); - model.setRoot(filterRoot); + null, null, model.filterProperty); + model.setRoot(fRoot); } }, /** @@ -320,6 +332,11 @@ define(function(require, exports, module) { */ get filterProperty(){ return model.filterProperty; }, set filterProperty(value){ model.filterProperty = value; }, + /** + * + */ + get filterRoot(){ return model.filterRoot; }, + set filterRoot(value){ model.filterRoot = value; }, /** * */ @@ -394,11 +411,21 @@ define(function(require, exports, module) { */ get getClassName(){ return model.getClassName; }, set getClassName(fn){ model.getClassName = fn; }, + /** + * + */ + get getTooltipText(){ return model.getTooltipText; }, + set getTooltipText(fn){ model.getTooltipText = fn; }, /** * */ get getIndex(){ return model.getIndex; }, set getIndex(fn){ model.getIndex = fn; }, + /** + * + */ + get getItemHeight(){ return model.getItemHeight; }, + set getItemHeight(fn){ model.getItemHeight = fn; }, // Events _events: [ @@ -494,7 +521,10 @@ define(function(require, exports, module) { */ setRoot: function(root){ model.cachedRoot = root; - return model.setRoot(root); + if (model.keyword) + plugin.filterKeyword = model.keyword; + else + return model.setRoot(root); }, /** * @@ -538,8 +568,8 @@ define(function(require, exports, module) { /** * */ - scrollIntoView: function(anchor, lead, offset){ - return acetree.renderer.scrollCaretIntoView(anchor, lead, offset); + scrollIntoView: function(anchor, offset){ + return acetree.renderer.scrollCaretIntoView(anchor, offset); }, /** * @@ -585,7 +615,10 @@ define(function(require, exports, module) { * */ refresh: function(){ - model.setRoot(filterRoot || plugin.root); + if (model.keyword) + plugin.filterKeyword = model.keyword; + else + model.setRoot(plugin.root); }, /** * diff --git a/plugins/c9.nodeapi/timeago.js b/plugins/c9.nodeapi/timeago.js new file mode 100644 index 00000000..5d7e61f5 --- /dev/null +++ b/plugins/c9.nodeapi/timeago.js @@ -0,0 +1,132 @@ +define(function(require, exports, module) { + + function timeago(timestamp) { + if (timestamp instanceof Date) { + return inWords(timestamp); + } else if (typeof timestamp === "string") { + return inWords(parse(timestamp)); + } else if (typeof timestamp === "number") { + return inWords(new Date(timestamp)); + } else { + return timeagoElement(timestamp); + } + } + + var settings = { + refreshMillis: 60000, + allowFuture: false, + strings: { + prefixAgo: null, + prefixFromNow: null, + suffixAgo: "ago", + suffixFromNow: "from now", + seconds: "less than a minute", + minute: "about a minute", + minutes: "%d minutes", + hour: "about an hour", + hours: "about %d hours", + day: "a day", + days: "%d days", + month: "about a month", + months: "%d months", + year: "about a year", + years: "%d years", + wordSeparator: " ", + numbers: [] + } + }; + function distanceInWords(distanceMillis) { + var $l = settings.strings; + var prefix = $l.prefixAgo; + var suffix = $l.suffixAgo; + if (settings.allowFuture) { + if (distanceMillis < 0) { + prefix = $l.prefixFromNow; + suffix = $l.suffixFromNow; + } + } + + var seconds = Math.abs(distanceMillis) / 1000; + var minutes = seconds / 60; + var hours = minutes / 60; + var days = hours / 24; + var years = days / 365; + + function substitute(stringOrFunction, number) { + var string = typeof stringOrFunction === "function" ? stringOrFunction(number, distanceMillis) : stringOrFunction; + var value = ($l.numbers && $l.numbers[number]) || number; + return string.replace(/%d/i, value); + } + + var words = seconds < 45 && substitute($l.seconds, Math.round(seconds)) || + seconds < 90 && substitute($l.minute, 1) || + minutes < 45 && substitute($l.minutes, Math.round(minutes)) || + minutes < 90 && substitute($l.hour, 1) || + hours < 24 && substitute($l.hours, Math.round(hours)) || + hours < 42 && substitute($l.day, 1) || + days < 30 && substitute($l.days, Math.round(days)) || + days < 45 && substitute($l.month, 1) || + days < 365 && substitute($l.months, Math.round(days / 30)) || + years < 1.5 && substitute($l.year, 1) || + substitute($l.years, Math.round(years)); + + var separator = $l.wordSeparator || ""; + if ($l.wordSeparator === undefined) { separator = " "; } + return [prefix, words, suffix].filter(function(s) { return s; }) + .map(function(s){ return s.trim(); }) + .join(separator); + } + function parse(iso8601) { + var s = iso8601.trim(); + s = s.replace(/\.\d+/,""); // remove milliseconds + s = s.replace(/-/,"/").replace(/-/,"/"); + s = s.replace(/T/," ").replace(/Z/," UTC"); + s = s.replace(/([\+\-]\d\d)\:?(\d\d)/," $1$2"); // -04:00 -> -0400 + return new Date(s); + } + function datetime(elem) { + var iso8601 = isTime(elem) ? elem.getAttribute("datetime") : elem.getAttribute("title"); + return parse(iso8601); + } + function isTime(elem) { + return elem.tagName.toLowerCase() === "time"; + } + + function timeagoElement(elem) { + refresh(elem); + if (settings.refreshMillis > 0) + setInterval(refresh.bind(null, elem), settings.refreshMillis); + } + + function refresh(elem) { + var datetime = prepareDateTime(elem); + if (!isNaN(datetime)) + elem.textContent = inWords(datetime); + } + + function prepareDateTime(elem) { + var timeagoAttr = elem.getAttribute("timeago"); + if (timeagoAttr) + return new Date(timeagoAttr); + var data = datetime(elem); + elem.setAttribute("timeago", data); + var text = elem.textContent.trim(); + if (text.length > 0 && !(isTime(elem) && elem.title)) + elem.title = text; + return data; + } + + function inWords(date) { + return distanceInWords(distance(date)); + } + + function distance(date) { + return (new Date().getTime() - date.getTime()); + } + + // fix for IE6 suckage + document.createElement("abbr"); + document.createElement("time"); + + module.exports = timeago; +}); \ No newline at end of file diff --git a/plugins/c9.static/build.js b/plugins/c9.static/build.js index ea929cda..b8e69593 100644 --- a/plugins/c9.static/build.js +++ b/plugins/c9.static/build.js @@ -254,6 +254,7 @@ function main(options, imports, register) { "plugins/c9.ide.language/worker", "plugins/c9.ide.language.generic/local_completer", "plugins/c9.ide.language.generic/snippet_completer", + "plugins/c9.ide.language.generic/mode_completer", "plugins/c9.ide.language.generic/open_files_local_completer", "plugins/c9.ide.language.generic/simple/make", "plugins/c9.ide.language.generic/simple/shell", diff --git a/plugins/c9.static/cdn.js b/plugins/c9.static/cdn.js index ede7d5f6..023a2257 100644 --- a/plugins/c9.static/cdn.js +++ b/plugins/c9.static/cdn.js @@ -76,6 +76,8 @@ function main(options, imports, register) { var parts = e.split(" "); var id = parts[1]; var etag = parts[0]; + if (!id || /^https?:\/\//.test(id)) + return q.oneDone(); var path = resolveModulePath(id, req.pathConfig.pathMap); if (path == id && !/^(\/|\w:)/.test(path)) { diff --git a/plugins/c9.vfs.client/endpoint.js b/plugins/c9.vfs.client/endpoint.js index 24ad4b9f..e36d2fda 100644 --- a/plugins/c9.vfs.client/endpoint.js +++ b/plugins/c9.vfs.client/endpoint.js @@ -225,6 +225,10 @@ define(function(require, exports, module) { callback(fatalError(res.error.message, "dashboard")); return; } + else if (err.code == 404) { + callback(fatalError("This workspace no longer appears to exist or failed to be created.", "dashboard")); + return; + } else if (err.code === 428 && res.error) { emit("restore", { projectState: res.error.projectState, diff --git a/plugins/c9.vfs.client/vfs_client.js b/plugins/c9.vfs.client/vfs_client.js index 9e11a4d0..0f07d9b4 100644 --- a/plugins/c9.vfs.client/vfs_client.js +++ b/plugins/c9.vfs.client/vfs_client.js @@ -253,7 +253,7 @@ define(function(require, exports, module) { err.message = "SSH permission denied. Please review your workspace configuration."; return showAlert("Workspace Error", "Unable to access your workspace", err.message, function() { window.location = dashboardUrl; - }); + }, { yes: "Return to dashboard" }); case "reload": lastError = showError(err.message + ". Please reload this window.", -1); setTimeout(function() { diff --git a/plugins/c9.vfs.standalone/www/test.js b/plugins/c9.vfs.standalone/www/test.js index f30a80b4..3d2217ef 100644 --- a/plugins/c9.vfs.standalone/www/test.js +++ b/plugins/c9.vfs.standalone/www/test.js @@ -199,6 +199,8 @@ require([ c.exec = function(name) { commands[name].exec(); }; + c.getPrettyHotkey = function(name) { return "" }; + c.getHotkey = function(name) { return "" }; c.getExceptionList = function(){ return []; }; return c;