Merge branch 'pr/1579' into toolbar-test

Conflicts:
	settings.json.template
	src/static/js/pad_editbar.js
This commit is contained in:
Marcel Klehr 2014-03-16 13:30:22 +01:00
commit a369347d86
6 changed files with 438 additions and 181 deletions

View File

@ -103,6 +103,22 @@
// restrict socket.io transport methods
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
/* The toolbar buttons configuration.
"toolbar": {
"left": [
["bold", "italic", "underline", "strikethrough"],
["orderedlist", "unorderedlist", "indent", "outdent"],
["undo", "redo"],
["clearauthorship"]
],
"right": [
["importexport", "timeslider", "savedrevision"],
["settings", "embed"],
["showusers"]
]
},
*/
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
"loglevel": "INFO",

View File

@ -1,5 +1,7 @@
var path = require('path');
var eejs = require('ep_etherpad-lite/node/eejs');
var toolbar = require("ep_etherpad-lite/node/utils/toolbar");
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
exports.expressCreateServer = function (hook_name, args, cb) {
// expose current stats
@ -30,8 +32,15 @@ exports.expressCreateServer = function (hook_name, args, cb) {
//serve pad.html under /p
args.app.get('/p/:pad', function(req, res, next)
{
res.send(eejs.require("ep_etherpad-lite/templates/pad.html", {req: req}));
{
hooks.callAll("padInitToolbar", {
toolbar: toolbar
});
res.send(eejs.require("ep_etherpad-lite/templates/pad.html", {
req: req,
toolbar: toolbar
}));
});
//serve timeslider.html under /p/$padname/timeslider

View File

@ -79,6 +79,23 @@ exports.dbSettings = { "filename" : path.join(exports.root, "dirty.db") };
*/
exports.defaultPadText = "Welcome to Etherpad!\n\nThis pad text is synchronized as you type, so that everyone viewing this page sees the same text. This allows you to collaborate seamlessly on documents!\n\nEtherpad on Github: http:\/\/j.mp/ep-lite\n";
/**
* The toolbar buttons and order.
*/
exports.toolbar = {
left: [
["bold", "italic", "underline", "strikethrough"],
["orderedlist", "unorderedlist", "indent", "outdent"],
["undo", "redo"],
["clearauthorship"]
],
right: [
["importexport", "timeslider", "savedrevision"],
["settings", "embed"],
["showusers"]
]
}
/**
* A flag that requires any user to have a valid session (via the api) before accessing a pad
*/

227
src/node/utils/toolbar.js Normal file
View File

@ -0,0 +1,227 @@
/**
* The Toolbar Module creates and renders the toolbars and buttons
*/
var _ = require("underscore")
, tagAttributes
, tag
, defaultButtons
, Button
, ButtonsGroup
, Separator
, defaultButtonAttributes;
defaultButtonAttributes = function (name, overrides) {
return {
key: name,
localizationId: "pad.toolbar." + name + ".title",
icon: "buttonicon-" + name
};
};
tag = function (name, attributes, contents) {
var aStr = tagAttributes(attributes);
if (_.isString(contents) && contents.length > 0) {
return '<' + name + aStr + '>' + contents + '</' + name + '>';
}
else {
return '<' + name + aStr + '></' + name + '>';
}
};
tagAttributes = function (attributes) {
attributes = _.reduce(attributes || {}, function (o, val, name) {
if (!_.isUndefined(val)) {
o[name] = val;
}
return o;
}, {});
return " " + _.map(attributes, function (val, name) {
return "" + name + '="' + _.escape(val) + '"';
}).join(" ");
};
ButtonsGroup = function () {
this.buttons = [];
};
ButtonsGroup.fromArray = function (array) {
var btnGroup = new this;
_.each(array, function (btnName) {
btnGroup.addButton(Button.load(btnName));
});
return btnGroup;
};
ButtonsGroup.prototype.addButton = function (button) {
this.buttons.push(button);
return this;
};
ButtonsGroup.prototype.render = function () {
if (this.buttons.length == 1) {
this.buttons[0].grouping = "";
}
else {
_.first(this.buttons).grouping = "grouped-left";
_.last(this.buttons).grouping = "grouped-right";
_.each(this.buttons.slice(1, -1), function (btn) {
btn.grouping = "grouped-middle"
});
}
return _.map(this.buttons, function (btn) {
return btn.render();
}).join("\n");
};
Button = function (attributes) {
this.attributes = attributes;
};
Button.load = function (btnName) {
var button = module.exports.availableButtons[btnName];
if (button.constructor === Button || button.constructor === SelectButton) {
return button;
}
else {
return new Button(button);
}
};
_.extend(Button.prototype, {
grouping: "",
render: function () {
var liAttributes = {
"data-type": "button",
"data-key": this.attributes.key,
};
return tag("li", liAttributes,
tag("a", { "class": this.grouping, "data-l10n-id": this.attributes.localizationId },
tag("span", { "class": "buttonicon " + this.attributes.icon })
)
);
}
});
SelectButton = function (attributes) {
this.attributes = attributes;
this.options = [];
};
_.extend(SelectButton.prototype, Button.prototype, {
addOption: function (value, text, attributes) {
this.options.push({
value: value,
text: text,
attributes: attributes
});
return this;
},
select: function (attributes) {
var self = this
, options = [];
_.each(this.options, function (opt) {
var a = _.extend({
value: opt.value
}, opt.attributes);
options.push( tag("option", a, opt.text) );
});
return tag("select", attributes, options.join(""));
},
render: function () {
var attributes = {
id: this.attributes.id,
"data-key": this.attributes.command,
"data-type": "select"
};
return tag("li", attributes,
this.select({ id: this.attributes.selectId })
);
}
});
Separator = function () {};
Separator.prototype.render = function () {
return tag("li", { "class": "separator" });
};
module.exports = {
availableButtons: {
bold: defaultButtonAttributes("bold"),
italic: defaultButtonAttributes("italic"),
underline: defaultButtonAttributes("underline"),
strikethrough: defaultButtonAttributes("strikethrough"),
orderedlist: {
key: "insertorderedlist",
localizationId: "pad.toolbar.ol.title",
icon: "buttonicon-insertorderedlist"
},
unorderedlist: {
key: "insertunorderedlist",
localizationId: "pad.toolbar.ul.title",
icon: "buttonicon-insertunorderedlist"
},
indent: defaultButtonAttributes("indent"),
outdent: {
key: "outdent",
localizationId: "pad.toolbar.unindent.title",
icon: "buttonicon-outdent"
},
undo: defaultButtonAttributes("undo"),
redo: defaultButtonAttributes("redo"),
clearauthorship: {
key: "clearauthorship",
localizationId: "pad.toolbar.clearAuthorship.title",
icon: "buttonicon-clearauthorship"
},
importexport: {
key: "import_export",
localizationId: "pad.toolbar.import_export.title",
icon: "buttonicon-import_export"
},
timeslider: {
key: "showTimeSlider",
localizationId: "pad.toolbar.timeslider.title",
icon: "buttonicon-history"
},
savedrevision: defaultButtonAttributes("savedRevision"),
settings: defaultButtonAttributes("settings"),
embed: defaultButtonAttributes("embed"),
showusers: defaultButtonAttributes("showusers")
},
registerButton: function (buttonName, buttonInfo) {
this.availableButtons[buttonName] = buttonInfo;
},
button: function (attributes) {
return new Button(attributes);
},
separator: function () {
return (new Separator).render();
},
selectButton: function (attributes) {
return new SelectButton(attributes);
},
menu: function (buttons) {
var groups = _.map(buttons, function (group) {
return ButtonsGroup.fromArray(group).render();
});
return groups.join(this.separator());
}
};

View File

@ -1,5 +1,5 @@
/**
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This code is mostly from the old Etherpad. Please help us to comment this code.
* This helps other people to understand this code better and helps them to improve it.
* TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
*/
@ -20,18 +20,60 @@
* limitations under the License.
*/
var hooks = require('./pluginfw/hooks');
var padutils = require('./pad_utils').padutils;
var padeditor = require('./pad_editor').padeditor;
var padsavedrevs = require('./pad_savedrevs');
function indexOf(array, value) {
for (var i = 0, ii = array.length; i < ii; i++) {
if (array[i] == value) {
return i;
}
var ToolbarItem = function (element) {
this.$el = element;
};
ToolbarItem.prototype.getCommand = function () {
return this.$el.attr("data-key");
};
ToolbarItem.prototype.getValue = function () {
if (this.isSelect()) {
return this.$el.find("select").val();
}
return -1;
}
};
ToolbarItem.prototype.setValue = function (val) {
if (this.isSelect()) {
return this.$el.find("select").val(val);
}
};
ToolbarItem.prototype.getType = function () {
return this.$el.attr("data-type");
};
ToolbarItem.prototype.isSelect = function () {
return this.getType() == "select";
};
ToolbarItem.prototype.isButton = function () {
return this.getType() == "button";
};
ToolbarItem.prototype.bind = function (callback) {
var self = this;
if (self.isButton()) {
self.$el.click(function (event) {
callback(self.getCommand(), self);
event.preventDefault();
});
}
else if (self.isSelect()) {
self.$el.find("select").change(function () {
callback(self.getCommand(), self);
});
}
};
var padeditbar = (function()
{
@ -95,17 +137,22 @@ var padeditbar = (function()
}());
var self = {
init: function()
{
init: function() {
var self = this;
$("#editbar .editbarbutton").attr("unselectable", "on"); // for IE
$("#editbar").removeClass("disabledtoolbar").addClass("enabledtoolbar");
$("#editbar [data-key]").each(function (i, e) {
$(e).click(function (event) {
self.toolbarClick($(e).attr('data-key'));
event.preventDefault();
$("#editbar [data-key]").each(function () {
(new ToolbarItem($(this))).bind(function (command, item) {
self.triggerCommand(command, item);
});
});
registerDefaultCommands(self);
hooks.callAll("postToolbarInit", {
toolbar: self,
ace: padeditor.ace
});
},
isEnabled: function()
{
@ -116,77 +163,34 @@ var padeditbar = (function()
{
$("#editbar").addClass('disabledtoolbar').removeClass("enabledtoolbar");
},
toolbarClick: function(cmd)
{
if (self.isEnabled())
{
if(cmd == "showusers")
{
self.toggleDropDown("users");
}
else if (cmd == 'settings')
{
self.toggleDropDown("settings");
}
else if (cmd == 'connectivity')
{
self.toggleDropDown("connectivity");
}
else if (cmd == 'embed')
{
self.setEmbedLinks();
$('#linkinput').focus().select();
self.toggleDropDown("embed");
}
else if (cmd == 'import_export')
{
self.toggleDropDown("importexport");
}
else if (cmd == 'savedRevision')
{
padsavedrevs.saveNow();
}
else
{
padeditor.ace.callWithAce(function(ace)
{
if (cmd == 'bold' || cmd == 'italic' || cmd == 'underline' || cmd == 'strikethrough') ace.ace_toggleAttributeOnSelection(cmd);
else if (cmd == 'undo' || cmd == 'redo') ace.ace_doUndoRedo(cmd);
else if (cmd == 'insertunorderedlist') ace.ace_doInsertUnorderedList();
else if (cmd == 'insertorderedlist') ace.ace_doInsertOrderedList();
else if (cmd == 'indent')
{
ace.ace_doIndentOutdent(false);
}
else if (cmd == 'outdent')
{
ace.ace_doIndentOutdent(true);
}
else if (cmd == 'clearauthorship')
{
if ((!(ace.ace_getRep().selStart && ace.ace_getRep().selEnd)) || ace.ace_isCaret())
{
if (window.confirm(html10n.get("pad.editbar.clearcolors")))
{
ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [
['author', '']
]);
}
}
else
{
ace.ace_setAttributeOnSelection('author', '');
}
}
}, cmd, true);
}
commands: {},
registerCommand: function (cmd, callback) {
this.commands[cmd] = callback;
return this;
},
registerDropdownCommand: function (cmd, dropdown) {
dropdown = dropdown || cmd;
this.registerCommand(cmd, function () {
self.toggleDropDown(dropdown);
});
},
registerAceCommand: function (cmd, callback) {
this.registerCommand(cmd, function (cmd, ace) {
ace.callWithAce(function (ace) {
callback(cmd, ace);
}, cmd, true);
});
},
triggerCommand: function (cmd, item) {
if (self.isEnabled() && this.commands[cmd]) {
this.commands[cmd](cmd, padeditor.ace, item);
}
if(padeditor.ace) padeditor.ace.focus();
},
toggleDropDown: function(moduleName, cb)
{
var modules = ["settings", "connectivity", "importexport", "embed", "users"];
// hide all modules and remove highlighting of all buttons
if(moduleName == "none")
{
@ -196,9 +200,9 @@ var padeditbar = (function()
//skip the userlist
if(modules[i] == "users")
continue;
var module = $("#" + modules[i]);
if(module.css('display') != "none")
{
$("#" + modules[i] + "link").removeClass("selected");
@ -208,14 +212,14 @@ var padeditbar = (function()
}
if(!returned && cb) return cb();
}
else
else
{
// hide all modules that are not selected and remove highlighting
// respectively add highlighting to the corresponding button
for(var i=0;i<modules.length;i++)
{
var module = $("#" + modules[i]);
if(module.css('display') != "none")
{
$("#" + modules[i] + "link").removeClass("selected");
@ -257,6 +261,76 @@ var padeditbar = (function()
}
}
};
function aceAttributeCommand(cmd, ace) {
ace.ace_toggleAttributeOnSelection(cmd);
}
function registerDefaultCommands(toolbar) {
toolbar.registerDropdownCommand("showusers", "users");
toolbar.registerDropdownCommand("settings");
toolbar.registerDropdownCommand("connectivity");
toolbar.registerDropdownCommand("import_export", "importexport");
toolbar.registerCommand("embed", function () {
toolbar.setEmbedLinks();
$('#linkinput').focus().select();
toolbar.toggleDropDown("embed");
});
toolbar.registerCommand("savedRevision", function () {
padsavedrevs.saveNow();
});
toolbar.registerCommand("showTimeSlider", function () {
document.location = document.location + "/timeslider";
});
toolbar.registerAceCommand("bold", aceAttributeCommand);
toolbar.registerAceCommand("italic", aceAttributeCommand);
toolbar.registerAceCommand("underline", aceAttributeCommand);
toolbar.registerAceCommand("strikethrough", aceAttributeCommand);
toolbar.registerAceCommand("undo", function (cmd, ace) {
ace.ace_doUndoRedo(cmd);
});
toolbar.registerAceCommand("redo", function (cmd) {
ace.ace_doUndoRedo(cmd);
});
toolbar.registerAceCommand("insertunorderedlist", function (cmd, ace) {
ace.ace_doInsertUnorderedList();
});
toolbar.registerAceCommand("insertorderedlist", function (cmd, ace) {
ace.ace_doInsertOrderedList();
});
toolbar.registerAceCommand("indent", function (cmd, ace) {
if (!ace.ace_doIndentOutdent(false)) {
ace.ace_doInsertUnorderedList();
}
});
toolbar.registerAceCommand("outdent", function (cmd, ace) {
ace.ace_doIndentOutdent(true);
});
toolbar.registerAceCommand("clearauthorship", function (cmd, ace) {
if ((!(ace.ace_getRep().selStart && ace.ace_getRep().selEnd)) || ace.ace_isCaret()) {
if (window.confirm(html10n.get("pad.editbar.clearcolors"))) {
ace.ace_performDocumentApplyAttributesToCharRange(0, ace.ace_getRep().alltext.length, [
['author', '']
]);
}
}
else {
ace.ace_setAttributeOnSelection('author', '');
}
});
}
return self;
}());

View File

@ -1,6 +1,8 @@
<%
var settings = require("ep_etherpad-lite/node/utils/Settings")
, hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks')
, langs = require("ep_etherpad-lite/node/hooks/i18n").availableLangs
;
%>
<!doctype html>
<% e.begin_block("htmlHead"); %>
@ -54,103 +56,15 @@
<div id="overlay">
<div id="overlay-inner"></div>
</div>
<ul class="menu_left">
<% e.begin_block("editbarMenuLeft"); %>
<li class="acl-write" id="bold" data-key="bold">
<a class="grouped-left" data-l10n-id="pad.toolbar.bold.title">
<span class="buttonicon buttonicon-bold"></span>
</a>
</li>
<li class="acl-write" id="italic" data-key="italic">
<a class="grouped-middle" data-l10n-id="pad.toolbar.italic.title">
<span class="buttonicon buttonicon-italic"></span>
</a>
</li>
<li class="acl-write" id="underline" data-key="underline">
<a class="grouped-middle" data-l10n-id="pad.toolbar.underline.title">
<span class="buttonicon buttonicon-underline"></span>
</a>
</li>
<li class="acl-write" id="strikethrough" data-key="strikethrough">
<a class="grouped-right" data-l10n-id="pad.toolbar.strikethrough.title">
<span class="buttonicon buttonicon-strikethrough"></span>
</a>
</li>
<li class="acl-write separator"></li>
<li class="acl-write" id="oderedlist" data-key="insertorderedlist">
<a class="grouped-left" data-l10n-id="pad.toolbar.ol.title">
<span class="buttonicon buttonicon-insertorderedlist"></span>
</a>
</li>
<li class="acl-write" id="unoderedlist" data-key="insertunorderedlist">
<a class="grouped-middle" data-l10n-id="pad.toolbar.ul.title">
<span class="buttonicon buttonicon-insertunorderedlist"></span>
</a>
</li>
<li class="acl-write" id="indent" data-key="indent">
<a class="grouped-middle" data-l10n-id="pad.toolbar.indent.title">
<span class="buttonicon buttonicon-indent"></span>
</a>
</li>
<li class="acl-write" id="outdent" data-key="outdent">
<a class="grouped-right" data-l10n-id="pad.toolbar.unindent.title">
<span class="buttonicon buttonicon-outdent"></span>
</a>
</li>
<li class="acl-write separator"></li>
<li class="acl-write" id="undo" data-key="undo">
<a class="grouped-left" data-l10n-id="pad.toolbar.undo.title">
<span class="buttonicon buttonicon-undo"></span>
</a>
</li>
<li class="acl-write" id="redo" data-key="redo">
<a class="grouped-right" data-l10n-id="pad.toolbar.redo.title">
<span class="buttonicon buttonicon-redo"></span>
</a>
</li>
<li class="acl-write separator"></li>
<li class="acl-write" id="clearAuthorship" data-key="clearauthorship">
<a data-l10n-id="pad.toolbar.clearAuthorship.title">
<span class="buttonicon buttonicon-clearauthorship"></span>
</a>
</li>
<%- toolbar.menu(settings.toolbar.left) %>
<% e.end_block(); %>
</ul>
<ul class="menu_right">
<% e.begin_block("editbarMenuRight"); %>
<li data-key="import_export">
<a class="grouped-left" id="importexportlink" data-l10n-id="pad.toolbar.import_export.title">
<span class="buttonicon buttonicon-import_export"></span>
</a>
</li>
<li onClick="document.location = document.location.pathname+ '/timeslider'">
<a id="timesliderlink" class="grouped-middle" data-l10n-id="pad.toolbar.timeslider.title">
<span class="buttonicon buttonicon-history"></span>
</a>
</li>
<li class="acl-write" data-key="savedRevision">
<a class="grouped-right" id="revisionlink" data-l10n-id="pad.toolbar.savedRevision.title">
<span class="buttonicon buttonicon-savedRevision"></span>
</a>
</li>
<li class="acl-write separator"></li>
<li class="acl-write" data-key="settings">
<a class="grouped-left" id="settingslink" data-l10n-id="pad.toolbar.settings.title">
<span class="buttonicon buttonicon-settings"></span>
</a>
</li>
<li data-key="embed">
<a class="grouped-right" id="embedlink" data-l10n-id="pad.toolbar.embed.title">
<span class="grouped-right buttonicon buttonicon-embed"></span>
</a>
</li>
<li class="separator"></li>
<li id="usericon" data-key="showusers">
<a data-l10n-id="pad.toolbar.showusers.title">
<span class="buttonicon buttonicon-showusers"></span>
<span id="online_count">1</span>
</a>
</li>
<%- toolbar.menu(settings.toolbar.right) %>
<% e.end_block(); %>
</ul>
</div>
@ -295,7 +209,7 @@
<% e.end_block(); %>
</div>
</div>
<div id="connectivity" class="popup">
<% e.begin_block("modals"); %>
<div class="connected visible">
@ -411,7 +325,7 @@
<% e.begin_block("scripts"); %>
<script type="text/javascript">
(function() {
// Display errors on page load to the user
// Display errors on page load to the user
// (Gets overridden by padutils.setupGlobalExceptionHandler)
var originalHandler = window.onerror;
window.onerror = function(msg, url, line) {
@ -424,7 +338,7 @@
};
})();
</script>
<script type="text/javascript" src="../static/js/require-kernel.js"></script>
<script type="text/javascript" src="../socket.io/socket.io.js"></script>
@ -439,10 +353,10 @@
var clientVars = {};
(function () {
var pathComponents = location.pathname.split('/');
// Strip 'p' and the padname from the pathname and set as baseURL
var baseURL = pathComponents.slice(0,pathComponents.length-2).join('/') + '/';
require.setRootURI(baseURL + "javascripts/src");
require.setLibraryURI(baseURL + "javascripts/lib");
require.setGlobalKeyPath("require");