commit
b95395a130
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -1,3 +1,14 @@
|
|||
# 1.5.3
|
||||
* NEW: Accessibility support for Screen readers, includes new fonts and keyboard shortcuts
|
||||
* NEW: API endpoint for Append Chat Message and Chat Backend Tests
|
||||
* NEW: Error messages displayed on load are included in Default Pad Text (can be supressed)
|
||||
* NEW: Content Collector can handle key values
|
||||
* NEW: getAttributesOnPosition Method
|
||||
* FIX: Firefox keeps attributes (bold etc) on cut/copy -> paste
|
||||
* Fix: showControls=false now works
|
||||
* Fix: Cut and Paste works...
|
||||
* SECURITY: Don't allow read files on directory traversal
|
||||
|
||||
# 1.5.2
|
||||
* NEW: Support for node version 0.12.x
|
||||
* NEW: API endpoint saveRevision, getSavedRevisionCount and listSavedRevisions
|
||||
|
|
|
@ -46,6 +46,12 @@ Now, run `start.bat` and open <http://localhost:9001> in your browser.
|
|||
|
||||
Update to the latest version with `git pull origin`, then run `bin\installOnWindows.bat`, again.
|
||||
|
||||
If cloning to a subdirectory within another project, you may need to do the following:
|
||||
|
||||
1. Start the server manually (e.g. `node/node_modules/ep_etherpad-lite/node/server.js]`)
|
||||
2. Edit the db `filename` in `settings.json` to the relative directory with the file (e.g. `application/lib/etherpad-lite/var/dirty.db`)
|
||||
3. Add auto-generated files to the main project `.gitignore`
|
||||
|
||||
[Next steps](#next-steps).
|
||||
|
||||
## GNU/Linux and other UNIX-like systems
|
||||
|
|
|
@ -103,7 +103,7 @@ if [ $DOWNLOAD_JQUERY = "true" ]; then
|
|||
fi
|
||||
|
||||
#Remove all minified data to force node creating it new
|
||||
echo "Clear minfified cache..."
|
||||
echo "Clearing minified cache..."
|
||||
rm -f var/minified*
|
||||
|
||||
echo "Ensure custom css/js files are created..."
|
||||
|
|
|
@ -55,7 +55,7 @@ do
|
|||
TIME_SINCE_LAST_SEND=$(($TIME_NOW - $LAST_EMAIL_SEND))
|
||||
|
||||
if [ $TIME_SINCE_LAST_SEND -gt $TIME_BETWEEN_EMAILS ]; then
|
||||
printf "Server was restared at: $(date)\nThe last 50 lines of the log before the error happens:\n $(tail -n 50 ${LOG})" | mail -s "Pad Server was restarted" $EMAIL_ADDRESS
|
||||
printf "Server was restarted at: $(date)\nThe last 50 lines of the log before the error happens:\n $(tail -n 50 ${LOG})" | mail -s "Pad Server was restarted" $EMAIL_ADDRESS
|
||||
|
||||
LAST_EMAIL_SEND=$TIME_NOW
|
||||
fi
|
||||
|
|
|
@ -203,6 +203,13 @@ Things in context:
|
|||
|
||||
This hook is called before the content of a node is collected by the usual methods. The cc object can be used to do a bunch of things that modify the content of the pad. See, for example, the heading1 plugin for etherpad original.
|
||||
|
||||
E.g. if you need to apply an attribute to newly inserted characters,
|
||||
call cc.doAttrib(state, "attributeName") which results in an attribute attributeName=true.
|
||||
|
||||
If you want to specify also a value, call cc.doAttrib(state, "attributeName:value")
|
||||
which results in an attribute attributeName=value.
|
||||
|
||||
|
||||
## collectContentImage
|
||||
Called from: src/static/js/contentcollector.js
|
||||
|
||||
|
|
|
@ -54,6 +54,9 @@
|
|||
//the default text of a pad
|
||||
"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\nGet involved with Etherpad at http:\/\/etherpad.org\n",
|
||||
|
||||
/* Shoud we suppress errors from being visible in the default Pad Text? */
|
||||
"suppressErrorsInPadText" : false,
|
||||
|
||||
/* Users must have a session to access pads. This effectively allows only group pads to be accessed. */
|
||||
"requireSession" : false,
|
||||
|
||||
|
|
|
@ -38,7 +38,24 @@
|
|||
"pad.settings.rtlcheck": "Read content from right to left?",
|
||||
"pad.settings.fontType": "Font type:",
|
||||
"pad.settings.fontType.normal": "Normal",
|
||||
"pad.settings.fontType.opendyslexic": "Open Dyslexic",
|
||||
"pad.settings.fontType.monospaced": "Monospace",
|
||||
"pad.settings.fontType.comicsans": "Comic Sans",
|
||||
"pad.settings.fontType.couriernew": "Courier New",
|
||||
"pad.settings.fontType.georgia": "Georgia",
|
||||
"pad.settings.fontType.impact": "Impact",
|
||||
"pad.settings.fontType.lucida": "Lucida",
|
||||
"pad.settings.fontType.lucidasans": "Lucida Sans",
|
||||
"pad.settings.fontType.palatino": "Palatino",
|
||||
"pad.settings.fontType.tahoma": "Tahoma",
|
||||
"pad.settings.fontType.timesnewroman": "Times New Roman",
|
||||
"pad.settings.fontType.trebuchet": "Trebuchet",
|
||||
"pad.settings.fontType.verdana": "Verdana",
|
||||
"pad.settings.fontType.symbol": "Symbol",
|
||||
"pad.settings.fontType.webdings": "Webdings",
|
||||
"pad.settings.fontType.wingdings": "Wingdings",
|
||||
"pad.settings.fontType.sansserif": "Sans Serif",
|
||||
"pad.settings.fontType.serif": "Serif",
|
||||
"pad.settings.globalView": "Global View",
|
||||
"pad.settings.language": "Language:",
|
||||
|
||||
|
@ -105,6 +122,10 @@
|
|||
"timeslider.version": "Version {{version}}",
|
||||
"timeslider.saved": "Saved {{month}} {{day}}, {{year}}",
|
||||
|
||||
"timeslider.playPause": "Playback / Pause Pad Contents",
|
||||
"timeslider.backRevision":"Go back a revision in this Pad",
|
||||
"timeslider.forwardRevision":"Go forward a revision in this Pad",
|
||||
|
||||
"timeslider.dateformat": "{{month}}/{{day}}/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
|
||||
"timeslider.month.january": "January",
|
||||
"timeslider.month.february": "February",
|
||||
|
|
|
@ -432,8 +432,8 @@ getChatHistory(padId, start, end), returns a part of or the whole chat-history o
|
|||
|
||||
Example returns:
|
||||
|
||||
{"code":0,"message":"ok","data":{"messages":[{"text":"foo","userId":"a.foo","time":1359199533759,"userName":"test"},
|
||||
{"text":"bar","userId":"a.foo","time":1359199534622,"userName":"test"}]}}
|
||||
{"code":0,"message":"ok","data":{"messages":[{"text":"foo","authorID":"a.foo","time":1359199533759,"userName":"test"},
|
||||
{"text":"bar","authorID":"a.foo","time":1359199534622,"userName":"test"}]}}
|
||||
|
||||
{code: 1, message:"start is higher or equal to the current chatHead", data: null}
|
||||
|
||||
|
@ -494,6 +494,33 @@ exports.getChatHistory = function(padID, start, end, callback)
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
appendChatMessage(padID, text, authorID, time), creates a chat message for the pad id, time is a timestamp
|
||||
|
||||
Example returns:
|
||||
|
||||
{code: 0, message:"ok", data: null
|
||||
{code: 1, message:"padID does not exist", data: null}
|
||||
*/
|
||||
exports.appendChatMessage = function(padID, text, authorID, time, callback)
|
||||
{
|
||||
//text is required
|
||||
if(typeof text != "string")
|
||||
{
|
||||
callback(new customError("text is no string","apierror"));
|
||||
return;
|
||||
}
|
||||
|
||||
//get the pad
|
||||
getPadSafe(padID, true, function(err, pad)
|
||||
{
|
||||
if(ERR(err, callback)) return;
|
||||
|
||||
pad.appendChatMessage(text, authorID, parseInt(time));
|
||||
callback();
|
||||
});
|
||||
}
|
||||
|
||||
/*****************/
|
||||
/**PAD FUNCTIONS */
|
||||
/*****************/
|
||||
|
|
|
@ -394,10 +394,60 @@ var version =
|
|||
, "getChatHead" : ["padID"]
|
||||
, "restoreRevision" : ["padID", "rev"]
|
||||
}
|
||||
, "1.2.12":
|
||||
{ "createGroup" : []
|
||||
, "createGroupIfNotExistsFor" : ["groupMapper"]
|
||||
, "deleteGroup" : ["groupID"]
|
||||
, "listPads" : ["groupID"]
|
||||
, "listAllPads" : []
|
||||
, "createDiffHTML" : ["padID", "startRev", "endRev"]
|
||||
, "createPad" : ["padID", "text"]
|
||||
, "createGroupPad" : ["groupID", "padName", "text"]
|
||||
, "createAuthor" : ["name"]
|
||||
, "createAuthorIfNotExistsFor": ["authorMapper" , "name"]
|
||||
, "listPadsOfAuthor" : ["authorID"]
|
||||
, "createSession" : ["groupID", "authorID", "validUntil"]
|
||||
, "deleteSession" : ["sessionID"]
|
||||
, "getSessionInfo" : ["sessionID"]
|
||||
, "listSessionsOfGroup" : ["groupID"]
|
||||
, "listSessionsOfAuthor" : ["authorID"]
|
||||
, "getText" : ["padID", "rev"]
|
||||
, "setText" : ["padID", "text"]
|
||||
, "getHTML" : ["padID", "rev"]
|
||||
, "setHTML" : ["padID", "html"]
|
||||
, "getAttributePool" : ["padID"]
|
||||
, "getRevisionsCount" : ["padID"]
|
||||
, "getSavedRevisionsCount" : ["padID"]
|
||||
, "listSavedRevisions" : ["padID"]
|
||||
, "saveRevision" : ["padID", "rev"]
|
||||
, "getRevisionChangeset" : ["padID", "rev"]
|
||||
, "getLastEdited" : ["padID"]
|
||||
, "deletePad" : ["padID"]
|
||||
, "copyPad" : ["sourceID", "destinationID", "force"]
|
||||
, "movePad" : ["sourceID", "destinationID", "force"]
|
||||
, "getReadOnlyID" : ["padID"]
|
||||
, "getPadID" : ["roID"]
|
||||
, "setPublicStatus" : ["padID", "publicStatus"]
|
||||
, "getPublicStatus" : ["padID"]
|
||||
, "setPassword" : ["padID", "password"]
|
||||
, "isPasswordProtected" : ["padID"]
|
||||
, "listAuthorsOfPad" : ["padID"]
|
||||
, "padUsersCount" : ["padID"]
|
||||
, "getAuthorName" : ["authorID"]
|
||||
, "padUsers" : ["padID"]
|
||||
, "sendClientsMessage" : ["padID", "msg"]
|
||||
, "listAllGroups" : []
|
||||
, "checkToken" : []
|
||||
, "appendChatMessage" : ["padID", "text", "authorID", "time"]
|
||||
, "getChatHistory" : ["padID"]
|
||||
, "getChatHistory" : ["padID", "start", "end"]
|
||||
, "getChatHead" : ["padID"]
|
||||
, "restoreRevision" : ["padID", "rev"]
|
||||
}
|
||||
};
|
||||
|
||||
// set the latest available API version here
|
||||
exports.latestApiVersion = '1.2.11';
|
||||
exports.latestApiVersion = '1.2.12';
|
||||
|
||||
// exports the versions so it can be used by the new Swagger endpoint
|
||||
exports.version = version;
|
||||
|
|
|
@ -148,6 +148,9 @@ exports.doImport = function(req, res, padId)
|
|||
if(!importHandledByPlugin || !directDatabaseAccess){
|
||||
var fileEnding = path.extname(srcFile).toLowerCase();
|
||||
var fileIsHTML = (fileEnding === ".html" || fileEnding === ".htm");
|
||||
var fileIsTXT = (fileEnding === ".txt");
|
||||
if (fileIsTXT) abiword = false; // Don't use abiword for text files
|
||||
// See https://github.com/ether/etherpad-lite/issues/2572
|
||||
if (abiword && !fileIsHTML) {
|
||||
abiword.convertFile(srcFile, destFile, "htm", function(err) {
|
||||
//catch convert errors
|
||||
|
@ -213,7 +216,7 @@ exports.doImport = function(req, res, padId)
|
|||
// Title needs to be stripped out else it appends it to the pad..
|
||||
text = text.replace("<title>", "<!-- <title>");
|
||||
text = text.replace("</title>","</title>-->");
|
||||
|
||||
|
||||
//node on windows has a delay on releasing of the file lock.
|
||||
//We add a 100ms delay to work around this
|
||||
if(os.type().indexOf("Windows") > -1){
|
||||
|
@ -245,7 +248,6 @@ exports.doImport = function(req, res, padId)
|
|||
padManager.getPad(padId, function(err, _pad){
|
||||
var pad = _pad;
|
||||
padManager.unloadPad(padId);
|
||||
|
||||
// direct Database Access means a pad user should perform a switchToPad
|
||||
// and not attempt to recieve updated pad data..
|
||||
if(!directDatabaseAccess){
|
||||
|
|
|
@ -656,12 +656,17 @@ function handleUserChanges(data, cb)
|
|||
, op
|
||||
while(iterator.hasNext()) {
|
||||
op = iterator.next()
|
||||
if(op.opcode != '+') continue;
|
||||
|
||||
//+ can add text with attribs
|
||||
//= can change or add attribs
|
||||
//- can have attribs, but they are discarded and don't show up in the attribs - but do show up in the pool
|
||||
|
||||
op.attribs.split('*').forEach(function(attr) {
|
||||
if(!attr) return
|
||||
attr = wireApool.getAttrib(attr)
|
||||
if(!attr) return
|
||||
if('author' == attr[0] && attr[1] != thisSession.author) throw new Error("Trying to submit changes as another author in changeset "+changeset);
|
||||
//the empty author is used in the clearAuthorship functionality so this should be the only exception
|
||||
if('author' == attr[0] && (attr[1] != thisSession.author && attr[1] != '')) throw new Error("Trying to submit changes as another author in changeset "+changeset);
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -1629,10 +1634,15 @@ function composePadChangesets(padId, startNum, endNum, callback)
|
|||
changeset = changesets[startNum];
|
||||
var pool = pad.apool();
|
||||
|
||||
for(var r=startNum+1;r<endNum;r++)
|
||||
{
|
||||
var cs = changesets[r];
|
||||
changeset = Changeset.compose(changeset, cs, pool);
|
||||
try {
|
||||
for(var r=startNum+1;r<endNum;r++) {
|
||||
var cs = changesets[r];
|
||||
changeset = Changeset.compose(changeset, cs, pool);
|
||||
}
|
||||
} catch(e){
|
||||
// r-1 indicates the rev that was build starting with startNum, applying startNum+1, +2, +3
|
||||
console.warn("failed to compose cs in pad:",padId," startrev:",startNum," current rev:",r);
|
||||
return callback(e);
|
||||
}
|
||||
|
||||
callback(null);
|
||||
|
|
|
@ -13,6 +13,8 @@ exports.createServer = function () {
|
|||
console.log("Report bugs at https://github.com/ether/etherpad-lite/issues")
|
||||
|
||||
serverName = "Etherpad " + settings.getGitCommit() + " (http://etherpad.org)";
|
||||
|
||||
console.log("Your Etherpad version is " + settings.getEpVersion() + " (" + settings.getGitCommit() + ")");
|
||||
|
||||
exports.restartServer();
|
||||
|
||||
|
|
|
@ -17,7 +17,13 @@ exports.expressCreateServer = function (hook_name, args, cb) {
|
|||
});
|
||||
args.app.get('/admin/plugins/info', function(req, res) {
|
||||
var gitCommit = settings.getGitCommit();
|
||||
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html", {gitCommit:gitCommit}) );
|
||||
var epVersion = settings.getEpVersion();
|
||||
res.send( eejs.require("ep_etherpad-lite/templates/admin/plugins-info.html",
|
||||
{
|
||||
gitCommit: gitCommit,
|
||||
epVersion: epVersion
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -284,6 +284,10 @@ var API = {
|
|||
}
|
||||
},
|
||||
"response": {"chatHead":{"type":"Message"}}
|
||||
},
|
||||
"appendChatMessage": {
|
||||
"func": "appendChatMessage",
|
||||
"description": "appends a chat message"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -145,7 +145,6 @@ function minify(req, res, next)
|
|||
filename = path.normalize(path.join(ROOT_DIR, filename));
|
||||
if (filename.indexOf(ROOT_DIR) == 0) {
|
||||
filename = filename.slice(ROOT_DIR.length);
|
||||
filename = filename.replace(/\\/g, '/'); // Windows (safe generally?)
|
||||
} else {
|
||||
res.writeHead(404, {});
|
||||
res.end();
|
||||
|
|
|
@ -27,7 +27,7 @@ var npm = require("npm/lib/npm.js");
|
|||
var jsonminify = require("jsonminify");
|
||||
var log4js = require("log4js");
|
||||
var randomString = require("./randomstring");
|
||||
|
||||
var suppressDisableMsg = " -- To suppress these warning messages change suppressErrorsInPadText to true in your settings.json\n";
|
||||
|
||||
/* Root path of the installation */
|
||||
exports.root = path.normalize(path.join(npm.dir, ".."));
|
||||
|
@ -54,6 +54,11 @@ exports.ip = "0.0.0.0";
|
|||
*/
|
||||
exports.port = process.env.PORT || 9001;
|
||||
|
||||
/**
|
||||
* Should we suppress Error messages from being in Pad Contents
|
||||
*/
|
||||
exports.suppressErrorsInPadText = false;
|
||||
|
||||
/**
|
||||
* The SSL signed server key and the Certificate Authority's own certificate
|
||||
* default case: ep-lite does *not* use SSL. A signed server key is not required in this case.
|
||||
|
@ -95,7 +100,7 @@ exports.toolbar = {
|
|||
["showusers"]
|
||||
],
|
||||
timeslider: [
|
||||
["timeslider_export", "timeslider_returnToPad"]
|
||||
["timeslider_export", "timeslider_settings", "timeslider_returnToPad"]
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -194,7 +199,6 @@ exports.getGitCommit = function() {
|
|||
var refPath = rootPath + "/.git/" + ref.substring(5, ref.indexOf("\n"));
|
||||
version = fs.readFileSync(refPath, "utf-8");
|
||||
version = version.substring(0, 7);
|
||||
console.log("Your Etherpad git version is " + version);
|
||||
}
|
||||
catch(e)
|
||||
{
|
||||
|
@ -203,6 +207,11 @@ exports.getGitCommit = function() {
|
|||
return version;
|
||||
}
|
||||
|
||||
// Return etherpad version from package.json
|
||||
exports.getEpVersion = function() {
|
||||
return require('ep_etherpad-lite/package.json').version;
|
||||
}
|
||||
|
||||
exports.reloadSettings = function reloadSettings() {
|
||||
// Discover where the settings file lives
|
||||
var settingsFilename = argv.settings || "settings.json";
|
||||
|
@ -266,7 +275,11 @@ exports.reloadSettings = function reloadSettings() {
|
|||
{
|
||||
fs.exists(exports.abiword, function(exists) {
|
||||
if (!exists) {
|
||||
console.error("Abiword does not exist at this path, check your settings file");
|
||||
var abiwordError = "Abiword does not exist at this path, check your settings file";
|
||||
if(!exports.suppressErrorsInPadText){
|
||||
exports.defaultPadText = exports.defaultPadText + "\nError: " + abiwordError + suppressDisableMsg;
|
||||
}
|
||||
console.error(abiwordError);
|
||||
exports.abiword = null;
|
||||
}
|
||||
});
|
||||
|
@ -275,11 +288,19 @@ exports.reloadSettings = function reloadSettings() {
|
|||
|
||||
if(!exports.sessionKey){ // If the secretKey isn't set we also create yet another unique value here
|
||||
exports.sessionKey = randomString(32);
|
||||
console.warn("You need to set a sessionKey value in settings.json, this will allow your users to reconnect to your Etherpad Instance if your instance restarts");
|
||||
var sessionWarning = "You need to set a sessionKey value in settings.json, this will allow your users to reconnect to your Etherpad Instance if your instance restarts";
|
||||
if(!exports.suppressErrorsInPadText){
|
||||
exports.defaultPadText = exports.defaultPadText + "\nWarning: " + sessionWarning + suppressDisableMsg;
|
||||
}
|
||||
console.warn(sessionWarning);
|
||||
}
|
||||
|
||||
if(exports.dbType === "dirty"){
|
||||
console.warn("DirtyDB is used. This is fine for testing but not recommended for production.");
|
||||
var dirtyWarning = "DirtyDB is used. This is fine for testing but not recommended for production.";
|
||||
if(!exports.suppressErrorsInPadText){
|
||||
exports.defaultPadText = exports.defaultPadText + "\nWarning: " + dirtyWarning + suppressDisableMsg;
|
||||
}
|
||||
console.warn(dirtyWarning);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -99,12 +99,14 @@ _.extend(Button.prototype, {
|
|||
};
|
||||
return tag("li", liAttributes,
|
||||
tag("a", { "class": this.grouping, "data-l10n-id": this.attributes.localizationId },
|
||||
tag("span", { "class": " "+ this.attributes.class })
|
||||
tag("button", { "class": " "+ this.attributes.class, "data-l10n-id": this.attributes.localizationId })
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
|
||||
SelectButton = function (attributes) {
|
||||
this.attributes = attributes;
|
||||
this.options = [];
|
||||
|
@ -208,6 +210,12 @@ module.exports = {
|
|||
class: "buttonicon buttonicon-import_export"
|
||||
},
|
||||
|
||||
timeslider_settings: {
|
||||
command: "settings",
|
||||
localizationId: "pad.toolbar.settings.title",
|
||||
class: "buttonicon buttonicon-settings"
|
||||
},
|
||||
|
||||
timeslider_returnToPad: {
|
||||
command: "timeslider_returnToPad",
|
||||
localizationId: "timeslider.toolbar.returnbutton",
|
||||
|
|
|
@ -13,25 +13,25 @@
|
|||
],
|
||||
"dependencies" : {
|
||||
"etherpad-yajsml" : "0.0.2",
|
||||
"request" : "2.53.0",
|
||||
"request" : "2.55.0",
|
||||
"etherpad-require-kernel" : "1.0.8",
|
||||
"resolve" : "1.1.0",
|
||||
"socket.io" : "1.3.3",
|
||||
"ueberDB" : "0.2.13",
|
||||
"resolve" : "1.1.6",
|
||||
"socket.io" : "1.3.5",
|
||||
"ueberDB" : "0.2.15",
|
||||
"express" : "3.8.1",
|
||||
"async" : "0.9.0",
|
||||
"connect" : "2.7.11",
|
||||
"clean-css" : "3.0.8",
|
||||
"uglify-js" : "2.4.16",
|
||||
"formidable" : "1.0.16",
|
||||
"clean-css" : "3.1.9",
|
||||
"uglify-js" : "2.4.19",
|
||||
"formidable" : "1.0.17",
|
||||
"log4js" : "0.6.22",
|
||||
"cheerio" : "0.18.0",
|
||||
"cheerio" : "0.19.0",
|
||||
"async-stacktrace" : "0.0.2",
|
||||
"npm" : "2.4.1",
|
||||
"npm" : "2.7.5",
|
||||
"ejs" : "1.0.0",
|
||||
"graceful-fs" : "3.0.5",
|
||||
"graceful-fs" : "3.0.6",
|
||||
"slide" : "1.1.6",
|
||||
"semver" : "4.2.0",
|
||||
"semver" : "4.3.3",
|
||||
"security" : "1.0.0",
|
||||
"tinycon" : "0.0.1",
|
||||
"underscore" : "1.5.1",
|
||||
|
@ -41,7 +41,7 @@
|
|||
"channels" : "0.0.4",
|
||||
"jsonminify" : "0.2.3",
|
||||
"measured" : "1.0.0",
|
||||
"mocha" : "2.1.0",
|
||||
"mocha" : "2.2.1",
|
||||
"supertest" : "0.15.0"
|
||||
},
|
||||
"bin": { "etherpad-lite": "./node/server.js" },
|
||||
|
@ -54,5 +54,5 @@
|
|||
"repository" : { "type" : "git",
|
||||
"url" : "http://github.com/ether/etherpad-lite.git"
|
||||
},
|
||||
"version" : "1.5.2"
|
||||
"version" : "1.5.3"
|
||||
}
|
||||
|
|
|
@ -98,10 +98,26 @@ body.grayedout { background-color: #eee !important }
|
|||
}
|
||||
|
||||
body.doesWrap {
|
||||
white-space: pre-wrap; /*Must be pre-wrap to keep trailing spaces. Otherwise you get a zombie caret, walking around your screen (see #1766), WARNING: Enabling this causes Paste as plain text in Chrome to remove line breaks, this is probably undesirable */
|
||||
/* white-space: pre-wrap; */
|
||||
|
||||
/*
|
||||
Must be pre-wrap to keep trailing spaces. Otherwise you get a zombie caret,
|
||||
walking around your screen (see #1766).
|
||||
WARNING: Enabling this causes Paste as plain text in Chrome to remove line breaks
|
||||
this is probably undesirable
|
||||
WARNING: This causes copy & paste events to lose bold etc. attributes
|
||||
NOTE: The walking-zombie caret issue seems to have been fixed in FF upstream
|
||||
so let's try diabling pre-wrap and see how we get on now.
|
||||
For more details see: https://github.com/ether/etherpad-lite/issues/2574
|
||||
*/
|
||||
word-wrap: break-word; /* fix for issue #1648 - firefox not wrapping long lines (without spaces) correctly */
|
||||
}
|
||||
|
||||
body.doesWrap > div{
|
||||
/* Related to #1766 */
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#innerdocbody {
|
||||
padding-top: 1px; /* important for some reason? */
|
||||
padding-right: 10px;
|
||||
|
|
|
@ -70,10 +70,6 @@ a img {
|
|||
.toolbar ul li {
|
||||
float: left;
|
||||
margin-left: 2px;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
height:32px;
|
||||
}
|
||||
.toolbar ul li.separator {
|
||||
|
@ -141,9 +137,24 @@ a img {
|
|||
top: 1px;
|
||||
}
|
||||
.toolbar ul li a .buttontext {
|
||||
color: #222;
|
||||
color: #666;
|
||||
font-size: 14px;
|
||||
border:none;
|
||||
background:none;
|
||||
margin-top:1px;
|
||||
color:#666;
|
||||
}
|
||||
|
||||
.buttontext::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
.buttontext:focus{
|
||||
/* Not sure why important is required here but it is */
|
||||
border: 1px solid #666 !important;
|
||||
}
|
||||
|
||||
.toolbar ul li a.grouped-left {
|
||||
border-radius: 3px 0 0 3px;
|
||||
}
|
||||
|
@ -197,6 +208,7 @@ li[data-key=showusers] > a #online_count {
|
|||
#editbar{
|
||||
display:none;
|
||||
}
|
||||
|
||||
#editorcontainer {
|
||||
position: absolute;
|
||||
top: 37px; /* + 1px border */
|
||||
|
@ -742,12 +754,24 @@ table#otheruserstable {
|
|||
height: 16px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
|
||||
border: none;
|
||||
padding: 0;
|
||||
background: none;
|
||||
font-family: "fontawesome-etherpad";
|
||||
font-size: 15px;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
color: #666;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.buttonicon::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0
|
||||
}
|
||||
|
||||
.buttonicon:focus{
|
||||
border: 1px solid #666;
|
||||
}
|
||||
.buttonicon-bold:before {
|
||||
content: "\e81c";
|
||||
|
@ -1216,6 +1240,11 @@ input[type=checkbox] {
|
|||
}
|
||||
/* End of gritter stuff */
|
||||
|
||||
@font-face {
|
||||
font-family: opendyslexic;
|
||||
src: url("../../static/font/opendyslexic.otf") format("opentype");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "fontawesome-etherpad";
|
||||
src:url("../font/fontawesome-etherpad.eot");
|
||||
|
@ -1254,3 +1283,11 @@ input[type=checkbox] {
|
|||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.hideControlsEditor{
|
||||
top:0px !important;
|
||||
}
|
||||
.hideControlsEditbar{
|
||||
display:none !important;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -78,6 +78,7 @@
|
|||
width: 44px;
|
||||
text-align:center;
|
||||
vertical-align:middle;
|
||||
background:none;
|
||||
}
|
||||
#playpause_button {
|
||||
right: 77px;
|
||||
|
@ -125,7 +126,7 @@
|
|||
font-family: fontawesome-etherpad;
|
||||
border-radius:2px;
|
||||
border: #666 solid 1px;
|
||||
line-height:18px;
|
||||
/* line-height:18px; */
|
||||
text-align:center;
|
||||
height:22px;
|
||||
color:#666;
|
||||
|
@ -204,12 +205,9 @@ stepper:active{
|
|||
float:right;
|
||||
height:30px;
|
||||
}
|
||||
#settings,
|
||||
#import_export,
|
||||
#embed,
|
||||
#connectivity,
|
||||
#users {
|
||||
top: 62px;
|
||||
#import_export, #settings{
|
||||
top: 115px;
|
||||
position: fixed;
|
||||
}
|
||||
#import_export .popup {
|
||||
width: 183px;
|
||||
|
@ -218,9 +216,7 @@ stepper:active{
|
|||
border-radius: 0 0 0 6px;
|
||||
}
|
||||
#import_export {
|
||||
top: 115px;
|
||||
width: 185px;
|
||||
position: fixed;
|
||||
}
|
||||
.timeslider-bar {
|
||||
background: #f7f7f7;
|
||||
|
@ -236,7 +232,7 @@ stepper:active{
|
|||
.timeslider-bar #editbar {
|
||||
border-bottom: none;
|
||||
float: right;
|
||||
width: 170px;
|
||||
width: 180px;
|
||||
}
|
||||
.timeslider-bar h1 {
|
||||
margin: 5px
|
||||
|
@ -337,3 +333,19 @@ OL {
|
|||
.list-number6 {
|
||||
list-style-type: lower-roman
|
||||
}
|
||||
|
||||
button{
|
||||
margin:0;
|
||||
padding:0;
|
||||
cursor:pointer;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner {
|
||||
padding: 0;
|
||||
border: 0
|
||||
}
|
||||
|
||||
button:focus{
|
||||
border: 1px solid #666;
|
||||
}
|
||||
|
||||
|
|
Binary file not shown.
|
@ -98,7 +98,7 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
|
||||
/*
|
||||
Gets all attributes on a line
|
||||
@param lineNum: the number of the line to set the attribute for
|
||||
@param lineNum: the number of the line to get the attribute for
|
||||
*/
|
||||
getAttributesOnLine: function(lineNum){
|
||||
// get attributes of first char of line
|
||||
|
@ -122,6 +122,59 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
return [];
|
||||
},
|
||||
|
||||
/*
|
||||
Gets all attributes at a position containing line number and column
|
||||
@param lineNumber starting with zero
|
||||
@param column starting with zero
|
||||
returns a list of attributes in the format
|
||||
[ ["key","value"], ["key","value"], ... ]
|
||||
*/
|
||||
getAttributesOnPosition: function(lineNumber, column){
|
||||
// get all attributes of the line
|
||||
var aline = this.rep.alines[lineNumber];
|
||||
|
||||
if (!aline) {
|
||||
return [];
|
||||
}
|
||||
// iterate through all operations of a line
|
||||
var opIter = Changeset.opIterator(aline);
|
||||
|
||||
// we need to sum up how much characters each operations take until the wanted position
|
||||
var currentPointer = 0;
|
||||
var attributes = [];
|
||||
var currentOperation;
|
||||
|
||||
while (opIter.hasNext()) {
|
||||
currentOperation = opIter.next();
|
||||
currentPointer = currentPointer + currentOperation.chars;
|
||||
|
||||
if (currentPointer > column) {
|
||||
// we got the operation of the wanted position, now collect all its attributes
|
||||
Changeset.eachAttribNumber(currentOperation.attribs, function (n) {
|
||||
attributes.push([
|
||||
this.rep.apool.getAttribKey(n),
|
||||
this.rep.apool.getAttribValue(n)
|
||||
]);
|
||||
}.bind(this));
|
||||
|
||||
// skip the loop
|
||||
return attributes;
|
||||
}
|
||||
}
|
||||
return attributes;
|
||||
|
||||
},
|
||||
|
||||
/*
|
||||
Gets all attributes at caret position
|
||||
if the user selected a range, the start of the selection is taken
|
||||
returns a list of attributes in the format
|
||||
[ ["key","value"], ["key","value"], ... ]
|
||||
*/
|
||||
getAttributesOnCaret: function(){
|
||||
return this.getAttributesOnPosition(this.rep.selStart[0], this.rep.selStart[1]);
|
||||
},
|
||||
|
||||
/*
|
||||
Sets a specified attribute on a line
|
||||
@param lineNum: the number of the line to set the attribute for
|
||||
|
@ -153,53 +206,58 @@ AttributeManager.prototype = _(AttributeManager.prototype).extend({
|
|||
return this.applyChangeset(builder);
|
||||
},
|
||||
|
||||
/*
|
||||
Removes a specified attribute on a line
|
||||
@param lineNum: the number of the affected line
|
||||
@param attributeKey: the name of the attribute to remove, e.g. list
|
||||
|
||||
/**
|
||||
* Removes a specified attribute on a line
|
||||
* @param lineNum the number of the affected line
|
||||
* @param attributeName the name of the attribute to remove, e.g. list
|
||||
* @param attributeValue if given only attributes with equal value will be removed
|
||||
*/
|
||||
removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
|
||||
var loc = [0,0];
|
||||
var builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
var hasMarker = this.lineHasMarker(lineNum);
|
||||
var attribs
|
||||
var foundAttrib = false
|
||||
|
||||
attribs = this.getAttributesOnLine(lineNum).map(function(attrib) {
|
||||
if(attrib[0] === attributeName) {
|
||||
foundAttrib = true
|
||||
return [attributeName, null] // remove this attrib from the linemarker
|
||||
}
|
||||
return attrib
|
||||
})
|
||||
removeAttributeOnLine: function(lineNum, attributeName, attributeValue){
|
||||
var builder = Changeset.builder(this.rep.lines.totalWidth());
|
||||
var hasMarker = this.lineHasMarker(lineNum);
|
||||
var found = false;
|
||||
|
||||
if(!foundAttrib) {
|
||||
return
|
||||
var attribs = _(this.getAttributesOnLine(lineNum)).map(function (attrib) {
|
||||
if (attrib[0] === attributeName && (!attributeValue || attrib[0] === attributeValue)){
|
||||
found = true;
|
||||
return [attributeName, ''];
|
||||
}
|
||||
return attrib;
|
||||
});
|
||||
|
||||
if(hasMarker){
|
||||
ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 0]));
|
||||
// If length == 4, there's [author, lmkr, insertorder, + the attrib being removed] thus we can remove the marker entirely
|
||||
if(attribs.length <= 4) ChangesetUtils.buildRemoveRange(this.rep, builder, loc, (loc = [lineNum, 1]))
|
||||
else ChangesetUtils.buildKeepRange(this.rep, builder, loc, (loc = [lineNum, 1]), attribs, this.rep.apool);
|
||||
}
|
||||
|
||||
return this.applyChangeset(builder);
|
||||
},
|
||||
if (!found) {
|
||||
return;
|
||||
}
|
||||
|
||||
ChangesetUtils.buildKeepToStartOfRange(this.rep, builder, [lineNum, 0]);
|
||||
|
||||
var countAttribsWithMarker = _.chain(attribs).filter(function(a){return !!a[1];})
|
||||
.map(function(a){return a[0];}).difference(['author', 'lmkr', 'insertorder', 'start']).size().value();
|
||||
|
||||
//if we have marker and any of attributes don't need to have marker. we need delete it
|
||||
if(hasMarker && !countAttribsWithMarker){
|
||||
ChangesetUtils.buildRemoveRange(this.rep, builder, [lineNum, 0], [lineNum, 1]);
|
||||
}else{
|
||||
ChangesetUtils.buildKeepRange(this.rep, builder, [lineNum, 0], [lineNum, 1], attribs, this.rep.apool);
|
||||
}
|
||||
|
||||
return this.applyChangeset(builder);
|
||||
},
|
||||
|
||||
/*
|
||||
Sets a specified attribute on a line
|
||||
@param lineNum: the number of the line to set the attribute for
|
||||
@param attributeKey: the name of the attribute to set, e.g. list
|
||||
@param attributeValue: an optional parameter to pass to the attribute (e.g. indention level)
|
||||
Toggles a line attribute for the specified line number
|
||||
If a line attribute with the specified name exists with any value it will be removed
|
||||
Otherwise it will be set to the given value
|
||||
@param lineNum: the number of the line to toggle the attribute for
|
||||
@param attributeKey: the name of the attribute to toggle, e.g. list
|
||||
@param attributeValue: the value to pass to the attribute (e.g. indention level)
|
||||
*/
|
||||
toggleAttributeOnLine: function(lineNum, attributeName, attributeValue) {
|
||||
return this.getAttributeOnLine(attributeName) ?
|
||||
return this.getAttributeOnLine(lineNum, attributeName) ?
|
||||
this.removeAttributeOnLine(lineNum, attributeName) :
|
||||
this.setAttributeOnLine(lineNum, attributeName, attributeValue);
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = AttributeManager;
|
||||
module.exports = AttributeManager;
|
||||
|
|
|
@ -265,7 +265,7 @@ plugins.ensure(function () {\n\
|
|||
iframeHTML: iframeHTML
|
||||
});
|
||||
|
||||
iframeHTML.push('</head><body id="innerdocbody" class="syntax" spellcheck="false"> </body></html>');
|
||||
iframeHTML.push('</head><body id="innerdocbody" role="application" class="syntax" spellcheck="false"> </body></html>');
|
||||
|
||||
// Expose myself to global for my child frame.
|
||||
var thisFunctionsName = "ChildAccessibleAce2Editor";
|
||||
|
@ -279,6 +279,7 @@ window.onload = function () {\n\
|
|||
setTimeout(function () {\n\
|
||||
var iframe = document.createElement("IFRAME");\n\
|
||||
iframe.name = "ace_inner";\n\
|
||||
iframe.title = "pad";\n\
|
||||
iframe.scrolling = "no";\n\
|
||||
var outerdocbody = document.getElementById("outerdocbody");\n\
|
||||
iframe.frameBorder = 0;\n\
|
||||
|
@ -319,6 +320,7 @@ window.onload = function () {\n\
|
|||
var outerFrame = document.createElement("IFRAME");
|
||||
outerFrame.name = "ace_outer";
|
||||
outerFrame.frameBorder = 0; // for IE
|
||||
outerFrame.title = "Ether";
|
||||
info.frame = outerFrame;
|
||||
document.getElementById(containerId).appendChild(outerFrame);
|
||||
|
||||
|
|
|
@ -608,8 +608,11 @@ function Ace2Inner(){
|
|||
|
||||
// Chrome can't handle the truth.. If CSS rule white-space:pre-wrap
|
||||
// is true then any paste event will insert two lines..
|
||||
// Sadly this will mean you get a walking Caret in Chrome when clicking on a URL
|
||||
// So this has to be set to pre-wrap ;(
|
||||
// We need to file a bug w/ the Chromium team.
|
||||
if(browser.chrome){
|
||||
$("#innerdocbody").css({"white-space":"normal"});
|
||||
$("#innerdocbody").css({"white-space":"pre-wrap"});
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -2322,93 +2325,72 @@ function Ace2Inner(){
|
|||
}
|
||||
editorInfo.ace_setAttributeOnSelection = setAttributeOnSelection;
|
||||
|
||||
|
||||
function getAttributeOnSelection(attributeName){
|
||||
if (!(rep.selStart && rep.selEnd)) return;
|
||||
|
||||
// get the previous/next characters formatting when we have nothing selected
|
||||
// To fix this we just change the focus area, we don't actually check anything yet.
|
||||
if(rep.selStart[1] == rep.selEnd[1]){
|
||||
// if we're at the beginning of a line bump end forward so we get the right attribute
|
||||
if(rep.selStart[1] == 0 && rep.selEnd[1] == 0){
|
||||
rep.selEnd[1] = 1;
|
||||
}
|
||||
if(rep.selStart[1] < 0){
|
||||
rep.selStart[1] = 0;
|
||||
}
|
||||
var line = rep.lines.atIndex(rep.selStart[0]);
|
||||
// if we're at the end of the line bmp the start back 1 so we get hte attribute
|
||||
if(rep.selEnd[1] == line.text.length){
|
||||
rep.selStart[1] = rep.selStart[1] -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Do the detection
|
||||
var selectionAllHasIt = true;
|
||||
if (!(rep.selStart && rep.selEnd)) return
|
||||
|
||||
var withIt = Changeset.makeAttribsString('+', [
|
||||
[attributeName, 'true']
|
||||
], rep.apool);
|
||||
var withItRegex = new RegExp(withIt.replace(/\*/g, '\\*') + "(\\*|$)");
|
||||
|
||||
function hasIt(attribs)
|
||||
{
|
||||
return withItRegex.test(attribs);
|
||||
}
|
||||
|
||||
var selStartLine = rep.selStart[0];
|
||||
var selEndLine = rep.selEnd[0];
|
||||
for (var n = selStartLine; n <= selEndLine; n++)
|
||||
{
|
||||
var opIter = Changeset.opIterator(rep.alines[n]);
|
||||
var indexIntoLine = 0;
|
||||
var selectionStartInLine = 0;
|
||||
var selectionEndInLine = rep.lines.atIndex(n).text.length; // exclude newline
|
||||
if(rep.lines.atIndex(n).text.length == 0){
|
||||
return false; // If the line length is 0 we basically treat it as having no formatting
|
||||
return rangeHasAttrib(rep.selStart, rep.selEnd)
|
||||
|
||||
function rangeHasAttrib(selStart, selEnd) {
|
||||
// if range is collapsed -> no attribs in range
|
||||
if(selStart[1] == selEnd[1] && selStart[0] == selEnd[0]) return false
|
||||
|
||||
if(selStart[0] != selEnd[0]) { // -> More than one line selected
|
||||
var hasAttrib = true
|
||||
|
||||
// from selStart to the end of the first line
|
||||
hasAttrib = hasAttrib && rangeHasAttrib(selStart, [selStart[0], rep.lines.atIndex(selStart[0]).text.length])
|
||||
|
||||
// for all lines in between
|
||||
for(var n=selStart[0]+1; n < selEnd[0]; n++) {
|
||||
hasAttrib = hasAttrib && rangeHasAttrib([n, 0], [n, rep.lines.atIndex(n).text.length])
|
||||
}
|
||||
|
||||
// for the last, potentially partial, line
|
||||
hasAttrib = hasAttrib && rangeHasAttrib([selEnd[0], 0], [selEnd[0], selEnd[1]])
|
||||
|
||||
return hasAttrib
|
||||
}
|
||||
if(rep.selStart[1] == rep.selEnd[1] && rep.selStart[1] == rep.lines.atIndex(n).text.length){
|
||||
return false; // If we're at the end of a line we treat it as having no formatting
|
||||
}
|
||||
if(rep.selStart[1] == 0 && rep.selEnd[1] == 0){
|
||||
rep.selEnd[1] == 1;
|
||||
}
|
||||
if(rep.selEnd[1] == -1){
|
||||
rep.selEnd[1] = 1; // sometimes rep.selEnd is -1, not sure why.. When it is we should look at the first char
|
||||
}
|
||||
if (n == selStartLine)
|
||||
{
|
||||
selectionStartInLine = rep.selStart[1];
|
||||
}
|
||||
if (n == selEndLine)
|
||||
{
|
||||
selectionEndInLine = rep.selEnd[1];
|
||||
}
|
||||
while (opIter.hasNext())
|
||||
{
|
||||
|
||||
// Logic tells us we now have a range on a single line
|
||||
|
||||
var lineNum = selStart[0]
|
||||
, start = selStart[1]
|
||||
, end = selEnd[1]
|
||||
, hasAttrib = true
|
||||
|
||||
// Iterate over attribs on this line
|
||||
|
||||
var opIter = Changeset.opIterator(rep.alines[lineNum])
|
||||
, indexIntoLine = 0
|
||||
|
||||
while (opIter.hasNext()) {
|
||||
var op = opIter.next();
|
||||
var opStartInLine = indexIntoLine;
|
||||
var opEndInLine = opStartInLine + op.chars;
|
||||
if (!hasIt(op.attribs))
|
||||
{
|
||||
if (!hasIt(op.attribs)) {
|
||||
// does op overlap selection?
|
||||
if (!(opEndInLine <= selectionStartInLine || opStartInLine >= selectionEndInLine))
|
||||
{
|
||||
selectionAllHasIt = false;
|
||||
if (!(opEndInLine <= start || opStartInLine >= end)) {
|
||||
hasAttrib = false; // since it's overlapping but hasn't got the attrib -> range hasn't got it
|
||||
break;
|
||||
}
|
||||
}
|
||||
indexIntoLine = opEndInLine;
|
||||
}
|
||||
if (!selectionAllHasIt)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(selectionAllHasIt){
|
||||
return true;
|
||||
}else{
|
||||
return false;
|
||||
|
||||
return hasAttrib
|
||||
}
|
||||
}
|
||||
|
||||
editorInfo.ace_getAttributeOnSelection = getAttributeOnSelection;
|
||||
|
||||
function toggleAttributeOnSelection(attributeName)
|
||||
|
@ -3636,6 +3618,8 @@ function Ace2Inner(){
|
|||
var charCode = evt.charCode;
|
||||
var keyCode = evt.keyCode;
|
||||
var which = evt.which;
|
||||
var altKey = evt.altKey;
|
||||
var shiftKey = evt.shiftKey;
|
||||
|
||||
// prevent ESC key
|
||||
if (keyCode == 27)
|
||||
|
@ -3676,7 +3660,6 @@ function Ace2Inner(){
|
|||
if (keyCode == 13 && browser.opera && (type == "keypress")){
|
||||
return; // This stops double enters in Opera but double Tabs still show on single tab keypress, adding keyCode == 9 to this doesn't help as the event is fired twice
|
||||
}
|
||||
|
||||
var specialHandled = false;
|
||||
var isTypeForSpecialKey = ((browser.msie || browser.safari || browser.chrome) ? (type == "keydown") : (type == "keypress"));
|
||||
var isTypeForCmdKey = ((browser.msie || browser.safari || browser.chrome) ? (type == "keydown") : (type == "keypress"));
|
||||
|
@ -3707,6 +3690,101 @@ function Ace2Inner(){
|
|||
evt:evt
|
||||
});
|
||||
specialHandled = (specialHandledInHook&&specialHandledInHook.length>0)?specialHandledInHook[0]:specialHandled;
|
||||
if ((!specialHandled) && altKey && isTypeForSpecialKey && keyCode == 120){
|
||||
// Alt F9 focuses on the File Menu and/or editbar.
|
||||
// Note that while most editors use Alt F10 this is not desirable
|
||||
// As ubuntu cannot use Alt F10....
|
||||
// Focus on the editbar. -- TODO: Move Focus back to previous state (we know it so we can use it)
|
||||
var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first();
|
||||
$(this).blur();
|
||||
firstEditbarElement.focus();
|
||||
evt.preventDefault();
|
||||
}
|
||||
if ((!specialHandled) && altKey && keyCode == 67){
|
||||
// Alt c focuses on the Chat window
|
||||
$(this).blur();
|
||||
parent.parent.chat.show();
|
||||
parent.parent.chat.focus();
|
||||
evt.preventDefault();
|
||||
}
|
||||
if ((!specialHandled) && evt.ctrlKey && shiftKey && keyCode == 50 && type === "keydown"){
|
||||
// Control-Shift-2 shows a gritter popup showing a line author
|
||||
var lineNumber = rep.selEnd[0];
|
||||
var alineAttrs = rep.alines[lineNumber];
|
||||
var apool = rep.apool;
|
||||
|
||||
// TODO: support selection ranges
|
||||
// TODO: Still work when authorship colors have been cleared
|
||||
// TODO: i18n
|
||||
// TODO: There appears to be a race condition or so.
|
||||
|
||||
var author = null;
|
||||
if (alineAttrs) {
|
||||
var authors = [];
|
||||
var authorNames = [];
|
||||
var opIter = Changeset.opIterator(alineAttrs);
|
||||
|
||||
while (opIter.hasNext()){
|
||||
var op = opIter.next();
|
||||
authorId = Changeset.opAttributeValue(op, 'author', apool);
|
||||
|
||||
// Only push unique authors and ones with values
|
||||
if(authors.indexOf(authorId) === -1 && authorId !== ""){
|
||||
authors.push(authorId);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No author information is available IE on a new pad.
|
||||
if(authors.length === 0){
|
||||
var authorString = "No author information is available";
|
||||
}
|
||||
else{
|
||||
// Known authors info, both current and historical
|
||||
var padAuthors = parent.parent.pad.userList();
|
||||
var authorObj = {};
|
||||
authors.forEach(function(authorId){
|
||||
padAuthors.forEach(function(padAuthor){
|
||||
// If the person doing the lookup is the author..
|
||||
if(padAuthor.userId === authorId){
|
||||
if(parent.parent.clientVars.userId === authorId){
|
||||
authorObj = {
|
||||
name: "Me"
|
||||
}
|
||||
}else{
|
||||
authorObj = padAuthor;
|
||||
}
|
||||
}
|
||||
});
|
||||
if(!authorObj){
|
||||
author = "Unknown";
|
||||
return;
|
||||
}
|
||||
author = authorObj.name;
|
||||
if(!author) author = "Unknown";
|
||||
authorNames.push(author);
|
||||
})
|
||||
}
|
||||
if(authors.length === 1){
|
||||
var authorString = "The author of this line is " + authorNames;
|
||||
}
|
||||
if(authors.length > 1){
|
||||
var authorString = "The authors of this line are " + authorNames.join(" & ");
|
||||
}
|
||||
|
||||
parent.parent.$.gritter.add({
|
||||
// (string | mandatory) the heading of the notification
|
||||
title: 'Line Authors',
|
||||
// (string | mandatory) the text inside the notification
|
||||
text: authorString,
|
||||
// (bool | optional) if you want it to fade out on its own or just sit there
|
||||
sticky: false,
|
||||
// (int | optional) the time you want it to be alive for before fading out
|
||||
time: '4000'
|
||||
});
|
||||
}
|
||||
if ((!specialHandled) && isTypeForSpecialKey && keyCode == 8)
|
||||
{
|
||||
// "delete" key; in mozilla, if we're at the beginning of a line, normalize now,
|
||||
|
@ -4863,7 +4941,11 @@ function Ace2Inner(){
|
|||
$(document).on("keypress", handleKeyEvent);
|
||||
$(document).on("keyup", handleKeyEvent);
|
||||
$(document).on("click", handleClick);
|
||||
$(document).on("cut", handleCut);
|
||||
|
||||
// Disabled: https://github.com/ether/etherpad-lite/issues/2546
|
||||
// Will break OL re-numbering: https://github.com/ether/etherpad-lite/pull/2533
|
||||
// $(document).on("cut", handleCut);
|
||||
|
||||
$(root).on("blur", handleBlur);
|
||||
if (browser.msie)
|
||||
{
|
||||
|
|
|
@ -290,6 +290,11 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
|
|||
|
||||
$(document).keyup(function(e)
|
||||
{
|
||||
// If focus is on editbar, don't do anything
|
||||
var target = $(':focus');
|
||||
if($(target).parents(".toolbar").length === 1){
|
||||
return;
|
||||
}
|
||||
var code = -1;
|
||||
if (!e) var e = window.event;
|
||||
if (e.keyCode) code = e.keyCode;
|
||||
|
@ -330,7 +335,6 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
|
|||
}
|
||||
}
|
||||
else if (code == 32) playpause();
|
||||
|
||||
});
|
||||
|
||||
$(window).resize(function()
|
||||
|
|
|
@ -18,6 +18,7 @@ var padutils = require('./pad_utils').padutils;
|
|||
var padcookie = require('./pad_cookie').padcookie;
|
||||
var Tinycon = require('tinycon/tinycon');
|
||||
var hooks = require('./pluginfw/hooks');
|
||||
var padeditor = require('./pad_editor').padeditor;
|
||||
|
||||
var chat = (function()
|
||||
{
|
||||
|
@ -36,6 +37,14 @@ var chat = (function()
|
|||
chatMentions = 0;
|
||||
Tinycon.setBubble(0);
|
||||
},
|
||||
focus: function ()
|
||||
{
|
||||
// I'm not sure why we need a setTimeout here but without it we don't get focus...
|
||||
// Animation maybe?
|
||||
setTimeout(function(){
|
||||
$("#chatinput").focus();
|
||||
},100);
|
||||
},
|
||||
stickToScreen: function(fromInitialCall) // Make chat stick to right hand side of screen
|
||||
{
|
||||
chat.show();
|
||||
|
@ -205,8 +214,28 @@ var chat = (function()
|
|||
init: function(pad)
|
||||
{
|
||||
this._pad = pad;
|
||||
$("#chatinput").keypress(function(evt)
|
||||
$("#chatinput").keyup(function(evt)
|
||||
{
|
||||
// If the event is Alt C or Escape & we're already in the chat menu
|
||||
// Send the users focus back to the pad
|
||||
if((evt.altKey == true && evt.which === 67) || evt.which === 27){
|
||||
// If we're in chat already..
|
||||
$(':focus').blur(); // required to do not try to remove!
|
||||
padeditor.ace.focus(); // Sends focus back to pad
|
||||
}
|
||||
});
|
||||
|
||||
$('body:not(#chatinput)').on("keydown", function(evt){
|
||||
if (evt.altKey && evt.which == 67){
|
||||
// Alt c focuses on the Chat window
|
||||
$(this).blur();
|
||||
parent.parent.chat.show();
|
||||
parent.parent.chat.focus();
|
||||
evt.preventDefault();
|
||||
}
|
||||
});
|
||||
|
||||
$("#chatinput").keypress(function(evt){
|
||||
//if the user typed enter, fire the send
|
||||
if(evt.which == 13 || evt.which == 10)
|
||||
{
|
||||
|
|
|
@ -297,7 +297,23 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
{
|
||||
if (state.attribs[a])
|
||||
{
|
||||
lst.push([a, 'true']);
|
||||
// The following splitting of the attribute name is a workaround
|
||||
// to enable the content collector to store key-value attributes
|
||||
// see https://github.com/ether/etherpad-lite/issues/2567 for more information
|
||||
// in long term the contentcollector should be refactored to get rid of this workaround
|
||||
var ATTRIBUTE_SPLIT_STRING = "::";
|
||||
|
||||
// see if attributeString is splittable
|
||||
var attributeSplits = a.split(ATTRIBUTE_SPLIT_STRING);
|
||||
if (attributeSplits.length > 1) {
|
||||
// the attribute name follows the convention key::value
|
||||
// so save it as a key value attribute
|
||||
lst.push([attributeSplits[0], attributeSplits[1]]);
|
||||
} else {
|
||||
// the "normal" case, the attribute is just a switch
|
||||
// so set it true
|
||||
lst.push([a, 'true']);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (state.authorLevel > 0)
|
||||
|
@ -571,7 +587,9 @@ function makeContentCollector(collectStyles, abrowser, apool, domInterface, clas
|
|||
}
|
||||
else if ((tname == "div" || tname == "p") && cls && cls.match(/(?:^| )ace-line\b/))
|
||||
{
|
||||
oldListTypeOrNull = (_enterList(state, type) || 'none');
|
||||
// This has undesirable behavior in Chrome but is right in other browsers.
|
||||
// See https://github.com/ether/etherpad-lite/issues/2412 for reasoning
|
||||
if(!abrowser.chrome) oldListTypeOrNull = (_enterList(state, type) || 'none');
|
||||
}
|
||||
if (className2Author && cls)
|
||||
{
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
_tpl_close: '<div class="gritter-close"></div>',
|
||||
_tpl_title: '<span class="gritter-title">[[title]]</span>',
|
||||
_tpl_item: '<div id="gritter-item-[[number]]" class="gritter-item-wrapper [[item_class]]" style="display:none"><div class="gritter-top"></div><div class="gritter-item">[[close]][[image]]<div class="[[class_name]]">[[title]]<p>[[text]]</p></div><div style="clear:both"></div></div><div class="gritter-bottom"></div></div>',
|
||||
_tpl_wrap: '<div id="gritter-notice-wrapper"></div>',
|
||||
_tpl_wrap: '<div id="gritter-notice-wrapper" aria-live="polite" aria-atomic="false" aria-relevant="additions" role="log"></div>',
|
||||
|
||||
/**
|
||||
* Add a gritter notification to the screen
|
||||
|
|
|
@ -110,7 +110,7 @@ function randomString()
|
|||
// callback: the function to call when all above succeeds, `val` is the value supplied by the user
|
||||
var getParameters = [
|
||||
{ name: "noColors", checkVal: "true", callback: function(val) { settings.noColors = true; $('#clearAuthorship').hide(); } },
|
||||
{ name: "showControls", checkVal: "false", callback: function(val) { $('#editbar').hide(); $('#editorcontainer').css({"top":"0px"}); } },
|
||||
{ name: "showControls", checkVal: "false", callback: function(val) { $('#editbar').addClass('hideControlsEditbar'); $('#editorcontainer').addClass('hideControlsEditor'); } },
|
||||
{ name: "showChat", checkVal: "false", callback: function(val) { $('#chaticon').hide(); } },
|
||||
{ name: "showLineNumbers", checkVal: "false", callback: function(val) { settings.LineNumbersDisabled = true; } },
|
||||
{ name: "useMonospaceFont", checkVal: "true", callback: function(val) { settings.useMonospaceFontGlobal = true; } },
|
||||
|
@ -433,6 +433,10 @@ var pad = {
|
|||
{
|
||||
return pad.myUserInfo.name;
|
||||
},
|
||||
userList: function()
|
||||
{
|
||||
return paduserlist.users();
|
||||
},
|
||||
sendClientReady: function(isReconnect, messageType)
|
||||
{
|
||||
messageType = typeof messageType !== 'undefined' ? messageType : 'CLIENT_READY';
|
||||
|
@ -576,9 +580,18 @@ var pad = {
|
|||
if(padcookie.getPref("rtlIsTrue") == true){
|
||||
pad.changeViewOption('rtlIsTrue', true);
|
||||
}
|
||||
if(padcookie.getPref("useMonospaceFont") == true){
|
||||
pad.changeViewOption('useMonospaceFont', true);
|
||||
}
|
||||
|
||||
var fonts = ['useMonospaceFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', 'useGeorgiaFont', 'useImpactFont',
|
||||
'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useTahomaFont', 'useTimesNewRomanFont',
|
||||
'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', 'useWingDingsFont', 'useSansSerifFont',
|
||||
'useSerifFont'];
|
||||
|
||||
$.each(fonts, function(i, font){
|
||||
if(padcookie.getPref(font) == true){
|
||||
pad.changeViewOption(font, true);
|
||||
}
|
||||
})
|
||||
|
||||
hooks.aCallAll("postAceInit", {ace: padeditor.ace, pad: pad});
|
||||
}
|
||||
},
|
||||
|
|
|
@ -63,6 +63,7 @@ ToolbarItem.prototype.bind = function (callback) {
|
|||
|
||||
if (self.isButton()) {
|
||||
self.$el.click(function (event) {
|
||||
$(':focus').blur();
|
||||
callback(self.getCommand(), self);
|
||||
event.preventDefault();
|
||||
});
|
||||
|
@ -155,6 +156,10 @@ var padeditbar = (function()
|
|||
});
|
||||
});
|
||||
|
||||
$('body:not(#editorcontainerbox)').on("keydown", function(evt){
|
||||
bodyKeyEvent(evt);
|
||||
});
|
||||
|
||||
$('#editbar').show();
|
||||
|
||||
this.redrawHeight();
|
||||
|
@ -300,6 +305,72 @@ var padeditbar = (function()
|
|||
}
|
||||
};
|
||||
|
||||
var editbarPosition = 0;
|
||||
|
||||
function bodyKeyEvent(evt){
|
||||
|
||||
// If the event is Alt F9 or Escape & we're already in the editbar menu
|
||||
// Send the users focus back to the pad
|
||||
if((evt.keyCode === 120 && evt.altKey) || evt.keyCode === 27){
|
||||
if($(':focus').parents(".toolbar").length === 1){
|
||||
// If we're in the editbar already..
|
||||
// Close any dropdowns we have open..
|
||||
padeditbar.toggleDropDown("none");
|
||||
// Check we're on a pad and not on the timeslider
|
||||
// Or some other window I haven't thought about!
|
||||
if(typeof pad === 'undefined'){
|
||||
// Timeslider probably..
|
||||
// Shift focus away from any drop downs
|
||||
$(':focus').blur(); // required to do not try to remove!
|
||||
$('#padmain').focus(); // Focus back onto the pad
|
||||
}else{
|
||||
// Shift focus away from any drop downs
|
||||
$(':focus').blur(); // required to do not try to remove!
|
||||
padeditor.ace.focus(); // Sends focus back to pad
|
||||
// The above focus doesn't always work in FF, you have to hit enter afterwards
|
||||
evt.preventDefault();
|
||||
}
|
||||
}else{
|
||||
// Focus on the editbar :)
|
||||
var firstEditbarElement = parent.parent.$('#editbar').children("ul").first().children().first().children().first().children().first();
|
||||
$(this).blur();
|
||||
firstEditbarElement.focus();
|
||||
evt.preventDefault();
|
||||
}
|
||||
}
|
||||
// Are we in the toolbar??
|
||||
if($(':focus').parents(".toolbar").length === 1){
|
||||
// On arrow keys go to next/previous button item in editbar
|
||||
if(evt.keyCode !== 39 && evt.keyCode !== 37) return;
|
||||
|
||||
// Get all the focusable items in the editbar
|
||||
var focusItems = $('#editbar').find('button, select');
|
||||
|
||||
// On left arrow move to next button in editbar
|
||||
if(evt.keyCode === 37){
|
||||
// If a dropdown is visible or we're in an input don't move to the next button
|
||||
if($('.popup').is(":visible") || evt.target.localName === "input") return;
|
||||
|
||||
editbarPosition--;
|
||||
// Allow focus to shift back to end of row and start of row
|
||||
if(editbarPosition === -1) editbarPosition = focusItems.length -1;
|
||||
$(focusItems[editbarPosition]).focus()
|
||||
}
|
||||
|
||||
// On right arrow move to next button in editbar
|
||||
if(evt.keyCode === 39){
|
||||
// If a dropdown is visible or we're in an input don't move to the next button
|
||||
if($('.popup').is(":visible") || evt.target.localName === "input") return;
|
||||
|
||||
editbarPosition++;
|
||||
// Allow focus to shift back to end of row and start of row
|
||||
if(editbarPosition >= focusItems.length) editbarPosition = 0;
|
||||
$(focusItems[editbarPosition]).focus();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function aceAttributeCommand(cmd, ace) {
|
||||
ace.ace_toggleAttributeOnSelection(cmd);
|
||||
}
|
||||
|
@ -311,10 +382,36 @@ var padeditbar = (function()
|
|||
toolbar.registerDropdownCommand("import_export");
|
||||
toolbar.registerDropdownCommand("embed");
|
||||
|
||||
toolbar.registerCommand("settings", function () {
|
||||
toolbar.toggleDropDown("settings", function(){
|
||||
$('#options-stickychat').focus();
|
||||
});
|
||||
});
|
||||
|
||||
toolbar.registerCommand("import_export", function () {
|
||||
toolbar.toggleDropDown("import_export", function(){
|
||||
// If Import file input exists then focus on it..
|
||||
if($('#importfileinput').length !== 0){
|
||||
setTimeout(function(){
|
||||
$('#importfileinput').focus();
|
||||
}, 100);
|
||||
}else{
|
||||
$('.exportlink').first().focus();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
toolbar.registerCommand("showusers", function () {
|
||||
toolbar.toggleDropDown("users", function(){
|
||||
$('#myusernameedit').focus();
|
||||
});
|
||||
});
|
||||
|
||||
toolbar.registerCommand("embed", function () {
|
||||
toolbar.setEmbedLinks();
|
||||
$('#linkinput').focus().select();
|
||||
toolbar.toggleDropDown("embed");
|
||||
toolbar.toggleDropDown("embed", function(){
|
||||
$('#linkinput').focus().select();
|
||||
});
|
||||
});
|
||||
|
||||
toolbar.registerCommand("savedRevision", function () {
|
||||
|
|
|
@ -28,6 +28,13 @@ var padeditor = (function()
|
|||
var Ace2Editor = undefined;
|
||||
var pad = undefined;
|
||||
var settings = undefined;
|
||||
|
||||
// Array of available fonts
|
||||
var fonts = ['useMonospaceFont', 'useOpenDyslexicFont', 'useComicSansFont', 'useCourierNewFont', 'useGeorgiaFont', 'useImpactFont',
|
||||
'useLucidaFont', 'useLucidaSansFont', 'usePalatinoFont', 'useTahomaFont', 'useTimesNewRomanFont',
|
||||
'useTrebuchetFont', 'useVerdanaFont', 'useSymbolFont', 'useWebdingsFont', 'useWingDingsFont', 'useSansSerifFont',
|
||||
'useSerifFont'];
|
||||
|
||||
var self = {
|
||||
ace: null,
|
||||
// this is accessed directly from other files
|
||||
|
@ -85,10 +92,15 @@ var padeditor = (function()
|
|||
padutils.setCheckbox($("#options-rtlcheck"), ('rtl' == html10n.getDirection()));
|
||||
})
|
||||
|
||||
// font face
|
||||
// font family change
|
||||
$("#viewfontmenu").change(function()
|
||||
{
|
||||
pad.changeViewOption('useMonospaceFont', $("#viewfontmenu").val() == 'monospace');
|
||||
$.each(fonts, function(i, font){
|
||||
var sfont = font.replace("use","");
|
||||
sfont = sfont.replace("Font","");
|
||||
sfont = sfont.toLowerCase();
|
||||
pad.changeViewOption(font, $("#viewfontmenu").val() == sfont);
|
||||
});
|
||||
});
|
||||
|
||||
// Language
|
||||
|
@ -98,12 +110,12 @@ var padeditor = (function()
|
|||
// this does not interfere with html10n's normal value-setting because html10n just ingores <input>s
|
||||
// also, a value which has been set by the user will be not overwritten since a user-edited <input>
|
||||
// does *not* have the editempty-class
|
||||
$('input[data-l10n-id]').each(function(key, input)
|
||||
{
|
||||
input = $(input);
|
||||
if(input.hasClass("editempty"))
|
||||
input.val(html10n.get(input.attr("data-l10n-id")));
|
||||
});
|
||||
$('input[data-l10n-id]').each(function(key, input){
|
||||
input = $(input);
|
||||
if(input.hasClass("editempty")){
|
||||
input.val(html10n.get(input.attr("data-l10n-id")));
|
||||
}
|
||||
});
|
||||
})
|
||||
$("#languagemenu").val(html10n.getLanguage());
|
||||
$("#languagemenu").change(function() {
|
||||
|
@ -136,13 +148,49 @@ var padeditor = (function()
|
|||
v = getOption('showAuthorColors', true);
|
||||
self.ace.setProperty("showsauthorcolors", v);
|
||||
padutils.setCheckbox($("#options-colorscheck"), v);
|
||||
// Override from parameters if true
|
||||
if (settings.noColors !== false)
|
||||
self.ace.setProperty("showsauthorcolors", !settings.noColors);
|
||||
|
||||
v = getOption('useMonospaceFont', false);
|
||||
self.ace.setProperty("textface", (v ? "monospace" : "Arial, sans-serif"));
|
||||
$("#viewfontmenu").val(v ? "monospace" : "normal");
|
||||
// Override from parameters if true
|
||||
if (settings.noColors !== false){
|
||||
self.ace.setProperty("showsauthorcolors", !settings.noColors);
|
||||
}
|
||||
|
||||
var normalFont = true;
|
||||
// Go through each font and see if the option is set..
|
||||
$.each(fonts, function(i, font){
|
||||
var isEnabled = getOption(font, false);
|
||||
if(isEnabled){
|
||||
font = font.replace("use","");
|
||||
font = font.replace("Font","");
|
||||
font = font.toLowerCase();
|
||||
if(font === "monospace") self.ace.setProperty("textface", "Courier new");
|
||||
if(font === "opendyslexic") self.ace.setProperty("textface", "OpenDyslexic");
|
||||
if(font === "comicsans") self.ace.setProperty("textface", "Comic Sans MS");
|
||||
if(font === "georgia") self.ace.setProperty("textface", "Georgia");
|
||||
if(font === "impact") self.ace.setProperty("textface", "Impact");
|
||||
if(font === "lucida") self.ace.setProperty("textface", "Lucida");
|
||||
if(font === "lucidasans") self.ace.setProperty("textface", "Lucida Sans Unicode");
|
||||
if(font === "palatino") self.ace.setProperty("textface", "Palatino Linotype");
|
||||
if(font === "tahoma") self.ace.setProperty("textface", "Tahoma");
|
||||
if(font === "timesnewroman") self.ace.setProperty("textface", "Times New Roman");
|
||||
if(font === "trebuchet") self.ace.setProperty("textface", "Trebuchet MS");
|
||||
if(font === "verdana") self.ace.setProperty("textface", "Verdana");
|
||||
if(font === "symbol") self.ace.setProperty("textface", "Symbol");
|
||||
if(font === "webdings") self.ace.setProperty("textface", "Webdings");
|
||||
if(font === "wingdings") self.ace.setProperty("textface", "Wingdings");
|
||||
if(font === "sansserif") self.ace.setProperty("textface", "MS Sans Serif");
|
||||
if(font === "serif") self.ace.setProperty("textface", "MS Serif");
|
||||
|
||||
// $("#viewfontmenu").val(font);
|
||||
normalFont = false;
|
||||
}
|
||||
});
|
||||
|
||||
// No font has been previously selected so use the Normal font
|
||||
if(normalFont){
|
||||
self.ace.setProperty("textface", "Arial, sans-serif");
|
||||
// $("#viewfontmenu").val("normal");
|
||||
}
|
||||
|
||||
},
|
||||
dispose: function()
|
||||
{
|
||||
|
|
|
@ -508,6 +508,30 @@ var paduserlist = (function()
|
|||
});
|
||||
//
|
||||
},
|
||||
users: function(){
|
||||
// Returns an object of users who have been on this pad
|
||||
// Firstly we have to get live data..
|
||||
var userList = otherUsersInfo;
|
||||
// Now we need to add ourselves..
|
||||
userList.push(myUserInfo);
|
||||
// Now we add historical authors
|
||||
var historical = clientVars.collab_client_vars.historicalAuthorData;
|
||||
for (var key in historical){
|
||||
var userId = historical[key].userId;
|
||||
// Check we don't already have this author in our array
|
||||
var exists = false;
|
||||
|
||||
userList.forEach(function(user){
|
||||
if(user.userId === userId) exists = true;
|
||||
});
|
||||
|
||||
if(exists === false){
|
||||
userList.push(historical[key]);
|
||||
}
|
||||
|
||||
}
|
||||
return userList;
|
||||
},
|
||||
setMyUserInfo: function(info)
|
||||
{
|
||||
//translate the colorId
|
||||
|
|
|
@ -157,6 +157,38 @@ function handleClientVars(message)
|
|||
fireWhenAllScriptsAreLoaded[i]();
|
||||
}
|
||||
$("#ui-slider-handle").css('left', $("#ui-slider-bar").width() - 2);
|
||||
|
||||
// Translate some strings where we only want to set the title not the actual values
|
||||
$('#playpause_button_icon').attr("title", html10n.get("timeslider.playPause"));
|
||||
$('#leftstep').attr("title", html10n.get("timeslider.backRevision"));
|
||||
$('#rightstep').attr("title", html10n.get("timeslider.forwardRevision"));
|
||||
|
||||
// font family change
|
||||
$("#viewfontmenu").change(function(){
|
||||
var font = $("#viewfontmenu").val();
|
||||
if(font === "monospace") setFont("Courier new");
|
||||
if(font === "opendyslexic") setFont("OpenDyslexic");
|
||||
if(font === "comicsans") setFont("Comic Sans MS");
|
||||
if(font === "georgia") setFont("Georgia");
|
||||
if(font === "impact") setFont("Impact");
|
||||
if(font === "lucida") setFont("Lucida");
|
||||
if(font === "lucidasans") setFont("Lucida Sans Unicode");
|
||||
if(font === "palatino") setFont("Palatino Linotype");
|
||||
if(font === "tahoma") setFont("Tahoma");
|
||||
if(font === "timesnewroman") setFont("Times New Roman");
|
||||
if(font === "trebuchet") setFont("Trebuchet MS");
|
||||
if(font === "verdana") setFont("Verdana");
|
||||
if(font === "symbol") setFont("Symbol");
|
||||
if(font === "webdings") setFont("Webdings");
|
||||
if(font === "wingdings") setFont("Wingdings");
|
||||
if(font === "sansserif") setFont("MS Sans Serif");
|
||||
if(font === "serif") setFont("MS Serif");
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
function setFont(font){
|
||||
$('#padcontent').css("font-family", font);
|
||||
}
|
||||
|
||||
exports.baseURL = '';
|
||||
|
|
|
@ -22,8 +22,9 @@
|
|||
</div>
|
||||
|
||||
<div class="innerwrapper">
|
||||
<h2>Etherpad Git Commit</h2>
|
||||
<p><a href='https://github.com/ether/etherpad-lite/commit/<%= gitCommit %>'><%= gitCommit %></a></p>
|
||||
<h2>Etherpad version</h2>
|
||||
<p>Version number: <%= epVersion %></p>
|
||||
<p>Git sha: <a href='https://github.com/ether/etherpad-lite/commit/<%= gitCommit %>'><%= gitCommit %></a></p>
|
||||
|
||||
<h2>Installed plugins</h2>
|
||||
<pre><%- plugins.formatPlugins().replace(", ","\n") %></pre>
|
||||
|
|
|
@ -70,9 +70,10 @@
|
|||
}
|
||||
#button {
|
||||
margin: 0 auto;
|
||||
border-radius: 3px;
|
||||
text-align: center;
|
||||
font: 36px verdana,arial,sans-serif;
|
||||
width:300px;
|
||||
border:none;
|
||||
color: white;
|
||||
text-shadow: 0 -1px 0 rgba(0,0,0,.8);
|
||||
height: 70px;
|
||||
|
@ -100,6 +101,7 @@
|
|||
text-align: left;
|
||||
text-shadow: 0 1px 1px #fff;
|
||||
margin: 16px auto 0;
|
||||
display:block;
|
||||
}
|
||||
#padname{
|
||||
height:38px;
|
||||
|
@ -158,8 +160,8 @@
|
|||
<div id="wrapper">
|
||||
<% e.begin_block("indexWrapper"); %>
|
||||
<div id="inner">
|
||||
<div id="button" onclick="go2Random()" data-l10n-id="index.newPad"></div>
|
||||
<div id="label" data-l10n-id="index.createOpenPad"></div>
|
||||
<buttOn id="button" onclick="go2Random()" data-l10n-id="index.newPad"></button>
|
||||
<label id="label" for="padname" data-l10n-id="index.createOpenPad"></label>
|
||||
<form action="#" onsubmit="go2Name();return false;">
|
||||
<input type="text" id="padname" autofocus x-webkit-speech>
|
||||
<button type="submit">OK</button>
|
||||
|
|
|
@ -56,17 +56,17 @@
|
|||
<!-- head and body had been removed intentionally -->
|
||||
|
||||
<% e.begin_block("body"); %>
|
||||
<div id="editbar" class="toolbar">
|
||||
<div id="editbar" class="toolbar" title="Toolbar (Alt F9)">
|
||||
<div id="overlay">
|
||||
<div id="overlay-inner"></div>
|
||||
</div>
|
||||
|
||||
<ul class="menu_left">
|
||||
<ul class="menu_left" role="toolbar">
|
||||
<% e.begin_block("editbarMenuLeft"); %>
|
||||
<%- toolbar.menu(settings.toolbar.left) %>
|
||||
<% e.end_block(); %>
|
||||
</ul>
|
||||
<ul class="menu_right">
|
||||
<ul class="menu_right" role="toolbar">
|
||||
<% e.begin_block("editbarMenuRight"); %>
|
||||
<%- toolbar.menu(settings.toolbar.right) %>
|
||||
<% e.end_block(); %>
|
||||
|
@ -88,7 +88,7 @@
|
|||
<div id="myusernameform"><input type="text" id="myusernameedit" disabled="disabled" data-l10n-id="pad.userlist.entername"></div>
|
||||
<div id="mystatusform"><input type="text" id="mystatusedit" disabled="disabled"></div>
|
||||
</div>
|
||||
<div id="otherusers">
|
||||
<div id="otherusers" aria-role="document">
|
||||
<div id="guestprompts"></div>
|
||||
<table id="otheruserstable" cellspacing="0" cellpadding="0" border="0">
|
||||
<tr><td></td></tr>
|
||||
|
@ -160,6 +160,22 @@
|
|||
<select id="viewfontmenu">
|
||||
<option value="normal" data-l10n-id="pad.settings.fontType.normal"></option>
|
||||
<option value="monospace" data-l10n-id="pad.settings.fontType.monospaced"></option>
|
||||
<option value="opendyslexic" data-l10n-id="pad.settings.fontType.opendyslexic"></option>
|
||||
<option value="comicsans" data-l10n-id="pad.settings.fontType.comicsans"></option>
|
||||
<option value="georgia" data-l10n-id="pad.settings.fontType.georgia"></option>
|
||||
<option value="impact" data-l10n-id="pad.settings.fontType.impact"></option>
|
||||
<option value="lucida" data-l10n-id="pad.settings.fontType.lucida"></option>
|
||||
<option value="lucidasans" data-l10n-id="pad.settings.fontType.lucidasans"></option>
|
||||
<option value="palatino" data-l10n-id="pad.settings.fontType.palatino"></option>
|
||||
<option value="tahoma" data-l10n-id="pad.settings.fontType.tahoma"></option>
|
||||
<option value="timesnewroman" data-l10n-id="pad.settings.fontType.timesnewroman"></option>
|
||||
<option value="trebuchet" data-l10n-id="pad.settings.fontType.trebuchet"></option>
|
||||
<option value="verdana" data-l10n-id="pad.settings.fontType.verdana"></option>
|
||||
<option value="symbol" data-l10n-id="pad.settings.fontType.symbol"></option>
|
||||
<option value="webdings" data-l10n-id="pad.settings.fontType.webdings"></option>
|
||||
<option value="wingdings" data-l10n-id="pad.settings.fontType.wingdings"></option>
|
||||
<option value="sansserif" data-l10n-id="pad.settings.fontType.sansserif"></option>
|
||||
<option value="serif" data-l10n-id="pad.settings.fontType.serif"></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -306,7 +322,7 @@
|
|||
<% e.end_block(); %>
|
||||
</div>
|
||||
|
||||
<div id="chaticon" onclick="chat.show();return false;">
|
||||
<div id="chaticon" onclick="chat.show();return false;" title="Chat (Alt C)">
|
||||
<span id="chatlabel" data-l10n-id="pad.chat"></span>
|
||||
<span class="buttonicon buttonicon-chat"></span>
|
||||
<span id="chatcounter">0</span>
|
||||
|
@ -317,7 +333,7 @@
|
|||
<a id="titlecross" onClick="chat.hide();return false;">- </a>
|
||||
<a id="titlesticky" onClick="chat.stickToScreen(true);$('#options-stickychat').prop('checked', true);return false;" title="Stick chat to screen">█ </a>
|
||||
</div>
|
||||
<div id="chattext" class="authorColors">
|
||||
<div id="chattext" class="authorColors" aria-live="polite" aria-relevant="additions removals text" role="log" aria-atomic="false">
|
||||
<div alt="loading.." id="chatloadmessagesball" class="chatloadmessages loadingAnimation" align="top"></div>
|
||||
<button id="chatloadmessagesbutton" class="chatloadmessages" data-l10n-id="pad.chat.loadmessages"></button>
|
||||
</div>
|
||||
|
|
|
@ -61,11 +61,11 @@
|
|||
<div id="ui-slider-bar"></div>
|
||||
</div>
|
||||
<div id="playpause_button">
|
||||
<div id="playpause_button_icon" class=""></div>
|
||||
<button id="playpause_button_icon" class=""></button>
|
||||
</div>
|
||||
<div id="steppers">
|
||||
<div class="stepper" id="leftstep"></div>
|
||||
<div class="stepper" id="rightstep"></div>
|
||||
<button class="stepper" id="leftstep"></button>
|
||||
<button class="stepper" id="rightstep"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -176,11 +176,41 @@
|
|||
<% e.end_block(); %>
|
||||
</div>
|
||||
|
||||
<div class="popup" id="settings">
|
||||
<tr>
|
||||
<td>
|
||||
<label for="viewfontmenu" data-l10n-id="pad.settings.fontType">Font type:</label>
|
||||
</td>
|
||||
<td>
|
||||
<select id="viewfontmenu">
|
||||
<option value="normal" data-l10n-id="pad.settings.fontType.normal"></option>
|
||||
<option value="monospace" data-l10n-id="pad.settings.fontType.monospaced"></option>
|
||||
<option value="opendyslexic" data-l10n-id="pad.settings.fontType.opendyslexic"></option>
|
||||
<option value="comicsans" data-l10n-id="pad.settings.fontType.comicsans"></option>
|
||||
<option value="georgia" data-l10n-id="pad.settings.fontType.georgia"></option>
|
||||
<option value="impact" data-l10n-id="pad.settings.fontType.impact"></option>
|
||||
<option value="lucida" data-l10n-id="pad.settings.fontType.lucida"></option>
|
||||
<option value="lucidasans" data-l10n-id="pad.settings.fontType.lucidasans"></option>
|
||||
<option value="palatino" data-l10n-id="pad.settings.fontType.palatino"></option>
|
||||
<option value="tahoma" data-l10n-id="pad.settings.fontType.tahoma"></option>
|
||||
<option value="timesnewroman" data-l10n-id="pad.settings.fontType.timesnewroman"></option>
|
||||
<option value="trebuchet" data-l10n-id="pad.settings.fontType.trebuchet"></option>
|
||||
<option value="verdana" data-l10n-id="pad.settings.fontType.verdana"></option>
|
||||
<option value="symbol" data-l10n-id="pad.settings.fontType.symbol"></option>
|
||||
<option value="webdings" data-l10n-id="pad.settings.fontType.webdings"></option>
|
||||
<option value="wingdings" data-l10n-id="pad.settings.fontType.wingdings"></option>
|
||||
<option value="sansserif" data-l10n-id="pad.settings.fontType.sansserif"></option>
|
||||
<option value="serif" data-l10n-id="pad.settings.fontType.serif"></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</div>
|
||||
|
||||
<!-- export code -->
|
||||
<div id="import_export">
|
||||
|
||||
<div id="export" class="popup">
|
||||
<p data-l10n-id="timeslider.exportCurrent"></p>
|
||||
<a id="exportetherpada" target="_blank" class="exportlink"><div class="exporttype" id="exportetherpad" data-l10n-id="pad.importExport.exportetherpad"></div></a>
|
||||
<a id="exporthtmla" target="_blank" class="exportlink"><div class="exporttype" id="exporthtml" data-l10n-id="pad.importExport.exporthtml"></div></a>
|
||||
<a id="exportplaina" target="_blank" class="exportlink"><div class="exporttype" id="exportplain" data-l10n-id="pad.importExport.exportplain"></div></a>
|
||||
<a id="exportworda" target="_blank" class="exportlink"><div class="exporttype" id="exportword" data-l10n-id="pad.importExport.exportword"></div></a>
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
var assert = require('assert')
|
||||
supertest = require(__dirname+'/../../../../src/node_modules/supertest'),
|
||||
fs = require('fs'),
|
||||
api = supertest('http://localhost:9001');
|
||||
path = require('path');
|
||||
|
||||
var filePath = path.join(__dirname, '../../../../APIKEY.txt');
|
||||
|
||||
var apiKey = fs.readFileSync(filePath, {encoding: 'utf-8'});
|
||||
apiKey = apiKey.replace(/\n$/, "");
|
||||
var apiVersion = 1;
|
||||
var authorID = "";
|
||||
var padID = makeid();
|
||||
var timestamp = Date.now();
|
||||
|
||||
describe('API Versioning', function(){
|
||||
it('errors if can not connect', function(done) {
|
||||
api.get('/api/')
|
||||
.expect(function(res){
|
||||
apiVersion = res.body.currentVersion;
|
||||
if (!res.body.currentVersion) throw new Error("No version set in API");
|
||||
return;
|
||||
})
|
||||
.expect(200, done)
|
||||
});
|
||||
})
|
||||
|
||||
// BEGIN GROUP AND AUTHOR TESTS
|
||||
/////////////////////////////////////
|
||||
/////////////////////////////////////
|
||||
|
||||
/* Tests performed
|
||||
-> createPad(padID)
|
||||
-> createAuthor([name]) -- should return an authorID
|
||||
-> appendChatMessage(padID, text, authorID, time)
|
||||
-> getChatHead(padID)
|
||||
-> getChatHistory(padID)
|
||||
*/
|
||||
|
||||
describe('createPad', function(){
|
||||
it('creates a new Pad', function(done) {
|
||||
api.get(endPoint('createPad')+"&padID="+padID)
|
||||
.expect(function(res){
|
||||
if(res.body.code !== 0) throw new Error("Unable to create new Pad");
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done)
|
||||
});
|
||||
})
|
||||
|
||||
describe('createAuthor', function(){
|
||||
it('Creates an author with a name set', function(done) {
|
||||
api.get(endPoint('createAuthor'))
|
||||
.expect(function(res){
|
||||
if(res.body.code !== 0 || !res.body.data.authorID) throw new Error("Unable to create author");
|
||||
authorID = res.body.data.authorID; // we will be this author for the rest of the tests
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done)
|
||||
});
|
||||
})
|
||||
|
||||
describe('appendChatMessage', function(){
|
||||
it('Adds a chat message to the pad', function(done) {
|
||||
api.get(endPoint('appendChatMessage')+"&padID="+padID+"&text=blalblalbha&authorID="+authorID+"&time="+timestamp)
|
||||
.expect(function(res){
|
||||
if(res.body.code !== 0) throw new Error("Unable to create chat message");
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done)
|
||||
});
|
||||
})
|
||||
|
||||
|
||||
describe('getChatHead', function(){
|
||||
it('Gets the head of chat', function(done) {
|
||||
api.get(endPoint('getChatHead')+"&padID="+padID)
|
||||
.expect(function(res){
|
||||
if(res.body.data.chatHead !== 0) throw new Error("Chat Head Length is wrong");
|
||||
|
||||
if(res.body.code !== 0) throw new Error("Unable to get chat head");
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done)
|
||||
});
|
||||
})
|
||||
|
||||
describe('getChatHistory', function(){
|
||||
it('Gets Chat History of a Pad', function(done) {
|
||||
api.get(endPoint('getChatHistory')+"&padID="+padID)
|
||||
.expect(function(res){
|
||||
if(res.body.data.messages.length !== 1) throw new Error("Chat History Length is wrong");
|
||||
if(res.body.code !== 0) throw new Error("Unable to get chat history");
|
||||
})
|
||||
.expect('Content-Type', /json/)
|
||||
.expect(200, done)
|
||||
});
|
||||
})
|
||||
|
||||
var endPoint = function(point){
|
||||
return '/api/'+apiVersion+'/'+point+'?apikey='+apiKey;
|
||||
}
|
||||
|
||||
function makeid()
|
||||
{
|
||||
var text = "";
|
||||
var possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
|
||||
for( var i=0; i < 5; i++ ){
|
||||
text += possible.charAt(Math.floor(Math.random() * possible.length));
|
||||
}
|
||||
return text;
|
||||
}
|
|
@ -47,6 +47,11 @@ describe("clear authorship colors button", function(){
|
|||
var hasAuthorClass = inner$("div").first().attr("class").indexOf("author") !== -1;
|
||||
expect(hasAuthorClass).to.be(false);
|
||||
|
||||
setTimeout(function(){
|
||||
var disconnectVisible = chrome$("div.disconnected").attr("class").indexOf("visible") === -1
|
||||
expect(disconnectVisible).to.be(true);
|
||||
},1000);
|
||||
|
||||
done();
|
||||
});
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ describe("font select", function(){
|
|||
|
||||
//check if font changed to monospace
|
||||
var fontFamily = inner$("body").css("font-family").toLowerCase();
|
||||
expect(fontFamily).to.be("monospace");
|
||||
expect(fontFamily).to.be("courier new");
|
||||
|
||||
done();
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue