From 92dc3e59d671b8e8249d3d1d15ae6336d34c0822 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Wed, 7 Mar 2012 17:36:11 +0100 Subject: [PATCH 01/40] First attempt to implement the block system from django templates on top of the npm module ejs. --- src/node/eejs/eejs.js | 84 ++++++++++++++++++++++++++++++++++ src/node/eejs/examples/bar.ejs | 11 +++++ src/node/eejs/examples/foo.ejs | 7 +++ 3 files changed, 102 insertions(+) create mode 100644 src/node/eejs/eejs.js create mode 100644 src/node/eejs/examples/bar.ejs create mode 100644 src/node/eejs/examples/foo.ejs diff --git a/src/node/eejs/eejs.js b/src/node/eejs/eejs.js new file mode 100644 index 000000000..ed6b329bb --- /dev/null +++ b/src/node/eejs/eejs.js @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2011 RedHog (Egil Möller) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS-IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* Basic usage: + * + * require("./eejs").require("./examples/foo.ejs") + */ + +var ejs = require("ejs"); +var fs = require("fs"); + +exports.init = function (b, recursive) { + if (!exports.info) { + exports.info = { + buf_stack: [], + block_stack: [], + blocks: {}, + level: 1 + } + } + exports.info.buf = b; +} + +exports.begin_capture = function() { + exports.info.buf_stack.push(exports.info.buf.concat()); + exports.info.buf.splice(0, exports.info.buf.length); +} + +exports.end_capture = function () { + var res = exports.info.buf.join(""); + exports.info.buf.splice.apply( + exports.info.buf, + [0, exports.info.buf.length].concat(exports.info.buf_stack.pop())); + return res; +} + +exports.begin_define_block = function (name) { + if (typeof exports.info.blocks[name] == "undefined") + exports.info.blocks[name] = {}; + exports.info.block_stack.push(name); + exports.begin_capture(); +} + +exports.end_define_block = function () { + content = exports.end_capture(); + var name = exports.info.block_stack.pop(); + if (typeof exports.info.blocks[name].content == "undefined") + exports.info.blocks[name].content = content; + return exports.info.blocks[name].content; +} + +exports.end_block = function () { + var res = exports.end_define_block(); + exports.info.buf.push(res); +} + +exports.begin_block = exports.begin_define_block; + +exports.require = function (name, args) { + if (args == undefined) args = {}; + if (exports.info) + exports.info.buf_stack.push(exports.info.buf); + var res = ejs.render( + fs.readFileSync(require.resolve(name)).toString(), + args); + if (exports.info) + exports.info.buf = exports.info.buf_stack.pop(); + if (exports.info.buf) + exports.info.buf.push(res); + return res; +} diff --git a/src/node/eejs/examples/bar.ejs b/src/node/eejs/examples/bar.ejs new file mode 100644 index 000000000..14ed975e7 --- /dev/null +++ b/src/node/eejs/examples/bar.ejs @@ -0,0 +1,11 @@ +<% var e = require("./eejs.js"); e.init(buf); %> + +a +<% e.begin_block("bar"); %> + A + <% e.begin_block("foo"); %> + XX + <% e.end_block(); %> + B +<% e.end_block(); %> +b diff --git a/src/node/eejs/examples/foo.ejs b/src/node/eejs/examples/foo.ejs new file mode 100644 index 000000000..5dcac61c6 --- /dev/null +++ b/src/node/eejs/examples/foo.ejs @@ -0,0 +1,7 @@ +<% var e = require("./eejs.js"); e.init(buf); %> + +<% e.begin_define_block("foo"); %> +YY +<% e.end_define_block(); %> + +<% e.require("./bar.ejs"); %> From f6212f452ca54efd1cb46dc5bd7c72d9a2213e4d Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Thu, 8 Mar 2012 21:01:01 +0100 Subject: [PATCH 02/40] Bugfix --- src/node/eejs/eejs.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/node/eejs/eejs.js b/src/node/eejs/eejs.js index ed6b329bb..8faaab800 100644 --- a/src/node/eejs/eejs.js +++ b/src/node/eejs/eejs.js @@ -21,6 +21,7 @@ var ejs = require("ejs"); var fs = require("fs"); +var path = require("path"); exports.init = function (b, recursive) { if (!exports.info) { @@ -28,7 +29,7 @@ exports.init = function (b, recursive) { buf_stack: [], block_stack: [], blocks: {}, - level: 1 + filestack: [], } } exports.info.buf = b; @@ -71,13 +72,22 @@ exports.begin_block = exports.begin_define_block; exports.require = function (name, args) { if (args == undefined) args = {}; - if (exports.info) - exports.info.buf_stack.push(exports.info.buf); + if (!exports.info) + exports.init(null); + + if ((name.indexOf("./") == 0 || name.indexOf("../") == 0) && exports.info.file_stack) { + name = path.join(exports.info.file_stack[exports.info.file_stack.length-1], name); + } + var ejspath = require.resolve(name) + + exports.info.file_stack.push(ejpath); + exports.info.buf_stack.push(exports.info.buf); var res = ejs.render( - fs.readFileSync(require.resolve(name)).toString(), + fs.readFileSync(ejspath).toString(), args); - if (exports.info) - exports.info.buf = exports.info.buf_stack.pop(); + exports.info.buf = exports.info.buf_stack.pop(); + exports.info.file_stack.pop(); + if (exports.info.buf) exports.info.buf.push(res); return res; From 72571e5ef08fc4b42f11bcfccee7ea4aa648264e Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Thu, 8 Mar 2012 21:03:22 +0100 Subject: [PATCH 03/40] Bugfixing cache --- src/node/utils/caching_middleware.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js index f25059b88..df114350a 100644 --- a/src/node/utils/caching_middleware.js +++ b/src/node/utils/caching_middleware.js @@ -21,9 +21,9 @@ var path = require('path'); var server = require('../server'); var zlib = require('zlib'); var util = require('util'); +var settings = require('./Settings'); -var ROOT_DIR = path.normalize(__dirname + "/../"); -var CACHE_DIR = ROOT_DIR + '../var/'; +var CACHE_DIR = path.join(settings.root, 'var'); var responseCache = {}; From 384d7686102ff5bbd6e795b07b70ec33eb6e1fa3 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Thu, 8 Mar 2012 21:47:18 +0100 Subject: [PATCH 04/40] One more bug in cache dir setting --- src/node/utils/caching_middleware.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/node/utils/caching_middleware.js b/src/node/utils/caching_middleware.js index df114350a..9712deb30 100644 --- a/src/node/utils/caching_middleware.js +++ b/src/node/utils/caching_middleware.js @@ -23,7 +23,7 @@ var zlib = require('zlib'); var util = require('util'); var settings = require('./Settings'); -var CACHE_DIR = path.join(settings.root, 'var'); +var CACHE_DIR = path.join(settings.root, 'var/'); var responseCache = {}; From 8756165ece8460fc15067ff535a59e9ff994708c Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Tue, 13 Mar 2012 17:08:57 +0100 Subject: [PATCH 05/40] Added a plugin-readme --- README.plugins | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 README.plugins diff --git a/README.plugins b/README.plugins new file mode 100644 index 000000000..72c456447 --- /dev/null +++ b/README.plugins @@ -0,0 +1,16 @@ +So, a plugin is an npm package whose name starts with ep_ and that contains a file ep.json +require("ep_etherpad-lite/static/js/plugingfw/plugins").update() will use npm to list all installed modules and read their ep.json files. These will contain registrations for hooks which are loaded +A hook registration is a pairs of a hook name and a function reference (filename for require() plus function name) +require("ep_etherpad-lite/static/js/plugingfw/hooks").callAll("hook_name", {argname:value}) will call all hook functions registered for hook_name +That is the basis. +Ok, so that was a slight simplification: inside ep.json, hook registrations are grouped into groups called "parts". Parts from all plugins are ordered using a topological sort according to "pre" and "post" pointers to other plugins/parts (just like dependencies, but non-installed plugins are silently ignored). +This ordering is honored when you do callAll(hook_name) - hook functions for that hook_name are called in that order +Ordering between plugins is undefined, only parts are ordered. + +A plugin usually has one part, but it van have multiple. +This is so that it can insert some hook registration before that of another plugin, and another one after. +This is important for e.g. registering URL-handlers for the express webserver, if you have some very generic and some very specific url-regexps +So, that's basically it... apart from client-side hooks +which works the same way, but uses a separate member of the part (part.client_hooks vs part.hooks), and where the hook function must obviously reside in a file require():able from the client... +One thing more: The main etherpad tree is actually a plugin itself, called ep_etherpad-lite, and it has it's own ep.json... +was that clear? \ No newline at end of file From 6fb0e00f036fa7d5bf7c56abe0b572d868ed91a3 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Tue, 13 Mar 2012 17:31:40 +0100 Subject: [PATCH 06/40] Bugfixes for eejs --- src/node/eejs/eejs.js | 28 ++++++++++++++-------------- src/node/eejs/examples/bar.ejs | 2 -- src/node/eejs/examples/foo.ejs | 2 -- src/package.json | 3 ++- 4 files changed, 16 insertions(+), 19 deletions(-) diff --git a/src/node/eejs/eejs.js b/src/node/eejs/eejs.js index 8faaab800..7401b0c48 100644 --- a/src/node/eejs/eejs.js +++ b/src/node/eejs/eejs.js @@ -23,15 +23,14 @@ var ejs = require("ejs"); var fs = require("fs"); var path = require("path"); +exports.info = { + buf_stack: [], + block_stack: [], + blocks: {}, + file_stack: [], +}; + exports.init = function (b, recursive) { - if (!exports.info) { - exports.info = { - buf_stack: [], - block_stack: [], - blocks: {}, - filestack: [], - } - } exports.info.buf = b; } @@ -75,16 +74,17 @@ exports.require = function (name, args) { if (!exports.info) exports.init(null); - if ((name.indexOf("./") == 0 || name.indexOf("../") == 0) && exports.info.file_stack) { - name = path.join(exports.info.file_stack[exports.info.file_stack.length-1], name); + if ((name.indexOf("./") == 0 || name.indexOf("../") == 0) && exports.info.file_stack.length) { + name = path.join(path.dirname(exports.info.file_stack[exports.info.file_stack.length-1]), name); } var ejspath = require.resolve(name) - exports.info.file_stack.push(ejpath); + args.e = exports; + var template = '<% e.init(buf); %>' + fs.readFileSync(ejspath).toString(); + + exports.info.file_stack.push(ejspath); exports.info.buf_stack.push(exports.info.buf); - var res = ejs.render( - fs.readFileSync(ejspath).toString(), - args); + var res = ejs.render(template, args); exports.info.buf = exports.info.buf_stack.pop(); exports.info.file_stack.pop(); diff --git a/src/node/eejs/examples/bar.ejs b/src/node/eejs/examples/bar.ejs index 14ed975e7..6a2cc4bab 100644 --- a/src/node/eejs/examples/bar.ejs +++ b/src/node/eejs/examples/bar.ejs @@ -1,5 +1,3 @@ -<% var e = require("./eejs.js"); e.init(buf); %> - a <% e.begin_block("bar"); %> A diff --git a/src/node/eejs/examples/foo.ejs b/src/node/eejs/examples/foo.ejs index 5dcac61c6..268882cb1 100644 --- a/src/node/eejs/examples/foo.ejs +++ b/src/node/eejs/examples/foo.ejs @@ -1,5 +1,3 @@ -<% var e = require("./eejs.js"); e.init(buf); %> - <% e.begin_define_block("foo"); %> YY <% e.end_define_block(); %> diff --git a/src/package.json b/src/package.json index b81f724f2..556b1c494 100644 --- a/src/package.json +++ b/src/package.json @@ -23,7 +23,8 @@ "log4js" : "0.4.1", "jsdom-nocontextifiy" : "0.2.10", "async-stacktrace" : "0.0.2", - "npm" : "1.1" + "npm" : "1.1", + "ejs" : "0.6.1" }, "devDependencies": { "jshint" : "*" From 3ffed708508fde8ab7597b4f66211258262d196f Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Tue, 13 Mar 2012 17:42:15 +0100 Subject: [PATCH 07/40] Added inherit function --- src/node/eejs/eejs.js | 24 +++++++++++++++++------- src/node/eejs/examples/foo.ejs | 8 ++++---- 2 files changed, 21 insertions(+), 11 deletions(-) diff --git a/src/node/eejs/eejs.js b/src/node/eejs/eejs.js index 7401b0c48..9291a3413 100644 --- a/src/node/eejs/eejs.js +++ b/src/node/eejs/eejs.js @@ -30,10 +30,18 @@ exports.info = { file_stack: [], }; -exports.init = function (b, recursive) { +exports._init = function (b, recursive) { + exports.info.buf_stack.push(exports.info.buf); exports.info.buf = b; } +exports._exit = function (b, recursive) { + exports.info.file_stack[exports.info.file_stack.length-1].inherit.forEach(function (item) { + exports.require(item.name, item.args); + }); + exports.info.buf = exports.info.buf_stack.pop(); +} + exports.begin_capture = function() { exports.info.buf_stack.push(exports.info.buf.concat()); exports.info.buf.splice(0, exports.info.buf.length); @@ -69,23 +77,25 @@ exports.end_block = function () { exports.begin_block = exports.begin_define_block; +exports.inherit = function (name, args) { + exports.info.file_stack[exports.info.file_stack.length-1].inherit.push({name:name, args:args}); +} + exports.require = function (name, args) { if (args == undefined) args = {}; if (!exports.info) exports.init(null); if ((name.indexOf("./") == 0 || name.indexOf("../") == 0) && exports.info.file_stack.length) { - name = path.join(path.dirname(exports.info.file_stack[exports.info.file_stack.length-1]), name); + name = path.join(path.dirname(exports.info.file_stack[exports.info.file_stack.length-1].path), name); } var ejspath = require.resolve(name) args.e = exports; - var template = '<% e.init(buf); %>' + fs.readFileSync(ejspath).toString(); + var template = '<% e._init(buf); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>'; - exports.info.file_stack.push(ejspath); - exports.info.buf_stack.push(exports.info.buf); - var res = ejs.render(template, args); - exports.info.buf = exports.info.buf_stack.pop(); + exports.info.file_stack.push({path: ejspath, inherit: []}); + var res = ejs.render(template, args); exports.info.file_stack.pop(); if (exports.info.buf) diff --git a/src/node/eejs/examples/foo.ejs b/src/node/eejs/examples/foo.ejs index 268882cb1..3778b59a7 100644 --- a/src/node/eejs/examples/foo.ejs +++ b/src/node/eejs/examples/foo.ejs @@ -1,5 +1,5 @@ -<% e.begin_define_block("foo"); %> -YY -<% e.end_define_block(); %> +<% e.inherit("./bar.ejs"); %> -<% e.require("./bar.ejs"); %> +<% e.begin_define_block("foo"); %> + YY +<% e.end_define_block(); %> From 05c2e0fde521b17a8229ac4d2f0478c71bef17d4 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Tue, 13 Mar 2012 18:24:45 +0100 Subject: [PATCH 08/40] Made all html pages into eejs templates --- src/node/eejs/{eejs.js => index.js} | 0 src/node/hooks/express/specialpages.js | 10 ++++------ src/{static => templates}/index.html | 0 src/{static => templates}/pad.html | 0 src/{static => templates}/timeslider.html | 0 5 files changed, 4 insertions(+), 6 deletions(-) rename src/node/eejs/{eejs.js => index.js} (100%) rename src/{static => templates}/index.html (100%) rename src/{static => templates}/pad.html (100%) rename src/{static => templates}/timeslider.html (100%) diff --git a/src/node/eejs/eejs.js b/src/node/eejs/index.js similarity index 100% rename from src/node/eejs/eejs.js rename to src/node/eejs/index.js diff --git a/src/node/hooks/express/specialpages.js b/src/node/hooks/express/specialpages.js index 13cfd8215..585a7eab8 100644 --- a/src/node/hooks/express/specialpages.js +++ b/src/node/hooks/express/specialpages.js @@ -1,12 +1,12 @@ var path = require('path'); +var eejs = require('ep_etherpad-lite/node/eejs'); exports.expressCreateServer = function (hook_name, args, cb) { //serve index.html under / args.app.get('/', function(req, res) { - var filePath = path.normalize(__dirname + "/../../../static/index.html"); - res.sendfile(filePath, { maxAge: exports.maxAge }); + res.send(eejs.require("ep_etherpad-lite/templates/index.html"), { maxAge: exports.maxAge }); }); //serve robots.txt @@ -34,15 +34,13 @@ exports.expressCreateServer = function (hook_name, args, cb) { //serve pad.html under /p args.app.get('/p/:pad', function(req, res, next) { - var filePath = path.normalize(__dirname + "/../../../static/pad.html"); - res.sendfile(filePath, { maxAge: exports.maxAge }); + res.send(eejs.require("ep_etherpad-lite/templates/pad.html"), { maxAge: exports.maxAge }); }); //serve timeslider.html under /p/$padname/timeslider args.app.get('/p/:pad/timeslider', function(req, res, next) { - var filePath = path.normalize(__dirname + "/../../../static/timeslider.html"); - res.sendfile(filePath, { maxAge: exports.maxAge }); + res.send(eejs.require("ep_etherpad-lite/templates/timeslider.html"), { maxAge: exports.maxAge }); }); } \ No newline at end of file diff --git a/src/static/index.html b/src/templates/index.html similarity index 100% rename from src/static/index.html rename to src/templates/index.html diff --git a/src/static/pad.html b/src/templates/pad.html similarity index 100% rename from src/static/pad.html rename to src/templates/pad.html diff --git a/src/static/timeslider.html b/src/templates/timeslider.html similarity index 100% rename from src/static/timeslider.html rename to src/templates/timeslider.html From a5366a0a161cf1ecfc8e9beb1da8d322402628f1 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Tue, 13 Mar 2012 20:32:56 +0100 Subject: [PATCH 09/40] Integrated hooks into templates and provided a blokc/hook for the left and right editbar menu --- available_plugins/ep_fintest/ep.json | 3 +- available_plugins/ep_fintest/otherpart.js | 9 ++ src/node/eejs/examples/foo.ejs | 2 + src/node/eejs/index.js | 16 +++- src/templates/pad.html | 110 +++++++++++----------- 5 files changed, 83 insertions(+), 57 deletions(-) diff --git a/available_plugins/ep_fintest/ep.json b/available_plugins/ep_fintest/ep.json index f9e914914..4ec8e3924 100644 --- a/available_plugins/ep_fintest/ep.json +++ b/available_plugins/ep_fintest/ep.json @@ -25,7 +25,8 @@ "hooks": { "somehookname": "ep_fintest/otherpart:somehook", "morehook": "ep_fintest/otherpart:morehook", - "expressCreateServer": "ep_fintest/otherpart:expressServer" + "expressCreateServer": "ep_fintest/otherpart:expressServer", + "eejsBlock_editbarMenuLeft": "ep_fintest/otherpart:eejsBlock_editbarMenuLeft" }, "client_hooks": { "somehookname": "ep_fintest/static/js/test:bar" diff --git a/available_plugins/ep_fintest/otherpart.js b/available_plugins/ep_fintest/otherpart.js index ca259f44b..718fb095c 100644 --- a/available_plugins/ep_fintest/otherpart.js +++ b/available_plugins/ep_fintest/otherpart.js @@ -14,3 +14,12 @@ exports.expressServer = function (hook_name, args, cb) { res.send("Abra cadabra"); }); } + +exports.eejsBlock_editbarMenuLeft = function (hook_name, args, cb) { + args.content = args.content + '\ +
  • \ + \ +
  • \ + '; + return cb(); +} diff --git a/src/node/eejs/examples/foo.ejs b/src/node/eejs/examples/foo.ejs index 3778b59a7..daee5f8e8 100644 --- a/src/node/eejs/examples/foo.ejs +++ b/src/node/eejs/examples/foo.ejs @@ -2,4 +2,6 @@ <% e.begin_define_block("foo"); %> YY + <% e.super(); %> + ZZ <% e.end_define_block(); %> diff --git a/src/node/eejs/index.js b/src/node/eejs/index.js index 9291a3413..a16ddc807 100644 --- a/src/node/eejs/index.js +++ b/src/node/eejs/index.js @@ -16,12 +16,13 @@ /* Basic usage: * - * require("./eejs").require("./examples/foo.ejs") + * require("./index").require("./examples/foo.ejs") */ var ejs = require("ejs"); var fs = require("fs"); var path = require("path"); +var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js"); exports.info = { buf_stack: [], @@ -62,17 +63,26 @@ exports.begin_define_block = function (name) { exports.begin_capture(); } +exports.super = function () { + exports.info.buf.push(''); +} + exports.end_define_block = function () { content = exports.end_capture(); var name = exports.info.block_stack.pop(); if (typeof exports.info.blocks[name].content == "undefined") exports.info.blocks[name].content = content; + else if (typeof exports.info.blocks[name].content.indexOf('')) + exports.info.blocks[name].content = exports.info.blocks[name].content.replace('', content); + return exports.info.blocks[name].content; } exports.end_block = function () { - var res = exports.end_define_block(); - exports.info.buf.push(res); + var name = exports.info.block_stack[exports.info.block_stack.length-1]; + var args = {content: exports.end_define_block()}; + hooks.callAll("eejsBlock_" + name, args); + exports.info.buf.push(args.content); } exports.begin_block = exports.begin_define_block; diff --git a/src/templates/pad.html b/src/templates/pad.html index 95a5b98fa..e34f572b7 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -15,61 +15,65 @@
    From 1a64a6c1c5ece11307db152581ae51d34a9449bf Mon Sep 17 00:00:00 2001 From: Constantin Jucovschi Date: Wed, 14 Mar 2012 11:45:25 +0100 Subject: [PATCH 10/40] makes plugin architecture work in client-side from inside IFrames as well --- src/static/js/ace.js | 5 ++++ src/static/js/pluginfw/parent_require.js | 37 ++++++++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 src/static/js/pluginfw/parent_require.js diff --git a/src/static/js/ace.js b/src/static/js/ace.js index 1306dba01..659503d86 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -261,6 +261,11 @@ require.setGlobalKeyPath("require");\n\ // Inject my plugins into my child. iframeHTML.push('\ + + <% if (errors.length) { %> @@ -54,7 +137,8 @@

    Search for plugins to install

    - + +
    @@ -64,23 +148,28 @@ - - <% for (var plugin_name in search_results) { %> - <% var plugin = search_results[plugin_name]; %> - - - - - - <% } %> + + + + + + + +
    <%= plugin.name %><%= plugin.description %> -
    - - -
    -
    + +
    +
    +

    + Please wait: + +

    + +
    +
    + From af96509fbb84f2c05426ba2e5208dcd34edd4f90 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Sat, 17 Mar 2012 18:17:10 +0100 Subject: [PATCH 14/40] Plugin install/uninstall --- src/node/hooks/express/adminplugins.js | 22 +++++++++++---- src/templates/admin/plugins.html | 38 +++++++++++++++++--------- 2 files changed, 41 insertions(+), 19 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index d3884acb2..4dbd788fe 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -32,14 +32,24 @@ exports.socketio = function (hook_name, args, cb) { }); }); - socket.on("install", function (query) { + socket.on("install", function (plugin_name) { + socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."}); + installer.install(plugin_name, function (er) { + if (er) + socket.emit("progress", {progress:1, error:er}); + else + socket.emit("progress", {progress:1, message:'Done.'}); + }); }); - socket.on("uninstall", function (query) { + socket.on("uninstall", function (plugin_name) { + socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."}); + installer.uninstall(plugin_name, function (er) { + if (er) + socket.emit("progress", {progress:1, error:er}); + else + socket.emit("progress", {progress:1, message:'Done.'}); + }); }); - - - - }); } diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index f83168574..61e277d2f 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -54,14 +54,28 @@ $(document).ready(function () { var socket = io.connect().of("/pluginfw/installer"); - $("#progress.dialog .close").click(function () { - $("#progress.dialog").hide(); - }); + function updateHandlers() { + $("#progress.dialog .close").click(function () { + $("#progress.dialog").hide(); + }); - $("#do-search").click(function () { - if ($("#search-query")[0].value != "") - socket.emit("search", $("#search-query")[0].value); - }); + $("#do-search").click(function () { + if ($("#search-query")[0].value != "") + socket.emit("search", $("#search-query")[0].value); + }); + + $("#do-install").click(function (e) { + var row = $(e.target).closest("tr"); + socket.emit("install", row.find(".name").html()); + }); + + $("#do-uninstall").click(function (e) { + var row = $(e.target).closest("tr"); + socket.emit("install", row.find(".name").html()); + }); + } + + updateHandlers(); socket.on('progress', function (data) { $("#progress.dialog .close").hide(); @@ -93,6 +107,7 @@ } $("#search-results").append(row); } + updateHandlers(); }); }); @@ -120,13 +135,10 @@ <% for (var plugin_name in plugins) { %> <% var plugin = plugins[plugin_name]; %> - <%= plugin.package.name %> + <%= plugin.package.name %> <%= plugin.package.description %> -
    - - -
    + <% } %> @@ -153,7 +165,7 @@ - + From 6fe7f2c2b201428f645f5af36a0f75801025ce8d Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Mon, 19 Mar 2012 16:59:57 +0100 Subject: [PATCH 15/40] Plugin list can now be reloaded 'live' --- src/static/js/pluginfw/read-installed.js | 324 +++++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 src/static/js/pluginfw/read-installed.js diff --git a/src/static/js/pluginfw/read-installed.js b/src/static/js/pluginfw/read-installed.js new file mode 100644 index 000000000..cc03b3579 --- /dev/null +++ b/src/static/js/pluginfw/read-installed.js @@ -0,0 +1,324 @@ +// A copy of npm/lib/utils/read-installed.js +// that is hacked to not cache everything :) + +// Walk through the file-system "database" of installed +// packages, and create a data object related to the +// installed versions of each package. + +/* +This will traverse through all node_modules folders, +resolving the dependencies object to the object corresponding to +the package that meets that dep, or just the version/range if +unmet. + +Assuming that you had this folder structure: + +/path/to ++-- package.json { name = "root" } +`-- node_modules + +-- foo {bar, baz, asdf} + | +-- node_modules + | +-- bar { baz } + | `-- baz + `-- asdf + +where "foo" depends on bar, baz, and asdf, bar depends on baz, +and bar and baz are bundled with foo, whereas "asdf" is at +the higher level (sibling to foo), you'd get this object structure: + +{ +, path: "/path/to" +, parent: null +, dependencies: + { foo : + { version: "1.2.3" + , path: "/path/to/node_modules/foo" + , parent: + , dependencies: + { bar: + { parent: + , path: "/path/to/node_modules/foo/node_modules/bar" + , version: "2.3.4" + , dependencies: { baz: } + } + , baz: { ... } + , asdf: + } + } + , asdf: { ... } + } +} + +Unmet deps are left as strings. +Extraneous deps are marked with extraneous:true +deps that don't meet a requirement are marked with invalid:true + +to READ(packagefolder, parentobj, name, reqver) +obj = read package.json +installed = ./node_modules/* +if parentobj is null, and no package.json + obj = {dependencies:{:"*"}} +deps = Object.keys(obj.dependencies) +obj.path = packagefolder +obj.parent = parentobj +if name, && obj.name !== name, obj.invalid = true +if reqver, && obj.version !satisfies reqver, obj.invalid = true +if !reqver && parentobj, obj.extraneous = true +for each folder in installed + obj.dependencies[folder] = READ(packagefolder+node_modules+folder, + obj, folder, obj.dependencies[folder]) +# walk tree to find unmet deps +for each dep in obj.dependencies not in installed + r = obj.parent + while r + if r.dependencies[dep] + if r.dependencies[dep].verion !satisfies obj.dependencies[dep] + WARN + r.dependencies[dep].invalid = true + obj.dependencies[dep] = r.dependencies[dep] + r = null + else r = r.parent +return obj + + +TODO: +1. Find unmet deps in parent directories, searching as node does up +as far as the left-most node_modules folder. +2. Ignore anything in node_modules that isn't a package folder. + +*/ + + +var npm = require("npm/lib/npm.js") + , fs = require("graceful-fs") + , path = require("path") + , asyncMap = require("slide").asyncMap + , semver = require("semver") + , readJson = require("npm/lib/utils/read-json.js") + , log = require("npm/lib/utils/log.js") + +module.exports = readInstalled + +function readInstalled (folder, cb) { + /* This is where we clear the cache, these three lines are all the + * new code there is */ + rpSeen = {}; + riSeen = []; + var fuSeen = []; + + var d = npm.config.get("depth") + readInstalled_(folder, null, null, null, 0, d, function (er, obj) { + if (er) return cb(er) + // now obj has all the installed things, where they're installed + // figure out the inheritance links, now that the object is built. + resolveInheritance(obj) + cb(null, obj) + }) +} + +var rpSeen = {} +function readInstalled_ (folder, parent, name, reqver, depth, maxDepth, cb) { + //console.error(folder, name) + + var installed + , obj + , real + , link + + fs.readdir(path.resolve(folder, "node_modules"), function (er, i) { + // error indicates that nothing is installed here + if (er) i = [] + installed = i.filter(function (f) { return f.charAt(0) !== "." }) + next() + }) + + readJson(path.resolve(folder, "package.json"), function (er, data) { + obj = copy(data) + + if (!parent) { + obj = obj || true + er = null + } + return next(er) + }) + + fs.lstat(folder, function (er, st) { + if (er) { + if (!parent) real = true + return next(er) + } + fs.realpath(folder, function (er, rp) { + //console.error("realpath(%j) = %j", folder, rp) + real = rp + if (st.isSymbolicLink()) link = rp + next(er) + }) + }) + + var errState = null + , called = false + function next (er) { + if (errState) return + if (er) { + errState = er + return cb(null, []) + } + //console.error('next', installed, obj && typeof obj, name, real) + if (!installed || !obj || !real || called) return + called = true + if (rpSeen[real]) return cb(null, rpSeen[real]) + if (obj === true) { + obj = {dependencies:{}, path:folder} + installed.forEach(function (i) { obj.dependencies[i] = "*" }) + } + if (name && obj.name !== name) obj.invalid = true + obj.realName = name || obj.name + obj.dependencies = obj.dependencies || {} + + // "foo":"http://blah" is always presumed valid + if (reqver + && semver.validRange(reqver) + && !semver.satisfies(obj.version, reqver)) { + obj.invalid = true + } + + if (parent + && !(name in parent.dependencies) + && !(name in (parent.devDependencies || {}))) { + obj.extraneous = true + } + obj.path = obj.path || folder + obj.realPath = real + obj.link = link + if (parent && !obj.link) obj.parent = parent + rpSeen[real] = obj + obj.depth = depth + if (depth >= maxDepth) return cb(null, obj) + asyncMap(installed, function (pkg, cb) { + var rv = obj.dependencies[pkg] + if (!rv && obj.devDependencies) rv = obj.devDependencies[pkg] + readInstalled_( path.resolve(folder, "node_modules/"+pkg) + , obj, pkg, obj.dependencies[pkg], depth + 1, maxDepth + , cb ) + }, function (er, installedData) { + if (er) return cb(er) + installedData.forEach(function (dep) { + obj.dependencies[dep.realName] = dep + }) + + // any strings here are unmet things. however, if it's + // optional, then that's fine, so just delete it. + if (obj.optionalDependencies) { + Object.keys(obj.optionalDependencies).forEach(function (dep) { + if (typeof obj.dependencies[dep] === "string") { + delete obj.dependencies[dep] + } + }) + } + return cb(null, obj) + }) + } +} + +// starting from a root object, call findUnmet on each layer of children +var riSeen = [] +function resolveInheritance (obj) { + if (typeof obj !== "object") return + if (riSeen.indexOf(obj) !== -1) return + riSeen.push(obj) + if (typeof obj.dependencies !== "object") { + obj.dependencies = {} + } + Object.keys(obj.dependencies).forEach(function (dep) { + findUnmet(obj.dependencies[dep]) + }) + Object.keys(obj.dependencies).forEach(function (dep) { + resolveInheritance(obj.dependencies[dep]) + }) +} + +// find unmet deps by walking up the tree object. +// No I/O +var fuSeen = [] +function findUnmet (obj) { + if (fuSeen.indexOf(obj) !== -1) return + fuSeen.push(obj) + //console.error("find unmet", obj.name, obj.parent && obj.parent.name) + var deps = obj.dependencies = obj.dependencies || {} + //console.error(deps) + Object.keys(deps) + .filter(function (d) { return typeof deps[d] === "string" }) + .forEach(function (d) { + //console.error("find unmet", obj.name, d, deps[d]) + var r = obj.parent + , found = null + while (r && !found && typeof deps[d] === "string") { + // if r is a valid choice, then use that. + found = r.dependencies[d] + if (!found && r.realName === d) found = r + + if (!found) { + r = r.link ? null : r.parent + continue + } + if ( typeof deps[d] === "string" + && !semver.satisfies(found.version, deps[d])) { + // the bad thing will happen + log.warn(obj.path + " requires "+d+"@'"+deps[d] + +"' but will load\n" + +found.path+",\nwhich is version "+found.version + ,"unmet dependency") + found.invalid = true + } + deps[d] = found + } + + }) + log.verbose([obj._id], "returning") + return obj +} + +function copy (obj) { + if (!obj || typeof obj !== 'object') return obj + if (Array.isArray(obj)) return obj.map(copy) + + var o = {} + for (var i in obj) o[i] = copy(obj[i]) + return o +} + +if (module === require.main) { + var util = require("util") + console.error("testing") + + var called = 0 + readInstalled(process.cwd(), function (er, map) { + console.error(called ++) + if (er) return console.error(er.stack || er.message) + cleanup(map) + console.error(util.inspect(map, true, 10, true)) + }) + + var seen = [] + function cleanup (map) { + if (seen.indexOf(map) !== -1) return + seen.push(map) + for (var i in map) switch (i) { + case "_id": + case "path": + case "extraneous": case "invalid": + case "dependencies": case "name": + continue + default: delete map[i] + } + var dep = map.dependencies +// delete map.dependencies + if (dep) { +// map.dependencies = dep + for (var i in dep) if (typeof dep[i] === "object") { + cleanup(dep[i]) + } + } + return map + } +} From c591efb352030d2e348b2fa63fba78e722c75a62 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Mon, 19 Mar 2012 17:16:49 +0100 Subject: [PATCH 16/40] Plugin list can now be reloaded 'live' --- src/node/hooks/express/adminplugins.js | 30 ++++--- src/package.json | 8 +- src/static/js/pluginfw/hooks.js | 8 +- src/static/js/pluginfw/installer.js | 103 ++++++++++++++++--------- src/static/js/pluginfw/plugins.js | 14 +++- src/templates/admin/plugins.html | 72 ++++++++++++----- 6 files changed, 152 insertions(+), 83 deletions(-) diff --git a/src/node/hooks/express/adminplugins.js b/src/node/hooks/express/adminplugins.js index 4dbd788fe..fa7e70771 100644 --- a/src/node/hooks/express/adminplugins.js +++ b/src/node/hooks/express/adminplugins.js @@ -1,6 +1,7 @@ var path = require('path'); var eejs = require('ep_etherpad-lite/node/eejs'); var installer = require('ep_etherpad-lite/static/js/pluginfw/installer'); +var plugins = require('ep_etherpad-lite/static/js/pluginfw/plugins'); exports.expressCreateServer = function (hook_name, args, cb) { args.app.get('/admin/plugins', function(req, res) { @@ -20,35 +21,30 @@ exports.expressCreateServer = function (hook_name, args, cb) { exports.socketio = function (hook_name, args, cb) { var io = args.io.of("/pluginfw/installer"); io.on('connection', function (socket) { + socket.on("load", function (query) { + socket.emit("installed-results", {results: plugins.plugins}); + }); + socket.on("search", function (query) { socket.emit("progress", {progress:0, message:'Fetching results...'}); - installer.search(query, function (er, data) { - if (er) { - socket.emit("progress", {progress:1, error:er}); - } else { - socket.emit("search-result", {results: data}); - socket.emit("progress", {progress:1, message:'Done.'}); - } + installer.search(query, function (progress) { + if (progress.results) + socket.emit("search-result", progress); + socket.emit("progress", progress); }); }); socket.on("install", function (plugin_name) { socket.emit("progress", {progress:0, message:'Downloading and installing ' + plugin_name + "..."}); - installer.install(plugin_name, function (er) { - if (er) - socket.emit("progress", {progress:1, error:er}); - else - socket.emit("progress", {progress:1, message:'Done.'}); + installer.install(plugin_name, function (progress) { + socket.emit("progress", progress); }); }); socket.on("uninstall", function (plugin_name) { socket.emit("progress", {progress:0, message:'Uninstalling ' + plugin_name + "..."}); - installer.uninstall(plugin_name, function (er) { - if (er) - socket.emit("progress", {progress:1, error:er}); - else - socket.emit("progress", {progress:1, message:'Done.'}); + installer.uninstall(plugin_name, function (progress) { + socket.emit("progress", progress); }); }); }); diff --git a/src/package.json b/src/package.json index 80378a0a3..34a9c01ca 100644 --- a/src/package.json +++ b/src/package.json @@ -23,8 +23,14 @@ "log4js" : "0.4.1", "jsdom-nocontextifiy" : "0.2.10", "async-stacktrace" : "0.0.2", + "npm" : "1.1", - "ejs" : "0.6.1" + "ejs" : "0.6.1", + "node.extend" : "1.0.0", + "graceful-fs" : "1.1.5", + "slide" : "1.1.3", + "semver" : "1.0.13" + }, "devDependencies": { "jshint" : "*" diff --git a/src/static/js/pluginfw/hooks.js b/src/static/js/pluginfw/hooks.js index 1b09a6e5d..0b96c8827 100644 --- a/src/static/js/pluginfw/hooks.js +++ b/src/static/js/pluginfw/hooks.js @@ -40,14 +40,14 @@ exports.callAll = function (hook_name, args) { } exports.aCallAll = function (hook_name, args, cb) { - if (plugins.hooks[hook_name] === undefined) cb([]); + if (plugins.hooks[hook_name] === undefined) return cb(null, []); async.map( plugins.hooks[hook_name], function (hook, cb) { hookCallWrapper(hook, hook_name, args, function (res) { cb(null, res); }); }, function (err, res) { - cb(exports.flatten(res)); + cb(null, exports.flatten(res)); } ); } @@ -58,8 +58,8 @@ exports.callFirst = function (hook_name, args) { } exports.aCallFirst = function (hook_name, args, cb) { - if (plugins.hooks[hook_name][0] === undefined) cb([]); - hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args, function (res) { cb(exports.flatten(res)); }); + if (plugins.hooks[hook_name][0] === undefined) return cb(null, []); + hookCallWrapper(plugins.hooks[hook_name][0], hook_name, args, function (res) { cb(null, exports.flatten(res)); }); } exports.callAllStr = function(hook_name, args, sep, pre, post) { diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 3ba7f4589..6cc043b77 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -3,43 +3,74 @@ var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks"); var npm = require("npm"); var registry = require("npm/lib/utils/npm-registry-client/index.js"); -exports.uninstall = function(plugin_name, cb) { +var withNpm = function (npmfn, cb) { npm.load({}, function (er) { - if (er) return cb(er) - npm.commands.uninstall([plugin_name], function (er) { - if (er) return cb(er); - hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er) { - cb(er); - }); - }) - }) -} - -exports.install = function(plugin_name, cb) { - npm.load({}, function (er) { - if (er) return cb(er) - npm.commands.install([plugin_name], function (er) { - if (er) return cb(er); - hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er) { - cb(er); - }); + if (er) return cb({progress:1, error:er}); + npm.on("log", function (message) { + cb({progress: 0.5, message:message.msg + ": " + message.pref}); + }); + npmfn(function (er, data) { + if (er) return cb({progress:1, error:er.code + ": " + er.path}); + if (!data) data = {}; + data.progress = 1; + data.message = "Done."; + cb(data); }); - }) -} - -exports.search = function(pattern, cb) { - npm.load({}, function (er) { - registry.get( - "/-/all", null, 600, false, true, - function (er, data) { - if (er) return cb(er); - var res = {}; - for (key in data) { - if (/*key.indexOf(plugins.prefix) == 0 &&*/ key.indexOf(pattern) != -1) - res[key] = data[key]; - } - cb(null, res); - } - ); }); } + +// All these functions call their callback multiple times with +// {progress:[0,1], message:STRING, error:object}. They will call it +// with progress = 1 at least once, and at all times will either +// message or error be present, not both. It can be called multiple +// times for all values of propgress except for 1. + +exports.uninstall = function(plugin_name, cb) { + withNpm( + function (cb) { + npm.commands.uninstall([plugin_name], function (er) { + if (er) return cb(er); + hooks.aCallAll("pluginUninstall", {plugin_name: plugin_name}, function (er, data) { + if (er) return cb(er); + plugins.update(cb); + }); + }); + }, + cb + ); +}; + +exports.install = function(plugin_name, cb) { + withNpm( + function (cb) { + npm.commands.install([plugin_name], function (er) { + if (er) return cb(er); + hooks.aCallAll("pluginInstall", {plugin_name: plugin_name}, function (er, data) { + if (er) return cb(er); + plugins.update(cb); + }); + }); + }, + cb + ); +}; + +exports.search = function(pattern, cb) { + withNpm( + function (cb) { + registry.get( + "/-/all", null, 600, false, true, + function (er, data) { + if (er) return cb(er); + var res = {}; + for (key in data) { + if (/*key.indexOf(plugins.prefix) == 0 &&*/ key.indexOf(pattern) != -1) + res[key] = data[key]; + } + cb(null, {results:res}); + } + ); + }, + cb + ); +}; diff --git a/src/static/js/pluginfw/plugins.js b/src/static/js/pluginfw/plugins.js index c5c219032..5017962bc 100644 --- a/src/static/js/pluginfw/plugins.js +++ b/src/static/js/pluginfw/plugins.js @@ -2,7 +2,7 @@ exports.isClient = typeof global != "object"; if (!exports.isClient) { var npm = require("npm/lib/npm.js"); - var readInstalled = require("npm/lib/utils/read-installed.js"); + var readInstalled = require("./read-installed.js"); var relativize = require("npm/lib/utils/relativize.js"); var readJson = require("npm/lib/utils/read-json.js"); var path = require("path"); @@ -10,6 +10,7 @@ if (!exports.isClient) { var fs = require("fs"); var tsort = require("./tsort"); var util = require("util"); + var extend = require("node.extend"); } exports.prefix = 'ep_'; @@ -112,14 +113,19 @@ exports.getPackages = function (cb) { function flatten(deps) { Object.keys(deps).forEach(function (name) { if (name.indexOf(exports.prefix) == 0) { - packages[name] = deps[name]; + packages[name] = extend({}, deps[name]); + // Delete anything that creates loops so that the plugin + // list can be sent as JSON to the web client + delete packages[name].dependencies; + delete packages[name].parent; } if (deps[name].dependencies !== undefined) flatten(deps[name].dependencies); - delete deps[name].dependencies; }); } - flatten([data]); + var tmp = {}; + tmp[data.name] = data; + flatten(tmp); cb(null, packages); }); } diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index 61e277d2f..a0822e87e 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -20,10 +20,10 @@ position: absolute; left: 50%; top: 50%; - width: 500px; - height: 400px; - margin-left: -250px; - margin-top: -200px; + width: 700px; + height: 500px; + margin-left: -350px; + margin-top: -250px; border: 3px solid #999999; background: #eeeeee; } @@ -33,6 +33,8 @@ border-bottom: 3px solid #999999; font-size: 24px; line-height: 24px; + height: 24px; + overflow: hidden; } .dialog .title .close { float: right; @@ -46,6 +48,7 @@ left: 10px; right: 10px; padding: 2px; + overflow: auto; } @@ -54,6 +57,8 @@ $(document).ready(function () { var socket = io.connect().of("/pluginfw/installer"); + var doUpdate = false; + function updateHandlers() { $("#progress.dialog .close").click(function () { $("#progress.dialog").hide(); @@ -64,14 +69,16 @@ socket.emit("search", $("#search-query")[0].value); }); - $("#do-install").click(function (e) { + $(".do-install").click(function (e) { var row = $(e.target).closest("tr"); + doUpdate = true; socket.emit("install", row.find(".name").html()); }); - $("#do-uninstall").click(function (e) { + $(".do-uninstall").click(function (e) { var row = $(e.target).closest("tr"); - socket.emit("install", row.find(".name").html()); + doUpdate = true; + socket.emit("uninstall", row.find(".name").html()); }); } @@ -80,17 +87,24 @@ socket.on('progress', function (data) { $("#progress.dialog .close").hide(); $("#progress.dialog").show(); - var message = data.message; + var message = "Unknown status"; + if (data.message) { + message = "" + data.message.toString() + ""; + } if (data.error) { - message = "
    " + data.error.toString() + "
    "; + message = "" + data.error.toString() + ""; } $("#progress.dialog .message").html(message); - $("#progress.dialog .history").append(message); + $("#progress.dialog .history").append("
    " + message + "
    "); if (data.progress >= 1) { if (data.error) { $("#progress.dialog .close").show(); } else { + if (doUpdate) { + doUpdate = false; + socket.emit("load"); + } $("#progress.dialog").hide(); } } @@ -109,6 +123,23 @@ } updateHandlers(); }); + + socket.on('installed-results', function (data) { + $("#installed-plugins *").remove(); + for (plugin_name in data.results) { + var plugin = data.results[plugin_name]; + var row = $("#installed-plugin-template").clone(); + + for (attr in plugin.package) { + row.find("." + attr).html(plugin.package[attr]); + } + $("#installed-plugins").append(row); + } + updateHandlers(); + }); + + socket.emit("load"); + }); @@ -131,17 +162,16 @@ - - <% for (var plugin_name in plugins) { %> - <% var plugin = plugins[plugin_name]; %> - - <%= plugin.package.name %> - <%= plugin.package.description %> - - - - - <% } %> + + + + + + + + + + From 37c0d279c1345a87413054ddb07838635110eb93 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Wed, 21 Mar 2012 19:27:06 +0100 Subject: [PATCH 17/40] Bugfix for when a block hook calls eejs.require recursively --- src/node/eejs/index.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/node/eejs/index.js b/src/node/eejs/index.js index a16ddc807..828ab103e 100644 --- a/src/node/eejs/index.js +++ b/src/node/eejs/index.js @@ -38,7 +38,7 @@ exports._init = function (b, recursive) { exports._exit = function (b, recursive) { exports.info.file_stack[exports.info.file_stack.length-1].inherit.forEach(function (item) { - exports.require(item.name, item.args); + exports._require(item.name, item.args); }); exports.info.buf = exports.info.buf_stack.pop(); } @@ -93,8 +93,6 @@ exports.inherit = function (name, args) { exports.require = function (name, args) { if (args == undefined) args = {}; - if (!exports.info) - exports.init(null); if ((name.indexOf("./") == 0 || name.indexOf("../") == 0) && exports.info.file_stack.length) { name = path.join(path.dirname(exports.info.file_stack[exports.info.file_stack.length-1].path), name); @@ -108,7 +106,9 @@ exports.require = function (name, args) { var res = ejs.render(template, args); exports.info.file_stack.pop(); - if (exports.info.buf) - exports.info.buf.push(res); return res; } + +exports._require = function (name, args) { + exports.info.buf.push(exports.require(name, args)); +} From 7304a9ef3f4650437602ce1b03dccd5ced3b9935 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Wed, 21 Mar 2012 19:27:43 +0100 Subject: [PATCH 18/40] Bugfix for misplaced hook call --- src/static/js/ace.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/static/js/ace.js b/src/static/js/ace.js index 1306dba01..473ecc1ba 100644 --- a/src/static/js/ace.js +++ b/src/static/js/ace.js @@ -233,13 +233,14 @@ require.setGlobalKeyPath("require");\n\ iframeHTML.push(doctype); iframeHTML.push(""); + hooks.callAll("aceInitInnerdocbodyHead", { + iframeHTML: iframeHTML + }); + // For compatability's sake transform in and out. for (var i = 0, ii = iframeHTML.length; i < ii; i++) { iframeHTML[i] = JSON.stringify(iframeHTML[i]); } - hooks.callAll("aceInitInnerdocbodyHead", { - iframeHTML: iframeHTML - }); for (var i = 0, ii = iframeHTML.length; i < ii; i++) { iframeHTML[i] = JSON.parse(iframeHTML[i]); } From 1ca12b24e658d27614f7b64dd8cb0e10fac2358a Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Wed, 21 Mar 2012 19:28:39 +0100 Subject: [PATCH 19/40] Bugfixes for plugin installer --- src/static/js/pluginfw/installer.js | 2 +- src/templates/admin/plugins.html | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/static/js/pluginfw/installer.js b/src/static/js/pluginfw/installer.js index 6cc043b77..127a95aa7 100644 --- a/src/static/js/pluginfw/installer.js +++ b/src/static/js/pluginfw/installer.js @@ -64,7 +64,7 @@ exports.search = function(pattern, cb) { if (er) return cb(er); var res = {}; for (key in data) { - if (/*key.indexOf(plugins.prefix) == 0 &&*/ key.indexOf(pattern) != -1) + if (key.indexOf(plugins.prefix) == 0 && key.indexOf(pattern) != -1) res[key] = data[key]; } cb(null, {results:res}); diff --git a/src/templates/admin/plugins.html b/src/templates/admin/plugins.html index a0822e87e..7dcb6fa3e 100644 --- a/src/templates/admin/plugins.html +++ b/src/templates/admin/plugins.html @@ -60,22 +60,22 @@ var doUpdate = false; function updateHandlers() { - $("#progress.dialog .close").click(function () { + $("#progress.dialog .close").unbind('click').click(function () { $("#progress.dialog").hide(); }); - $("#do-search").click(function () { + $("#do-search").unbind('click').click(function () { if ($("#search-query")[0].value != "") socket.emit("search", $("#search-query")[0].value); }); - $(".do-install").click(function (e) { + $(".do-install").unbind('click').click(function (e) { var row = $(e.target).closest("tr"); doUpdate = true; socket.emit("install", row.find(".name").html()); }); - $(".do-uninstall").click(function (e) { + $(".do-uninstall").unbind('click').click(function (e) { var row = $(e.target).closest("tr"); doUpdate = true; socket.emit("uninstall", row.find(".name").html()); From 50474dff1f1fd9c6a573c723a928f297651a5975 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Wed, 21 Mar 2012 19:29:06 +0100 Subject: [PATCH 20/40] Some new blocks to allow plugins to add scripts, styles and modals --- src/templates/pad.html | 138 +++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 67 deletions(-) diff --git a/src/templates/pad.html b/src/templates/pad.html index e34f572b7..42bf483bc 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -7,9 +7,11 @@ - - - + <% e.begin_block("styles"); %> + + + + <% e.end_block(); %> @@ -215,74 +217,76 @@
    -
    -
    -
    Connecting...
    -
    Reestablishing connection...
    -
    -

    Disconnected.

    -

    Opened in another window.

    -

    No Authorization.

    -
    -

    We're having trouble talking to the EtherPad lite synchronization server. You may be connecting through an incompatible firewall or proxy server.

    -
    -
    -

    We were unable to connect to the EtherPad lite synchronization server. This may be due to an incompatibility with your web browser or internet connection.

    -
    -
    -

    You seem to have opened this pad in another browser window. If you'd like to use this window instead, you can reconnect.

    -
    -
    -

    Lost connection with the EtherPad lite synchronization server. This may be due to a loss of network connectivity.

    -
    -
    -

    Server not responding. This may be due to network connectivity issues or high load on the server.

    -
    -
    -

    Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.

    -
    -
    -

    This pad was deleted.

    -
    -
    -

    If this continues to happen, please let us know

    -
    -
    - + <% e.begin_block("modals"); %> +
    +
    +
    Connecting...
    +
    Reestablishing connection...
    +
    +

    Disconnected.

    +

    Opened in another window.

    +

    No Authorization.

    +
    +

    We're having trouble talking to the EtherPad lite synchronization server. You may be connecting through an incompatible firewall or proxy server.

    +
    +
    +

    We were unable to connect to the EtherPad lite synchronization server. This may be due to an incompatibility with your web browser or internet connection.

    +
    +
    +

    You seem to have opened this pad in another browser window. If you'd like to use this window instead, you can reconnect.

    +
    +
    +

    Lost connection with the EtherPad lite synchronization server. This may be due to a loss of network connectivity.

    +
    +
    +

    Server not responding. This may be due to network connectivity issues or high load on the server.

    +
    +
    +

    Your browser's credentials or permissions have changed while viewing this pad. Try reconnecting.

    +
    +
    +

    This pad was deleted.

    +
    +
    +

    If this continues to happen, please let us know

    +
    +
    + +
    +
    - -
    - + <% e.end_block(); %>
    + + <% e.begin_block("scripts"); %> + + + + + - - - - - + /* TODO: These globals shouldn't exist. */ + pad = require('ep_etherpad-lite/static/js/pad').pad; + chat = require('ep_etherpad-lite/static/js/chat').chat; + padeditbar = require('ep_etherpad-lite/static/js/pad_editbar').padeditbar; + padimpexp = require('ep_etherpad-lite/static/js/pad_impexp').padimpexp; + }()); + + <% e.end_block(); %> From d21cc1912ccc3d63ee234aa893f6357e50e098f2 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Wed, 21 Mar 2012 19:29:59 +0100 Subject: [PATCH 21/40] Better error message for broken JSON --- src/static/js/json2.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/static/js/json2.js b/src/static/js/json2.js index 663f932cd..d72b4be98 100644 --- a/src/static/js/json2.js +++ b/src/static/js/json2.js @@ -465,7 +465,7 @@ if (!JSON) } // If the text is not JSON parseable, then a SyntaxError is thrown. - throw new SyntaxError('JSON.parse'); + throw new SyntaxError('JSON.parse: ' + text); }; } }()); From 5261ba5a69c2df7e927c910451360d703bbe70d0 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Thu, 22 Mar 2012 18:34:08 +0100 Subject: [PATCH 22/40] Forward-ported some bugfixes from olad etherpad --- src/static/js/ace2_inner.js | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/static/js/ace2_inner.js b/src/static/js/ace2_inner.js index 66f19faf1..65a398b56 100644 --- a/src/static/js/ace2_inner.js +++ b/src/static/js/ace2_inner.js @@ -846,7 +846,7 @@ function Ace2Inner(){ var cmdArgs = Array.prototype.slice.call(arguments, 1); if (CMDS[cmd]) { - inCallStack(cmd, function() + inCallStackIfNecessary(cmd, function() { fastIncorp(9); CMDS[cmd].apply(CMDS, cmdArgs); @@ -856,7 +856,7 @@ function Ace2Inner(){ function replaceRange(start, end, text) { - inCallStack('replaceRange', function() + inCallStackIfNecessary('replaceRange', function() { fastIncorp(9); performDocumentReplaceRange(start, end, text); @@ -1159,7 +1159,7 @@ function Ace2Inner(){ return; } - inCallStack("idleWorkTimer", function() + inCallStackIfNecessary("idleWorkTimer", function() { var isTimeUp = newTimeLimit(250); @@ -2335,6 +2335,7 @@ function Ace2Inner(){ var cs = builder.toString(); performDocumentApplyChangeset(cs); } + editorInfo.ace_performDocumentApplyAttributesToRange = performDocumentApplyAttributesToRange; function buildKeepToStartOfRange(builder, start) { @@ -2860,6 +2861,7 @@ function Ace2Inner(){ currentCallStack.selectionAffected = true; } } + editorInfo.ace_performSelectionChange = performSelectionChange; // Change the abstract representation of the document to have a different selection. // Should not rely on the line representation. Should not affect the DOM. @@ -3287,7 +3289,7 @@ function Ace2Inner(){ function handleClick(evt) { - inCallStack("handleClick", function() + inCallStackIfNecessary("handleClick", function() { idleWorkTimer.atMost(200); }); @@ -3609,7 +3611,7 @@ function Ace2Inner(){ var stopped = false; - inCallStack("handleKeyEvent", function() + inCallStackIfNecessary("handleKeyEvent", function() { if (type == "keypress" || (isTypeForSpecialKey && keyCode == 13 /*return*/ )) @@ -4698,7 +4700,7 @@ function Ace2Inner(){ } // click below the body - inCallStack("handleOuterClick", function() + inCallStackIfNecessary("handleOuterClick", function() { // put caret at bottom of doc fastIncorp(11); @@ -4771,7 +4773,7 @@ function Ace2Inner(){ function setup() { doc = document; // defined as a var in scope outside - inCallStack("setup", function() + inCallStackIfNecessary("setup", function() { var body = doc.getElementById("innerdocbody"); root = body; // defined as a var in scope outside From b8a4333d1bf699725ae5df578a90417e045cd168 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Thu, 22 Mar 2012 18:34:38 +0100 Subject: [PATCH 23/40] Exposed require to ejs pages! --- src/node/eejs/index.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/node/eejs/index.js b/src/node/eejs/index.js index 828ab103e..90c69e595 100644 --- a/src/node/eejs/index.js +++ b/src/node/eejs/index.js @@ -100,6 +100,7 @@ exports.require = function (name, args) { var ejspath = require.resolve(name) args.e = exports; + args.require = require; var template = '<% e._init(buf); %>' + fs.readFileSync(ejspath).toString() + '<% e._exit(); %>'; exports.info.file_stack.push({path: ejspath, inherit: []}); From 22eaa6ffc012e83438d54e6fb5b023a7952246a7 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Thu, 22 Mar 2012 18:35:06 +0100 Subject: [PATCH 24/40] Made it possible to disable minification again --- src/templates/pad.html | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/templates/pad.html b/src/templates/pad.html index 42bf483bc..901fd4334 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -1,3 +1,6 @@ +<% + var settings = require("ep_etherpad-lite/node/utils/Settings"); +%> @@ -268,13 +271,20 @@ - + <% if (settings.minify) { %> + + <% } %> '); hooks.callAll("aceInitInnerdocbodyHead", { iframeHTML: iframeHTML From b74447aa3cc550eb2e20034b1a56ea47c20c7b09 Mon Sep 17 00:00:00 2001 From: Egil Moeller Date: Wed, 28 Mar 2012 19:20:07 +0200 Subject: [PATCH 29/40] Styling generalization - use classes instead of id:s where applicable --- src/static/css/pad.css | 52 ++++++++++++++++++++++-------------------- src/templates/pad.html | 6 ++--- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/src/static/css/pad.css b/src/static/css/pad.css index 969d00276..13f4bc933 100644 --- a/src/static/css/pad.css +++ b/src/static/css/pad.css @@ -24,7 +24,7 @@ a img } /* menu */ -#editbar ul +.toolbar ul { position: relative; list-style: none; @@ -35,18 +35,20 @@ a img } -#editbar +.toolbar { background: #f7f7f7; background: linear-gradient(#f7f7f7, #f1f1f1 80%); border-bottom: 1px solid #ccc; - height: 32px; overflow: hidden; padding-top: 3px; - width: 100%; + position: absolute; + left: 0; + right: 0; + height: 32px; } -#editbar ul li +.toolbar ul li { background: #fff; background: linear-gradient(#fff, #f0f0f0); @@ -61,52 +63,52 @@ a img width: 18px; } -#editbar ul li a +.toolbar ul li a { text-decoration: none; color: #ccc; position: absolute; } -#editbar ul li a span +.toolbar ul li a span { position: relative; top:-2px } -#editbar ul li:hover { +.toolbar ul li:hover { background: #fff; background: linear-gradient(#f4f4f4, #e4e4e4); } -#editbar ul li:active { +.toolbar ul li:active { background: #eee; background: linear-gradient(#ddd, #fff); box-shadow: 0 0 8px rgba(0,0,0,.1) inset; } -#editbar ul li.separator +.toolbar ul li.separator { border: inherit; background: inherit; visibility:hidden; width: 0px; } -#editbar ul li a +.toolbar ul li a { display: block; } -#editbar ul li a img +.toolbar ul li a img { padding: 1px; } -#editbar ul +.toolbar ul { float: left; } -#editbar ul#menu_right +.toolbar ul.menu_right { float: right; } @@ -320,7 +322,7 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; } z-index: 10; } -#editbarsavetable +.toolbarsavetable { position:absolute; top: 6px; @@ -328,7 +330,7 @@ a#hidetopmsg { position: absolute; right: 5px; bottom: 5px; } height: 24px; } -#editbarsavetable td, #editbartable td +.toolbarsavetable td, .toolbartable td { white-space: nowrap; } @@ -688,14 +690,14 @@ a#topbarmaximize { background: url(static/img/maximize_maximized.png); } -#editbarinner h1 { +.toolbarinner h1 { line-height: 29px; font-size: 16px; padding-left: 6pt; margin-top: 0; } -#editbarinner h1 a { +.toolbarinner h1 a { font-size: 12px; } @@ -1173,13 +1175,13 @@ input[type=checkbox] { } @media screen and (max-width: 600px) { - #editbar ul li { + .toolbar ul li { padding: 4px 1px; } } @media only screen and (min-device-width: 320px) and (max-device-width: 720px) { - #editbar ul li { + .toolbar ul li { padding: 4px 3px; } #users { @@ -1194,7 +1196,7 @@ input[type=checkbox] { #editorcontainer { margin-bottom: 33px; } - #editbar ul#menu_right { + .toolbar ul.menu_right { background: #f7f7f7; background: linear-gradient(#f7f7f7, #f1f1f1 80%); width: 100%; @@ -1204,7 +1206,7 @@ input[type=checkbox] { bottom: 0; border-top: 1px solid #ccc; } - #editbar ul#menu_right li:last-child { + .toolbar ul.menu_right li:last-child { height: 24px; border-radius: 0; margin-top: 0; @@ -1226,7 +1228,7 @@ input[type=checkbox] { border-top-right-radius: 0; border-right: none; } - #editbar ul li a span { + .toolbar ul li a span { top: -3px; } #usericonback { @@ -1235,10 +1237,10 @@ input[type=checkbox] { #qrcode { display: none; } - #editbar ul#menu_right li:not(:last-child) { + .toolbar ul.menu_right li:not(:last-child) { display: block; } - #editbar ul#menu_right > li { + .toolbar ul.menu_right > li { background: none; border: none; margin-top: 4px; diff --git a/src/templates/pad.html b/src/templates/pad.html index 901fd4334..765eec0cc 100644 --- a/src/templates/pad.html +++ b/src/templates/pad.html @@ -18,8 +18,8 @@ -
    -