Merge branch 'release/1.2.8'

This commit is contained in:
John McLear 2013-03-05 13:39:53 +00:00
commit cb2d148734
95 changed files with 7555 additions and 6982 deletions

View file

@ -1,3 +1,39 @@
# 1.2.8
! IMPORTANT: New setting.json value is required to automatically reconnect clients on disconnect
* NEW: Use Socket IO for rooms (allows for pads to be load balanced with sticky rooms)
* NEW: Plugins can now provide their own frontend tests
* NEW: Improved server-side logging
* NEW: Admin dashboard mobile device support and new hooks for Admin dashboard
* NEW: Get current API version from API
* NEW: CLI script to delete pads
* Fix: Automatic client reconnection on disonnect
* Fix: Text Export indentation now supports multiple indentations
* Fix: Bugfix getChatHistory API method
* Fix: Stop Chrome losing caret after paste is texted
* Fix: Make colons on end of line create 4 spaces on indent
* Fix: Stop the client disconnecting if a rev is in the wrong order
* Fix: Various server crash issues based on rev in wrong order
* Fix: Various tests
* Fix: Make indent when on middle of the line stop creating list
* Fix: Stop long strings breaking the UX by moving focus away from beginning of line
* Fix: Redis findKeys support
* Fix: padUsersCount no longer hangs server
* Fix: Issue with two part locale specs not working
* Fix: Make plugin search case insensitive
* Fix: Indentation and bullets on text export
* Fix: Resolve various warnings on dependencies during install
* Fix: Page up / Page down now works in all browsers
* Fix: Stop Opera browser inserting two new lines on enter keypress
* Fix: Stop timeslider from showing NaN on pads with only one revision
* Other: Allow timeslider tests to run and provide & fix various other frontend-tests
* Other: Begin dropping referene to Lite. Etherpad Lite is now named "Etherpad"
* Other: Update to latest jQuery
* Other: Change loading message asking user to please wait on first build
* Other: Allow etherpad to use global npm installation (Safe since node 6.3)
* Other: Better documentation for log rotation and log message handling
# 1.2.7
* NEW: notifications are now modularized and can be stacked
* NEW: Visit a specific revision in the timeslider by suffixing #%revNumber% IE http://localhost/p/test/timeslider#12

View file

@ -28,7 +28,7 @@ documented codebase makes it easier for developers to improve the code and contr
Etherpad Lite is designed to be easily embeddable and provides a [HTTP API](https://github.com/ether/etherpad-lite/wiki/HTTP-API)
that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/johnyma22/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website.
that allows your web application to manage pads, users and groups. It is recommended to use the [available client implementations](https://github.com/ether/etherpad-lite/wiki/HTTP-API-client-libraries) in order to interact with this API. There is also a [jQuery plugin](https://github.com/ether/etherpad-lite-jquery-plugin) that helps you to embed Pads into your website.
There's also a full-featured plugin framework, allowing you to easily add your own features.
Finally, Etherpad Lite comes with translations into tons of different languages!
@ -108,7 +108,7 @@ You know all this and just want to know how you can help?
Look at the [TODO list](https://github.com/ether/etherpad-lite/wiki/TODO) and our [Issue tracker](https://github.com/ether/etherpad-lite/issues). (Please consider using [jshint](http://www.jshint.com/about/), if you plan to contribute code.)
Also, and most importantly, read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/wiki/Developer-Guidelines), really!
Also, and most importantly, read our [**Developer Guidelines**](https://github.com/ether/etherpad-lite/blob/master/CONTRIBUTING.md), really!
# Get in touch
Join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) and make some noise on our freenode irc channel [#etherpad-lite-dev](http://webchat.freenode.net?channels=#etherpad-lite-dev)!
@ -122,6 +122,7 @@ Join the [mailinglist](http://groups.google.com/group/etherpad-lite-dev) and mak
# Donate!
* [Flattr] (http://flattr.com/thing/71378/Etherpad-Foundation)
* Paypal - Press the donate button on [etherpad.org](http://etherpad.org)
* [Bitcoin] (https://coinbase.com/checkouts/1e572bf8a82e4663499f7f1f66c2d15a)
# License
[Apache License v2](http://www.apache.org/licenses/LICENSE-2.0.html)

63
bin/deletePad.js Normal file
View file

@ -0,0 +1,63 @@
/*
A tool for deleting pads from the CLI, because sometimes a brick is required to fix a window.
*/
if(process.argv.length != 3)
{
console.error("Use: node deletePad.js $PADID");
process.exit(1);
}
//get the padID
var padId = process.argv[2];
var db, padManager, pad, settings;
var neededDBValues = ["pad:"+padId];
var npm = require("../src/node_modules/npm");
var async = require("../src/node_modules/async");
async.series([
// load npm
function(callback) {
npm.load({}, function(er) {
if(er)
{
console.error("Could not load NPM: " + er)
process.exit(1);
}
else
{
callback();
}
})
},
// load modules
function(callback) {
settings = require('../src/node/utils/Settings');
db = require('../src/node/db/DB');
callback();
},
// intallize the database
function (callback)
{
db.init(callback);
},
// delete the pad and it's links
function (callback)
{
padManager = require('../src/node/db/PadManager');
padManager.removePad(padId, function(err){
callback(err);
});
callback();
}
], function (err)
{
if(err) throw err;
else
{
console.log("Finished deleting padId: "+padId);
process.exit();
}
});

View file

@ -13,8 +13,8 @@ var padId = process.argv[2];
var db, dirty, padManager, pad, settings;
var neededDBValues = ["pad:"+padId];
var npm = require("../src/node_modules/npm");
var async = require("../src/node_modules/async");
var npm = require("../node_modules/ep_etherpad-lite/node_modules/npm");
var async = require("../node_modules/ep_etherpad-lite/node_modules/async");
async.series([
// load npm
@ -33,9 +33,10 @@ async.series([
},
// load modules
function(callback) {
settings = require('../src/node/utils/Settings');
db = require('../src/node/db/DB');
dirty = require("../src/node_modules/ueberDB/node_modules/dirty")(padId + ".db");
settings = require('../node_modules/ep_etherpad-lite/node/utils/Settings');
db = require('../node_modules/ep_etherpad-lite/node/db/DB');
dirty = require("../node_modules/ep_etherpad-lite/node_modules/ueberDB/node_modules/dirty")(padId + ".db");
callback();
},
//intallize the database
function (callback)
@ -45,7 +46,7 @@ async.series([
//get the pad
function (callback)
{
padManager = require('../node/db/PadManager');
padManager = require('../node_modules/ep_etherpad-lite/node/db/PadManager');
padManager.getPad(padId, function(err, _pad)
{
@ -82,7 +83,10 @@ async.series([
db.db.db.wrappedDB.get(dbkey, function(err, dbvalue)
{
if(err) { callback(err); return}
dbvalue=JSON.parse(dbvalue);
if(dbvalue && typeof dbvalue != 'object'){
dbvalue=JSON.parse(dbvalue); // if its not json then parse it as json
}
dirty.set(dbkey, dbvalue, callback);
});

View file

@ -63,7 +63,7 @@ if [ ! -f $settings ]; then
cp settings.json.template $settings || exit 1
fi
echo "Ensure that all dependencies are up to date..."
echo "Ensure that all dependencies are up to date... If this is the first time you have run Etherpad please be patient."
(
mkdir -p node_modules
cd node_modules
@ -77,7 +77,7 @@ echo "Ensure that all dependencies are up to date..."
echo "Ensure jQuery is downloaded and up to date..."
DOWNLOAD_JQUERY="true"
NEEDED_VERSION="1.7.1"
NEEDED_VERSION="1.9.1"
if [ -f "src/static/js/jquery.js" ]; then
if [ $(uname) = "SunOS" ]; then
VERSION=$(cat src/static/js/jquery.js | head -n 3 | ggrep -o "v[0-9]\.[0-9]\(\.[0-9]\)\?");

View file

@ -12,7 +12,7 @@ set check_version="if(['6','8'].indexOf(process.version.split('.')[1].toString()
cmd /C node -e %check_version% || exit /B 1
echo _
echo Installing etherpad-lite and dependencies...
echo Ensure that all dependencies are up to date... If this is the first time you have run Etherpad please be patient.
cmd /C npm install src/ --loglevel warn || exit /B 1
echo _
@ -36,4 +36,4 @@ IF NOT EXIST settings.json (
)
echo _
echo Installed Etherpad-lite! To run Etherpad type start.bat
echo Installed Etherpad! To run Etherpad type start.bat

View file

@ -1,3 +1,7 @@
This is the new load testing file: https://bitbucket.org/rbraakman/etherpad-stresstest
BELOW is the original load testing file.
This load tester is extremely useful for testing how many dormant clients can connect to etherpad lite.
TODO:

View file

@ -129,6 +129,11 @@ Things in context:
There doesn't appear to be any example available of this particular hook being used, but it gets fired after the editor is all set up.
## postTimesliderInit
Called from: src/static/js/timeslider.js
There doesn't appear to be any example available of this particular hook being used, but it gets fired after the timeslider is all set up.
## userJoinOrUpdate
Called from: src/static/js/pad_userlist.js

View file

@ -61,7 +61,9 @@ Portal submits content into new blog post
## Usage
### API version
The latest version is `1.2`
The latest version is `1.2.7`
The current version can be queried via /api.
### Request Format

View file

@ -105,3 +105,15 @@ Your plugin must also contain a [package definition file](http://npmjs.org/doc/j
## Templates
If your plugin adds or modifies the front end HTML (e.g. adding buttons or changing their functions), you should put the necessary HTML code for such operations in `templates/`, in files of type ".ejs", since Etherpad-Lite uses EJS for HTML templating. See the following link for more information about EJS: <https://github.com/visionmedia/ejs>.
## Writing and running front-end tests for your plugin
Etherpad allows you to easily create front-end tests for plugins.
1. Create a new folder
```
%your_plugin%/static/tests/frontend/specs
```
2. Put your spec file in here (Example spec files are visible in %etherpad_root_folder%/frontend/tests/specs)
3. Visit http://yourserver.com/frontend/tests your front-end tests will run.

View file

@ -5,7 +5,7 @@
*/
{
// Name your instance!
"title": "Etherpad Lite",
"title": "Etherpad",
// favicon default name
// alternatively, set up a fully specified Url to your own favicon
@ -15,6 +15,10 @@
"ip": "0.0.0.0",
"port" : 9001,
// Session Key, used for reconnecting user sessions
// Set this to a secure string at least 10 characters long. Do not share this value.
"sessionKey" : "",
/*
// Node native SSL support
// this is disabled by default
@ -47,15 +51,8 @@
},
*/
//Logging configuration. See log4js documentation for further information
// https://github.com/nomiddlename/log4js-node
"logconfig" :
{ "appenders": [
{ "type": "console" }
] },
//the default text of a pad
"defaultPadText" : "Welcome to Etherpad Lite!\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",
"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",
/* Users must have a session to access pads. This effectively allows only group pads to be accessed. */
"requireSession" : false,
@ -97,9 +94,50 @@
},
*/
// restrict socket.io transport methods
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"],
/* The log level we are using, can be: DEBUG, INFO, WARN, ERROR */
"loglevel": "INFO",
// restrict socket.io transport methods
"socketTransportProtocols" : ["xhr-polling", "jsonp-polling", "htmlfile"]
//Logging configuration. See log4js documentation for further information
// https://github.com/nomiddlename/log4js-node
// You can add as many appenders as you want here:
"logconfig" :
{ "appenders": [
{ "type": "console"
//, "category": "access"// only logs pad access
}
/*
, { "type": "file"
, "filename": "your-log-file-here.log"
, "maxLogSize": 1024
, "backups": 3 // how many log files there're gonna be at max
//, "category": "test" // only log a specific category
}*/
/*
, { "type": "logLevelFilter"
, "level": "warn" // filters out all log messages that have a lower level than "error"
, "appender":
{ Use whatever appender you want here }
}*/
/*
, { "type": "logLevelFilter"
, "level": "error" // filters out all log messages that have a lower level than "error"
, "appender":
{ "type": "smtp"
, "subject": "An error occured in your EPL instance!"
, "recipients": "bar@blurdybloop.com, baz@blurdybloop.com"
, "sendInterval": 60*5 // in secs -- will buffer log messages; set to 0 to send a mail for every message
, "transport": "SMTP", "SMTP": { // see https://github.com/andris9/Nodemailer#possible-transport-methods
"host": "smtp.example.com", "port": 465,
"secureConnection": true,
"auth": {
"user": "foo@example.com",
"pass": "bar_foo"
}
}
}
}*/
] }
}

1
src/README.md Normal file
View file

@ -0,0 +1 @@
Ignore this file and see the file in the base installation folder

View file

@ -2,26 +2,60 @@
"@metadata": {
"authors": [
"Bellayet",
"Nasir8891"
"Nasir8891",
"Sankarshan"
]
},
"index.newPad": "\u09a8\u09a4\u09c1\u09a8 \u09aa\u09cd\u09af\u09be\u09a1",
"index.createOpenPad": "\u0985\u09a5\u09ac\u09be \u09a8\u09be\u09ae \u09b2\u09bf\u0996\u09c7 \u09aa\u09cd\u09af\u09be\u09a1 \u0996\u09c1\u09b2\u09c1\u09a8\/\u09a4\u09c8\u09b0\u09c0 \u0995\u09b0\u09c1\u09a8:",
"pad.toolbar.bold.title": "\u0997\u09be\u09a1\u09bc \u0995\u09b0\u09be (Ctrl-B)",
"pad.toolbar.italic.title": "\u09ac\u09be\u0981\u0995\u09be \u0995\u09b0\u09be (Ctrl-I)",
"pad.toolbar.underline.title": "\u0986\u09a8\u09cd\u09a1\u09be\u09b0\u09b2\u09be\u0987\u09a8 (Ctrl-U)",
"pad.toolbar.ol.title": "\u09b8\u09be\u09b0\u09bf\u09ac\u09a6\u09cd\u09a7 \u09a4\u09be\u09b2\u09bf\u0995\u09be",
"pad.toolbar.indent.title": "\u09aa\u09cd\u09b0\u09be\u09a8\u09cd\u09a4\u09bf\u0995\u0995\u09b0\u09a3",
"pad.toolbar.unindent.title": "\u0986\u0989\u099f\u09a1\u09c7\u09a8\u09cd\u099f",
"pad.toolbar.undo.title": "\u09ac\u09be\u09a4\u09bf\u09b2 \u0995\u09b0\u09c1\u09a8 (Ctrl-Z)",
"pad.toolbar.redo.title": "\u09aa\u09c1\u09a8\u09b0\u09be\u09af\u09bc \u0995\u09b0\u09c1\u09a8 (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "\u0995\u09c3\u09a4\u09bf \u09b0\u0982 \u09aa\u09b0\u09bf\u09b7\u09cd\u0995\u09be\u09b0 \u0995\u09b0\u09c1\u09a8",
"pad.toolbar.timeslider.title": "\u099f\u09be\u0987\u09ae\u09b8\u09cd\u09b2\u09be\u0987\u09a1\u09be\u09b0",
"pad.toolbar.savedRevision.title": "\u09b8\u0982\u09b8\u09cd\u0995\u09b0\u09a3 \u09b8\u0982\u09b0\u0995\u09cd\u09b7\u09a3 \u0995\u09b0\u09c1\u09a8",
"pad.toolbar.settings.title": "\u09b8\u09c7\u099f\u09bf\u0982",
"pad.toolbar.embed.title": "\u098f\u0987 \u09aa\u09cd\u09af\u09be\u09a1-\u099f\u09bf \u098f\u09ae\u09cd\u09ac\u09c7\u09a1 \u0995\u09b0\u09c1\u09a8",
"pad.toolbar.showusers.title": "\u098f\u0987 \u09aa\u09cd\u09af\u09be\u09a1\u09c7\u09b0 \u09ac\u09cd\u09af\u09ac\u09b9\u09be\u09b0\u0995\u09be\u09b0\u09c0\u09a6\u09c7\u09b0 \u09a6\u09c7\u0996\u09be\u09a8",
"pad.colorpicker.save": "\u09b8\u0982\u09b0\u0995\u09cd\u09b7\u09a3",
"pad.colorpicker.cancel": "\u09ac\u09be\u09a4\u09bf\u09b2",
"pad.loading": "\u09b2\u09cb\u09a1\u09bf\u0982...",
"pad.passwordRequired": "\u098f\u0987 \u09aa\u09cd\u09af\u09be\u09a1-\u099f\u09bf \u09a6\u09c7\u0996\u09be\u09b0 \u099c\u09a8\u09cd\u09af \u0986\u09aa\u09a8\u09be\u0995\u09c7 \u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u09ac\u09cd\u09af\u09ac\u09b9\u09be\u09b0 \u0995\u09b0\u09a4\u09c7 \u09b9\u09ac\u09c7",
"pad.permissionDenied": "\u09a6\u09c1\u0983\u0996\u09bf\u09a4, \u098f \u09aa\u09cd\u09af\u09be\u09a1-\u099f\u09bf \u09a6\u09c7\u0996\u09be\u09b0 \u0985\u09a7\u09bf\u0995\u09be\u09b0 \u0986\u09aa\u09a8\u09be\u09b0 \u09a8\u09c7\u0987",
"pad.wrongPassword": "\u0986\u09aa\u09a8\u09be\u09b0 \u09aa\u09be\u09b8\u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1 \u09b8\u09a0\u09bf\u0995 \u09a8\u09af\u09bc",
"pad.settings.padSettings": "\u09aa\u09cd\u09af\u09be\u09a1\u09c7\u09b0 \u09b8\u09cd\u09a5\u09be\u09aa\u09a8",
"pad.settings.myView": "\u0986\u09ae\u09be\u09b0 \u09a6\u09c3\u09b6\u09cd\u09af",
"pad.settings.stickychat": "\u099a\u09cd\u09af\u09be\u099f \u09b8\u0995\u09cd\u09b0\u09c0\u09a8\u09c7 \u09aa\u09cd\u09b0\u09a6\u09b0\u09cd\u09b6\u09a8 \u0995\u09b0\u09be \u09b9\u09ac\u09c7",
"pad.settings.colorcheck": "\u09b2\u09c7\u0996\u0995\u09a6\u09c7\u09b0 \u09a8\u09bf\u099c\u09b8\u09cd\u09ac \u09a8\u09bf\u09b0\u09cd\u09ac\u09be\u099a\u09bf\u09a4 \u09b0\u0982",
"pad.settings.linenocheck": "\u09b2\u09be\u0987\u09a8 \u09a8\u09ae\u09cd\u09ac\u09b0",
"pad.settings.fontType": "\u09ab\u09a8\u09cd\u099f-\u098f\u09b0 \u09aa\u09cd\u09b0\u0995\u09be\u09b0:",
"pad.settings.fontType.normal": "\u09b8\u09be\u09a7\u09be\u09b0\u09a3",
"pad.settings.fontType.monospaced": "Monospace",
"pad.settings.globalView": "\u09b8\u09b0\u09cd\u09ac\u09ac\u09cd\u09af\u09be\u09aa\u09c0 \u09a6\u09c3\u09b6\u09cd\u09af",
"pad.settings.language": "\u09ad\u09be\u09b7\u09be:",
"pad.importExport.import_export": "\u0987\u09ae\u09cd\u09aa\u09cb\u09b0\u099f\/\u098f\u0995\u09cd\u09b8\u09aa\u09cb\u09b0\u09cd\u099f",
"pad.importExport.import": "\u0995\u09cb\u09a8 \u099f\u09c7\u0995\u09cd\u09b8\u099f \u09ab\u09be\u0987\u09b2 \u09ac\u09be \u09a1\u0995\u09c1\u09ae\u09c7\u09a8\u09cd\u099f \u0986\u09aa\u09b2\u09cb\u09a1 \u0995\u09b0\u09c1\u09a8",
"pad.importExport.importSuccessful": "\u09b8\u09ab\u09b2!",
"pad.importExport.export": "\u098f\u0987 \u09aa\u09cd\u09af\u09be\u09a1\u099f\u09bf \u098f\u0995\u09cd\u09b8\u09aa\u09cb\u09b0\u09cd\u099f \u0995\u09b0\u09c1\u09a8",
"pad.importExport.exporthtml": "\u098f\u0987\u099a\u099f\u09bf\u098f\u09ae\u098f\u09b2",
"pad.importExport.exportplain": "\u09b8\u09be\u09a7\u09be\u09b0\u09a3 \u09b2\u09c7\u0996\u09be",
"pad.importExport.exportword": "\u09ae\u09be\u0987\u0995\u09cd\u09b0\u09cb\u09b8\u09ab\u099f \u0993\u09af\u09bc\u09be\u09b0\u09cd\u09a1",
"pad.importExport.exportpdf": "\u09aa\u09bf\u09a1\u09bf\u098f\u09ab",
"pad.importExport.exportopen": "\u0993\u09a1\u09bf\u098f\u09ab (\u0993\u09aa\u09c7\u09a8 \u09a1\u0995\u09c1\u09ae\u09c7\u09a8\u09cd\u099f \u09ab\u09b0\u09ae\u09cd\u09af\u09be\u099f)",
"pad.importExport.exportdokuwiki": "DokuWiki",
"pad.modals.connected": "\u09af\u09cb\u0997\u09be\u09af\u09cb\u0997 \u09b8\u09ab\u09b2",
"pad.modals.reconnecting": "\u0986\u09aa\u09a8\u09be\u09b0 \u09aa\u09cd\u09af\u09be\u09a1\u09c7\u09b0 \u09b8\u09be\u09a5\u09c7 \u09b8\u0982\u09af\u09cb\u0997\u09b8\u09cd\u09a5\u09be\u09aa\u09a8 \u0995\u09b0\u09be \u09b9\u099a\u09cd\u099b\u09c7..",
"pad.modals.forcereconnect": "\u09aa\u09c1\u09a8\u09b0\u09be\u09af\u09bc \u09b8\u0982\u09af\u09cb\u0997\u09b8\u09cd\u09a5\u09be\u09aa\u09a8\u09c7\u09b0 \u099a\u09c7\u09b7\u09cd\u099f\u09be",
"pad.modals.userdup": "\u0985\u09a8\u09cd\u09af \u0989\u0987\u09a8\u09cd\u09a1\u09cb-\u09a4\u09c7 \u0996\u09cb\u09b2\u09be \u09b9\u09af\u09bc\u09c7\u099b\u09c7",
"pad.modals.unauth": "\u0986\u09aa\u09a8\u09be\u09b0 \u0985\u09a7\u09bf\u0995\u09be\u09b0 \u09a8\u09c7\u0987",
"pad.modals.looping": "\u09af\u09cb\u0997\u09be\u09af\u09cb\u0997 \u09ac\u09bf\u099a\u09cd\u099b\u09bf\u09a8\u09cd\u09a8",
"pad.modals.initsocketfail": "\u09b8\u09be\u09b0\u09cd\u09ad\u09be\u09b0-\u098f\u09b0 \u09b8\u09be\u09a5\u09c7 \u09af\u09cb\u0997\u09be\u09af\u09cb\u0997 \u0995\u09b0\u09a4\u09c7 \u0985\u09b8\u0995\u09cd\u09b7\u09ae\u0964",
"pad.modals.slowcommit": "\u09af\u09cb\u0997\u09be\u09af\u09cb\u0997 \u09ac\u09bf\u099a\u09cd\u099b\u09bf\u09a8\u09cd\u09a8",
"pad.modals.deleted": "\u0985\u09aa\u09b8\u09be\u09b0\u09bf\u09a4\u0964",
"pad.modals.deleted.explanation": "\u098f\u0987 \u09aa\u09cd\u09af\u09be\u09a1\u099f\u09bf \u0985\u09aa\u09b8\u09be\u09b0\u09a3 \u0995\u09b0\u09be \u09b9\u09af\u09bc\u09c7\u099b\u09c7\u0964",
"pad.modals.disconnected.explanation": "\u09b8\u09be\u09b0\u09cd\u09ad\u09be\u09b0\u09c7\u09b0 \u09b8\u09be\u09a5\u09c7 \u09af\u09cb\u0997\u09be\u09af\u09cb\u0997 \u0995\u09b0\u09be \u09af\u09be\u099a\u09cd\u099b\u09c7 \u09a8\u09be",
@ -34,6 +68,7 @@
"timeslider.toolbar.authors": "\u09b2\u09c7\u0996\u0995\u0997\u09a3:",
"timeslider.toolbar.authorsList": "\u0995\u09cb\u09a8\u09cb \u09b2\u09c7\u0996\u0995 \u09a8\u09c7\u0987",
"timeslider.exportCurrent": "\u09ac\u09b0\u09cd\u09a4\u09ae\u09be\u09a8 \u09b8\u0982\u09b8\u09cd\u0995\u09b0\u09a3\u099f\u09bf \u098f\u0995\u09cd\u09b8\u09aa\u09cb\u09b0\u09cd\u099f \u0995\u09b0\u09c1\u09a8:",
"timeslider.dateformat": "{{month}}\/{{day}}\/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "\u099c\u09be\u09a8\u09c1\u09af\u09bc\u09be\u09b0\u09bf",
"timeslider.month.february": "\u09ab\u09c7\u09ac\u09cd\u09b0\u09c1\u09af\u09bc\u09be\u09b0\u09bf",
"timeslider.month.march": "\u09ae\u09be\u09b0\u09cd\u099a",
@ -45,5 +80,12 @@
"timeslider.month.september": "\u09b8\u09c7\u09aa\u09cd\u099f\u09c7\u09ae\u09cd\u09ac\u09b0",
"timeslider.month.october": "\u0985\u0995\u09cd\u099f\u09cb\u09ac\u09b0",
"timeslider.month.november": "\u09a8\u09ad\u09c7\u09ae\u09cd\u09ac\u09b0",
"timeslider.month.december": "\u09a1\u09bf\u09b8\u09c7\u09ae\u09cd\u09ac\u09b0"
"timeslider.month.december": "\u09a1\u09bf\u09b8\u09c7\u09ae\u09cd\u09ac\u09b0",
"pad.userlist.entername": "\u0986\u09aa\u09a8\u09be\u09b0 \u09a8\u09be\u09ae",
"pad.userlist.unnamed": "\u0995\u09cb\u09a8 \u09a8\u09be\u09ae \u09a8\u09bf\u09b0\u09cd\u09ac\u09be\u099a\u09a8 \u0995\u09b0\u09be \u09b9\u09af\u09bc\u09a8\u09bf",
"pad.userlist.guest": "\u0985\u09a4\u09bf\u09a5\u09bf",
"pad.userlist.approve": "\u0985\u09a8\u09c1\u09ae\u09cb\u09a6\u09bf\u09a4",
"pad.impexp.importbutton": "\u098f\u0996\u09a8 \u0987\u09ae\u09cd\u09aa\u09cb\u09b0\u09cd\u099f \u0995\u09b0\u09c1\u09a8",
"pad.impexp.importing": "\u0987\u09ae\u09cd\u09aa\u09cb\u09b0\u09cd\u099f \u099a\u09b2\u099b\u09c7...",
"pad.impexp.importfailed": "\u0987\u09ae\u09cd\u09aa\u09cb\u09b0\u09cd\u099f \u0985\u09b8\u0995\u09cd\u09b7\u09ae"
}

123
src/locales/br.json Normal file
View file

@ -0,0 +1,123 @@
{
"@metadata": {
"authors": [
"Fohanno",
"Fulup",
"Gwenn-Ael",
"Y-M D"
]
},
"index.newPad": "Pad nevez",
"index.createOpenPad": "pe kroui\u00f1\/digeri\u00f1 ur pad gant an anv :",
"pad.toolbar.bold.title": "Tev (Ctrl-B)",
"pad.toolbar.italic.title": "Italek (Ctrl-I)",
"pad.toolbar.underline.title": "Islinenna\u00f1 (Ctrl-U)",
"pad.toolbar.strikethrough.title": "Barrennet",
"pad.toolbar.ol.title": "Roll urzhiet",
"pad.toolbar.ul.title": "Roll en dizurzh",
"pad.toolbar.indent.title": "Endanta\u00f1",
"pad.toolbar.unindent.title": "Diendanta\u00f1",
"pad.toolbar.undo.title": "Dizober (Ktrl-Z)",
"pad.toolbar.redo.title": "Adober (Ktrl-Y)",
"pad.toolbar.clearAuthorship.title": "Diverka\u00f1 al livio\u00f9 oc'h anaout an aozerien",
"pad.toolbar.import_export.title": "Enporzhia\u00f1\/Ezporzhia\u00f1 eus\/war-zu ur furmad restr dishe\u00f1vel",
"pad.toolbar.timeslider.title": "Istor dinamek",
"pad.toolbar.savedRevision.title": "Doareo\u00f9 enrollet",
"pad.toolbar.settings.title": "Arventenno\u00f9",
"pad.toolbar.embed.title": "Enframma\u00f1 ar pad-ma\u00f1",
"pad.toolbar.showusers.title": "Diskwelet implijerien ar Pad",
"pad.colorpicker.save": "Enrolla\u00f1",
"pad.colorpicker.cancel": "Nulla\u00f1",
"pad.loading": "O karga\u00f1...",
"pad.passwordRequired": "Ezhomm ho peus ur ger-tremen evit mont d'ar Pad-se",
"pad.permissionDenied": "\nN'oc'h ket aotreet da vont d'ar pad-ma\u00f1",
"pad.wrongPassword": "Fazius e oa ho ker-tremen",
"pad.settings.padSettings": "Arventenno\u00f9 Pad",
"pad.settings.myView": "Ma diskwel",
"pad.settings.stickychat": "Diskwel ar flap bepred",
"pad.settings.colorcheck": "Livio\u00f9 anaout",
"pad.settings.linenocheck": "Niverenno\u00f9 linenno\u00f9",
"pad.settings.fontType": "Seurt font :",
"pad.settings.fontType.normal": "Reizh",
"pad.settings.fontType.monospaced": "Monospas",
"pad.settings.globalView": "Gwel dre vras",
"pad.settings.language": "Yezh :",
"pad.importExport.import_export": "Enporzhia\u00f1\/Ezporzhia\u00f1",
"pad.importExport.import": "Enkarga\u00f1 un destenn pe ur restr",
"pad.importExport.importSuccessful": "Deuet eo ganeoc'h !",
"pad.importExport.export": "Ezporzhia\u00f1 ar pad brema\u00f1 evel :",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "Testenn blaen",
"pad.importExport.exportword": "Microsoft Word",
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)",
"pad.importExport.exportdokuwiki": "DokuWiki",
"pad.importExport.abiword.innerHTML": "Ne c'hallit ket emporzjia\u00f1 furmado\u00f9 testenno\u00f9 kriz pe html. Evit arc'hwelio\u00f9 enporzhia\u00f1 emdroetoc'h, staliit <a href=\"https:\/\/github.com\/ether\/etherpad-lite\/wiki\/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">abiword<\/a> mar plij.",
"pad.modals.connected": "Kevreet.",
"pad.modals.reconnecting": "Adkevrea\u00f1 war-zu ho pad...",
"pad.modals.forcereconnect": "Adkevrea\u00f1 dre heg",
"pad.modals.userdup": "Digor en ur prenestr all",
"pad.modals.userdup.explanation": "Digor eo ho pad, war a seblant, e meur a brenestr eus ho merdeer en urzhiataer-ma\u00f1.",
"pad.modals.userdup.advice": "Kevrea\u00f1 en ur implijout ar prenestr-ma\u00f1.",
"pad.modals.unauth": "N'eo ket aotreet",
"pad.modals.unauth.explanation": "Kemmet e vo hoc'h aotreo\u00f9 pa vo diskwelet ar bajenn.-ma\u00f1 Klaskit kevrea\u00f1 en-dro.",
"pad.modals.looping": "Digevreet.",
"pad.modals.looping.explanation": "Kudenno\u00f9 kehenti\u00f1 zo gant ar servijer sinkronelekaat.",
"pad.modals.looping.cause": "Posupl eo e vefe gwarezet ho kevreadur gant ur maltouter diembreget pe ur servijer proksi",
"pad.modals.initsocketfail": "Ne c'haller ket tizhout ar servijer.",
"pad.modals.initsocketfail.explanation": "Ne c'haller ket kevrea\u00f1 ouzh ar servijer sinkronelaat.",
"pad.modals.initsocketfail.cause": "Gallout a ra ar gudenn dont eus ho merdeer Web pe eus ho kevreadur Internet.",
"pad.modals.slowcommit": "Digevreet.",
"pad.modals.slowcommit.explanation": "Ne respont ket ar serveur.",
"pad.modals.slowcommit.cause": "Gallout a ra dont diwar kudenno\u00f9 kevrea\u00f1 gant ar rouedad.",
"pad.modals.deleted": "Dilamet.",
"pad.modals.deleted.explanation": "Lamet eo bet ar pad-ma\u00f1.",
"pad.modals.disconnected": "Digevreet oc'h bet.",
"pad.modals.disconnected.explanation": "Kollet eo bet ar c'hevreadur gant ar servijer",
"pad.modals.disconnected.cause": "Dizimplijadus eo ar servijer marteze. Kelaouit ac'hanomp ma pad ar gudenn.",
"pad.share": "Ranna\u00f1 ar pad-ma\u00f1.",
"pad.share.readonly": "Lenn hepken",
"pad.share.link": "Liamm",
"pad.share.emebdcode": "Enframma\u00f1 an URL",
"pad.chat": "Flap",
"pad.chat.title": "Digeri\u00f1 ar flap kevelet gant ar pad-ma\u00f1.",
"pad.chat.loadmessages": "Karga\u00f1 muioc'h a gemennadenno\u00f9",
"timeslider.pageTitle": "Istor dinamek eus {{appTitle}}",
"timeslider.toolbar.returnbutton": "Distrei\u00f1 d'ar pad-ma\u00f1.",
"timeslider.toolbar.authors": "Aozerien :",
"timeslider.toolbar.authorsList": "Aozer ebet",
"timeslider.toolbar.exportlink.title": "Ezporzhia\u00f1",
"timeslider.exportCurrent": "Ezporzhia\u00f1 an doare brema\u00f1 evel :",
"timeslider.version": "Stumm {{version}}",
"timeslider.saved": "Enrolla\u00f1 {{day}} {{month}} {{year}}",
"timeslider.dateformat": "{{month}}\/{{day}}\/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "Genver",
"timeslider.month.february": "C'hwevrer",
"timeslider.month.march": "Meurzh",
"timeslider.month.april": "Ebrel",
"timeslider.month.may": "Mae",
"timeslider.month.june": "Mezheven",
"timeslider.month.july": "Gouere",
"timeslider.month.august": "Eost",
"timeslider.month.september": "Gwengolo",
"timeslider.month.october": "Here",
"timeslider.month.november": "Du",
"timeslider.month.december": "Kerzu",
"timeslider.unnamedauthor": "{{niver}} aozer dianav",
"timeslider.unnamedauthors": "Aozerien dianav",
"pad.savedrevs.marked": "Merket eo an adweladenn-ma\u00f1 evel adweladenn gwiriet",
"pad.userlist.entername": "Ebarzhit hoc'h anv",
"pad.userlist.unnamed": "dizanv",
"pad.userlist.guest": "Den pedet",
"pad.userlist.deny": "Nac'h",
"pad.userlist.approve": "Aproui\u00f1",
"pad.editbar.clearcolors": "Diverka\u00f1 al livio\u00f9 stag ouzh an aozerien en teul a-bezh ?",
"pad.impexp.importbutton": "Enporzhia\u00f1 brema\u00f1",
"pad.impexp.importing": "Oc'h enporzhia\u00f1...",
"pad.impexp.confirmimport": "Ma vez enporzhiet ur restr e vo diverket ar pezh zo en teul a-vrema\u00f1. Ha sur oc'h e fell deoc'h mont betek penn ?",
"pad.impexp.convertFailed": "N'eus ket bet gallet enporzhia\u00f1 ar restr. Ober gant ur furmad teul all pe eila\u00f1\/pega\u00f1 gant an dorn.",
"pad.impexp.uploadFailed": "C'hwitet eo bet an enporzhia\u00f1. Klaskit en-dro.",
"pad.impexp.importfailed": "C'hwitet eo an enporzhiadenn",
"pad.impexp.copypaste": "Eilit\/pegit, mar plij",
"pad.impexp.exportdisabled": "Diweredekaet eo ezporzhia\u00f1 d'ar furmad {{type}}. Kit e darempred gant merour ar reizhiad evit gouzout hiroc'h."
}

View file

@ -17,7 +17,7 @@
"pad.toolbar.undo.title": "Desf\u00e9s (Ctrl-Z)",
"pad.toolbar.redo.title": "Ref\u00e9s (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "Neteja els colors d'autoria",
"pad.toolbar.savedRevision.title": "Revisions desades",
"pad.toolbar.savedRevision.title": "Desa la revisi\u00f3",
"pad.toolbar.settings.title": "Configuraci\u00f3",
"pad.toolbar.showusers.title": "Mostra els usuaris d\u2019aquest pad",
"pad.colorpicker.save": "Desa",
@ -25,18 +25,25 @@
"pad.loading": "S'est\u00e0 carregant...",
"pad.wrongPassword": "La contrasenya \u00e9s incorrecta",
"pad.settings.myView": "La meva vista",
"pad.settings.stickychat": "Xateja sempre a la pantalla",
"pad.settings.colorcheck": "Colors d'autoria",
"pad.settings.linenocheck": "N\u00fameros de l\u00ednia",
"pad.settings.fontType": "Tipus de lletra:",
"pad.settings.fontType.normal": "Normal",
"pad.settings.fontType.monospaced": "D'amplada fixa",
"pad.settings.globalView": "Vista global",
"pad.settings.language": "Llengua:",
"pad.importExport.import_export": "Importaci\u00f3\/exportaci\u00f3",
"pad.importExport.import": "Puja qualsevol fitxer de text o document",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "Text net",
"pad.importExport.exportpdf": "PDF",
"pad.importExport.exportopen": "ODF (Open Document Format)",
"pad.importExport.exportdokuwiki": "DokuWiki",
"pad.modals.connected": "Connectat.",
"pad.modals.forcereconnect": "For\u00e7a tornar a connectar",
"pad.modals.unauth": "No autoritzat",
"pad.modals.unauth.explanation": "Els vostres permisos han canviat mentre es visualitzava la p\u00e0gina. Proveu de reconnectar-vos.",
"pad.modals.looping": "Desconnectat.",
"pad.modals.initsocketfail": "El servidor no \u00e9s accessible.",
"pad.modals.initsocketfail.explanation": "No s'ha pogut connectar amb el servidor de sincronitzaci\u00f3.",
@ -44,6 +51,7 @@
"pad.modals.slowcommit.explanation": "El servidor no respon.",
"pad.modals.deleted": "Suprimit.",
"pad.modals.disconnected": "Heu estat desconnectat.",
"pad.modals.disconnected.cause": "El servidor sembla que no est\u00e0 disponible. Notifiqueu-nos si continua passant.",
"pad.share.readonly": "Nom\u00e9s de lectura",
"pad.share.link": "Enlla\u00e7",
"pad.chat": "Xat",
@ -52,6 +60,8 @@
"timeslider.toolbar.exportlink.title": "Exporta",
"timeslider.exportCurrent": "Exporta la versi\u00f3 actual com a:",
"timeslider.version": "Versi\u00f3 {{version}}",
"timeslider.saved": "Desat {{month}} {{day}}, {{year}}",
"timeslider.dateformat": "{{month}}\/{{day}}\/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "Gener",
"timeslider.month.february": "Febrer",
"timeslider.month.march": "Mar\u00e7",
@ -69,9 +79,11 @@
"pad.userlist.guest": "Convidat",
"pad.userlist.deny": "Refusa",
"pad.userlist.approve": "Aprova",
"pad.editbar.clearcolors": "Voleu netejar els colors d'autor del document sencer?",
"pad.impexp.importbutton": "Importa ara",
"pad.impexp.importing": "Important...",
"pad.impexp.convertFailed": "No \u00e9s possible d'importar aquest fitxer. Si us plau, podeu provar d'utilitzar un format diferent o copiar i enganxar manualment.",
"pad.impexp.uploadFailed": "Ha fallat la c\u00e0rrega. Torneu-ho a provar",
"pad.impexp.importfailed": "Ha fallat la importaci\u00f3",
"pad.impexp.copypaste": "Si us plau, copieu i enganxeu"
}

View file

@ -1,7 +1,8 @@
{
"@metadata": {
"authors": [
"Christian List"
"Christian List",
"Peter Alberti"
]
},
"index.newPad": "Ny Pad",
@ -100,6 +101,8 @@
"timeslider.month.october": "oktober",
"timeslider.month.november": "november",
"timeslider.month.december": "december",
"timeslider.unnamedauthor": "{{num}} unavngiven forfatter",
"timeslider.unnamedauthors": "{{num}} unavngivne forfattere",
"pad.savedrevs.marked": "Denne revision er nu markeret som en gemt revision",
"pad.userlist.entername": "Indtast dit navn",
"pad.userlist.unnamed": "ikke-navngivet",

View file

@ -22,7 +22,7 @@
"pad.toolbar.clearAuthorship.title": "Autorenfarben zur\u00fccksetzen",
"pad.toolbar.import_export.title": "Import\/Export in verschiedenen Dateiformaten",
"pad.toolbar.timeslider.title": "Pad-Versionsgeschichte anzeigen",
"pad.toolbar.savedRevision.title": "Diese Revision markieren",
"pad.toolbar.savedRevision.title": "Version speichern",
"pad.toolbar.settings.title": "Einstellungen",
"pad.toolbar.embed.title": "Dieses Pad teilen oder einbetten",
"pad.toolbar.showusers.title": "Aktuell verbundene Benutzer anzeigen",

View file

@ -22,7 +22,7 @@
"pad.toolbar.clearAuthorship.title": "\u039a\u03b1\u03b8\u03b1\u03c1\u03b9\u03c3\u03bc\u03cc\u03c2 \u03a7\u03c1\u03c9\u03bc\u03ac\u03c4\u03c9\u03bd \u03a3\u03c5\u03bd\u03c4\u03b1\u03ba\u03c4\u03ce\u03bd",
"pad.toolbar.import_export.title": "\u0395\u03b9\u03c3\u03b1\u03b3\u03c9\u03b3\u03ae\/\u0395\u03be\u03b1\u03b3\u03c9\u03b3\u03ae \u03b1\u03c0\u03cc\/\u03c3\u03b5 \u03b4\u03b9\u03b1\u03c6\u03bf\u03c1\u03b5\u03c4\u03b9\u03ba\u03bf\u03cd\u03c2 \u03c4\u03cd\u03c0\u03bf\u03c5\u03c2 \u03b1\u03c1\u03c7\u03b5\u03af\u03c9\u03bd",
"pad.toolbar.timeslider.title": "\u03a7\u03c1\u03bf\u03bd\u03bf\u03b4\u03b9\u03ac\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1",
"pad.toolbar.savedRevision.title": "\u0391\u03c0\u03bf\u03b8\u03ae\u03ba\u03b5\u03c5\u03c3\u03b7 \u0388\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2",
"pad.toolbar.savedRevision.title": "\u0391\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03c5\u03bc\u03ad\u03bd\u03b5\u03c2 \u0391\u03bd\u03b1\u03b8\u03b5\u03c9\u03c1\u03ae\u03c3\u03b5\u03b9\u03c2",
"pad.toolbar.settings.title": "\u03a1\u03c5\u03b8\u03bc\u03af\u03c3\u03b5\u03b9\u03c2",
"pad.toolbar.embed.title": "\u0395\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7 \u03c4\u03bf\u03c5 pad",
"pad.toolbar.showusers.title": "\u0395\u03bc\u03c6\u03ac\u03bd\u03b9\u03c3\u03b7 \u03c4\u03c9\u03bd \u03c7\u03c1\u03b7\u03c3\u03c4\u03ce\u03bd \u03b1\u03c5\u03c4\u03bf\u03cd \u03c4\u03bf\u03c5 pad",
@ -81,6 +81,7 @@
"pad.share.emebdcode": "URL \u03b5\u03bd\u03c3\u03c9\u03bc\u03ac\u03c4\u03c9\u03c3\u03b7\u03c2",
"pad.chat": "\u03a3\u03c5\u03bd\u03bf\u03bc\u03b9\u03bb\u03af\u03b1",
"pad.chat.title": "\u0386\u03bd\u03bf\u03b9\u03b3\u03bc\u03b1 \u03c4\u03b7\u03c2 \u03c3\u03c5\u03bd\u03bf\u03bc\u03b9\u03bb\u03af\u03b1\u03c2 \u03b3\u03b9\u03b1 \u03b1\u03c5\u03c4\u03cc \u03c4\u03bf pad.",
"pad.chat.loadmessages": "\u03a6\u03cc\u03c1\u03c4\u03c9\u03c3\u03b7 \u03c0\u03b5\u03c1\u03b9\u03c3\u03c3\u03cc\u03c4\u03b5\u03c1\u03c9\u03bd \u03bc\u03b7\u03bd\u03c5\u03bc\u03ac\u03c4\u03c9\u03bd",
"timeslider.pageTitle": "{{appTitle}} \u03a7\u03c1\u03bf\u03bd\u03bf\u03b4\u03b9\u03ac\u03b3\u03c1\u03b1\u03bc\u03bc\u03b1",
"timeslider.toolbar.returnbutton": "\u0395\u03c0\u03b9\u03c3\u03c4\u03c1\u03bf\u03c6\u03ae \u03c3\u03c4\u03bf pad",
"timeslider.toolbar.authors": "\u03a3\u03c5\u03bd\u03c4\u03ac\u03ba\u03c4\u03b5\u03c2:",
@ -88,7 +89,7 @@
"timeslider.toolbar.exportlink.title": "\u0395\u03be\u03b1\u03b3\u03c9\u03b3\u03ae",
"timeslider.exportCurrent": "\u0395\u03be\u03b1\u03b3\u03c9\u03b3\u03ae \u03c4\u03c1\u03ad\u03c7\u03bf\u03c5\u03c3\u03b1\u03c2 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7\u03c2 \u03c9\u03c2:",
"timeslider.version": "\u0388\u03ba\u03b4\u03bf\u03c3\u03b7 {{version}}",
"timeslider.saved": "\u0391\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 {{day}} {{month}}, {{year}}",
"timeslider.saved": "\u0391\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03cd\u03c4\u03b7\u03ba\u03b5 \u03c3\u03c4\u03b9\u03c2 {{day}} {{month}} {{year}}",
"timeslider.dateformat": "{{day}}\/{{month}}\/{{year}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "\u0399\u03b1\u03bd\u03bf\u03c5\u03b1\u03c1\u03af\u03bf\u03c5",
"timeslider.month.february": "\u03a6\u03b5\u03b2\u03c1\u03bf\u03c5\u03b1\u03c1\u03af\u03bf\u03c5",
@ -102,6 +103,8 @@
"timeslider.month.october": "\u039f\u03ba\u03c4\u03c9\u03b2\u03c1\u03af\u03bf\u03c5",
"timeslider.month.november": "\u039d\u03bf\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5",
"timeslider.month.december": "\u0394\u03b5\u03ba\u03b5\u03bc\u03b2\u03c1\u03af\u03bf\u03c5",
"timeslider.unnamedauthor": "{{num}} \u03b1\u03bd\u03ce\u03bd\u03c5\u03bc\u03bf\u03c2 \u03c3\u03c5\u03b3\u03b3\u03c1\u03b1\u03c6\u03ad\u03b1\u03c2",
"timeslider.unnamedauthors": "{{num}} \u03b1\u03bd\u03ce\u03bd\u03c5\u03bc\u03bf\u03b9 \u03c3\u03c5\u03b3\u03b3\u03c1\u03b1\u03c6\u03b5\u03af\u03c2",
"pad.savedrevs.marked": "\u0391\u03c5\u03c4\u03ae \u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7 \u03b5\u03c0\u03b9\u03c3\u03b7\u03bc\u03ac\u03bd\u03b8\u03b7\u03ba\u03b5 \u03c9\u03c2 \u03b1\u03c0\u03bf\u03b8\u03b7\u03ba\u03b5\u03c5\u03bc\u03ad\u03bd\u03b7 \u03ad\u03ba\u03b4\u03bf\u03c3\u03b7",
"pad.userlist.entername": "\u0395\u03b9\u03c3\u03ac\u03b3\u03b5\u03c4\u03b5 \u03c4\u03bf \u03cc\u03bd\u03bf\u03bc\u03ac \u03c3\u03b1\u03c2",
"pad.userlist.unnamed": "\u03b1\u03bd\u03ce\u03bd\u03c5\u03bc\u03bf\u03c2",

View file

@ -4,8 +4,9 @@
"0": "Armando-Martin",
"1": "Jacobo",
"2": "Joker",
"4": "Vivaelcelta",
"5": "Xuacu"
"3": "Rubenwap",
"5": "Vivaelcelta",
"6": "Xuacu"
}
},
"index.newPad": "Nuevo Pad",
@ -104,6 +105,8 @@
"timeslider.month.october": "Octubre",
"timeslider.month.november": "Noviembre",
"timeslider.month.december": "Diciembre",
"timeslider.unnamedauthor": "{{num}} autor desconocido",
"timeslider.unnamedauthors": "{{num}} autores desconocidos",
"pad.savedrevs.marked": "Revisi\u00f3n guardada",
"pad.userlist.entername": "Escribe tu nombre",
"pad.userlist.unnamed": "an\u00f3nimo",

View file

@ -24,7 +24,7 @@
"pad.toolbar.clearAuthorship.title": "Poista kirjoittajav\u00e4rit",
"pad.toolbar.import_export.title": "Tuo tai vie eri tiedostomuodoista tai -muotoihin",
"pad.toolbar.timeslider.title": "Aikajana",
"pad.toolbar.savedRevision.title": "Tallennetut versiot",
"pad.toolbar.savedRevision.title": "Tallenna muutos",
"pad.toolbar.settings.title": "Asetukset",
"pad.toolbar.embed.title": "Upota muistio",
"pad.toolbar.showusers.title": "N\u00e4yt\u00e4 muistion k\u00e4ytt\u00e4j\u00e4t",
@ -105,6 +105,8 @@
"timeslider.month.october": "lokakuu",
"timeslider.month.november": "marraskuu",
"timeslider.month.december": "joulukuu",
"timeslider.unnamedauthor": "{{num}} nimet\u00f6n tekij\u00e4",
"timeslider.unnamedauthors": "{{num}} nimet\u00f6nt\u00e4 tekij\u00e4\u00e4",
"pad.savedrevs.marked": "T\u00e4m\u00e4 versio on nyt merkitty tallennetuksi versioksi",
"pad.userlist.entername": "Kirjoita nimesi",
"pad.userlist.unnamed": "nimet\u00f6n",

View file

@ -109,6 +109,8 @@
"timeslider.month.october": "Octobre",
"timeslider.month.november": "Novembre",
"timeslider.month.december": "D\u00e9cembre",
"timeslider.unnamedauthor": "{{num}} auteur anonyme",
"timeslider.unnamedauthors": "{{num}} auteurs anonymes",
"pad.savedrevs.marked": "Cette r\u00e9vision est maintenant marqu\u00e9e comme r\u00e9vision enregistr\u00e9e",
"pad.userlist.entername": "Entrez votre nom",
"pad.userlist.unnamed": "sans nom",

View file

@ -100,6 +100,8 @@
"timeslider.month.october": "outubro",
"timeslider.month.november": "novembro",
"timeslider.month.december": "decembro",
"timeslider.unnamedauthor": "{{num}} autor an\u00f3nimo",
"timeslider.unnamedauthors": "{{num}} autores an\u00f3nimos",
"pad.savedrevs.marked": "Esta revisi\u00f3n est\u00e1 agora marcada como revisi\u00f3n gardada",
"pad.userlist.entername": "Insira o seu nome",
"pad.userlist.unnamed": "an\u00f3nimo",

View file

@ -79,6 +79,7 @@
"pad.share.emebdcode": "\u05d4\u05d8\u05de\u05e2\u05ea \u05e7\u05d9\u05e9\u05d5\u05e8",
"pad.chat": "\u05e9\u05d9\u05d7\u05d4",
"pad.chat.title": "\u05e4\u05ea\u05d9\u05d7\u05ea \u05d4\u05e9\u05d9\u05d7\u05d4 \u05e9\u05dc \u05d4\u05e4\u05e0\u05e7\u05e1 \u05d4\u05d6\u05d4.",
"pad.chat.loadmessages": "\u05d8\u05e2\u05d9\u05e0\u05ea \u05d4\u05d5\u05d3\u05e2\u05d5\u05ea \u05e0\u05d5\u05e1\u05e4\u05d5\u05ea",
"timeslider.pageTitle": "\u05d2\u05d5\u05dc\u05dc \u05d6\u05de\u05df \u05e9\u05dc {{appTitle}}",
"timeslider.toolbar.returnbutton": "\u05d7\u05d6\u05e8\u05d4 \u05d0\u05dc \u05d4\u05e4\u05e0\u05e7\u05e1",
"timeslider.toolbar.authors": "\u05db\u05d5\u05ea\u05d1\u05d9\u05dd:",
@ -100,6 +101,8 @@
"timeslider.month.october": "\u05d0\u05d5\u05e7\u05d8\u05d5\u05d1\u05e8",
"timeslider.month.november": "\u05e0\u05d5\u05d1\u05de\u05d1\u05e8",
"timeslider.month.december": "\u05d3\u05e6\u05de\u05d1\u05e8",
"timeslider.unnamedauthor": "\u05db\u05d5\u05ea\u05d1 \u05d7\u05e1\u05e8\u05be\u05e9\u05dd \u05d0\u05d7\u05d3",
"timeslider.unnamedauthors": "{{num}} \u05db\u05d5\u05ea\u05d1\u05d9\u05dd \u05d7\u05e1\u05e8\u05d9\u05be\u05e9\u05dd",
"pad.savedrevs.marked": "\u05d2\u05e8\u05e1\u05d4 \u05d6\u05d5 \u05de\u05e1\u05d5\u05de\u05e0\u05ea \u05db\u05d2\u05e8\u05e1\u05d4 \u05e9\u05de\u05d5\u05e8\u05d4",
"pad.userlist.entername": "\u05e0\u05d0 \u05dc\u05d4\u05d6\u05d9\u05df \u05d0\u05ea \u05e9\u05de\u05da",
"pad.userlist.unnamed": "\u05dc\u05dc\u05d0 \u05e9\u05dd",

View file

@ -103,6 +103,8 @@
"timeslider.month.october": "ottobre",
"timeslider.month.november": "novembre",
"timeslider.month.december": "dicembre",
"timeslider.unnamedauthor": "{{num}} autore senza nome",
"timeslider.unnamedauthors": "{{num}} autori senza nome",
"pad.savedrevs.marked": "Questa revisione \u00e8 ora contrassegnata come una versione salvata",
"pad.userlist.entername": "Inserisci il tuo nome",
"pad.userlist.unnamed": "senza nome",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "\u4f5c\u8005\u306e\u8272\u5206\u3051\u3092\u6d88\u53bb",
"pad.toolbar.import_export.title": "\u4ed6\u306e\u5f62\u5f0f\u306e\u30d5\u30a1\u30a4\u30eb\u306e\u30a4\u30f3\u30dd\u30fc\u30c8\/\u30a8\u30af\u30b9\u30dd\u30fc\u30c8",
"pad.toolbar.timeslider.title": "\u30bf\u30a4\u30e0\u30b9\u30e9\u30a4\u30c0\u30fc",
"pad.toolbar.savedRevision.title": "\u4fdd\u5b58\u6e08\u307f\u306e\u7248",
"pad.toolbar.savedRevision.title": "\u7248\u3092\u4fdd\u5b58",
"pad.toolbar.settings.title": "\u8a2d\u5b9a",
"pad.toolbar.embed.title": "\u3053\u306e\u30d1\u30c3\u30c9\u3092\u57cb\u3081\u8fbc\u3080",
"pad.toolbar.showusers.title": "\u3053\u306e\u30d1\u30c3\u30c9\u306e\u30e6\u30fc\u30b6\u30fc\u3092\u8868\u793a",
@ -87,7 +87,7 @@
"timeslider.exportCurrent": "\u73fe\u5728\u306e\u7248\u3092\u30a8\u30af\u30b9\u30dd\u30fc\u30c8\u3059\u308b\u5f62\u5f0f:",
"timeslider.version": "\u30d0\u30fc\u30b8\u30e7\u30f3 {{version}}",
"timeslider.saved": "| {{year}}\u5e74{{month}}{{day}}\u65e5\u306b\u4fdd\u5b58",
"timeslider.dateformat": "{{year}}\u5e74{{month}}{{day}}\u65e5 {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.dateformat": "{{year}}\u5e74{{month}}\u6708{{day}}\u65e5 {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "1\u6708",
"timeslider.month.february": "2\u6708",
"timeslider.month.march": "3\u6708",
@ -100,6 +100,8 @@
"timeslider.month.october": "10\u6708",
"timeslider.month.november": "11\u6708",
"timeslider.month.december": "12\u6708",
"timeslider.unnamedauthor": "{{num}} \u4eba\u306e\u533f\u540d\u306e\u4f5c\u8005",
"timeslider.unnamedauthors": "{{num}} \u4eba\u306e\u533f\u540d\u306e\u4f5c\u8005",
"pad.savedrevs.marked": "\u3053\u306e\u7248\u3092\u3001\u4fdd\u5b58\u6e08\u307f\u306e\u7248\u3068\u3057\u3066\u30de\u30fc\u30af\u3057\u307e\u3057\u305f\u3002",
"pad.userlist.entername": "\u540d\u524d\u3092\u5165\u529b",
"pad.userlist.unnamed": "\u540d\u524d\u306a\u3057",

View file

@ -100,6 +100,8 @@
"timeslider.month.october": "10\uc6d4",
"timeslider.month.november": "11\uc6d4",
"timeslider.month.december": "12\uc6d4",
"timeslider.unnamedauthor": "\uc774\ub984 \uc5c6\ub294 \uc800\uc790 {{num}}\uba85",
"timeslider.unnamedauthors": "\uc774\ub984 \uc5c6\ub294 \uc800\uc790 {{num}}\uba85",
"pad.savedrevs.marked": "\uc774 \ud310\uc740 \uc774\uc81c \uc800\uc7a5\ud55c \ud310\uc73c\ub85c \ud45c\uc2dc\ud569\ub2c8\ub2e4.",
"pad.userlist.entername": "\uc774\ub984\uc744 \uc785\ub825\ud558\uc138\uc694",
"pad.userlist.unnamed": "\uc774\ub984\uc5c6\uc74c",

View file

@ -19,13 +19,16 @@
"pad.toolbar.clearAuthorship.title": "d\u00e4 Schriiver ier F\u00e4rve fottn\u00e4mme",
"pad.toolbar.import_export.title": "Vun ongerscheidlijje Dattei_Fommaate empotteere udder \u00e4xpotteere",
"pad.toolbar.timeslider.title": "Verjangeheid afschpelle",
"pad.toolbar.savedRevision.title": "Fa\u00dfjehallde Versione",
"pad.toolbar.savedRevision.title": "de Versjohn fa\u00dfhallde",
"pad.toolbar.settings.title": "Enscht\u00e4llonge",
"pad.toolbar.embed.title": "Donn dat Padd enbenge",
"pad.toolbar.showusers.title": "Verbonge Metschriiver aanzeije",
"pad.colorpicker.save": "Fa\u00dfhallde",
"pad.colorpicker.cancel": "Oph\u00fc\u00fcre",
"pad.loading": "Aam Laade&nbsp;&hellip;",
"pad.passwordRequired": "Do bruchs e Pa\u00dfwoot f\u00f6r heh dat P\u00e4dd.",
"pad.permissionDenied": "Do h\u00e4s nit dat R\u00e4\u00e4sch, op heh dat P\u00e4dd zohzejriife.",
"pad.wrongPassword": "Ding Pa\u00dfwoot wohr verkeht.",
"pad.settings.padSettings": "Dam P\u00e4dd sin Enscht\u00e4llonge",
"pad.settings.myView": "Anseesch",
"pad.settings.stickychat": "Donn der Klaaf emmer aanzeije",
@ -38,6 +41,7 @@
"pad.settings.language": "Schprooch:",
"pad.importExport.import_export": "Empoot\/\u00c4xpoot",
"pad.importExport.import": "Donn jeede T\u00e4x udder jeede Zoot Dokem\u00e4nt huhlaade",
"pad.importExport.importSuccessful": "Jeschaff!",
"pad.importExport.export": "Don dat P\u00e4dd \u00e4xpoteere al\u00df:",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "Eijfach T\u00e4x",
@ -45,9 +49,11 @@
"pad.importExport.exportpdf": "PDF (Poteerbaa Dokem\u00e4nte Fommaat)",
"pad.importExport.exportopen": "ODF (Offe Dokem\u00e4nte-Fommaat)",
"pad.importExport.exportdokuwiki": "DokuWiki",
"pad.importExport.abiword.innerHTML": "Mer k\u00fcnne blo\u00df eijfaache T\u00e4xte udder HTML_Fommaate empoteere. Opw\u00e4ndejere M\u00fcjjeleschkeite f\u00f6 der Empoot jon och, dof\u00f6r bruch mer en <a href=\"https:\/\/github.com\/ether\/etherpad-lite\/wiki\/How-to-enable-importing-and-exporting-different-file-formats-in-Ubuntu-or-OpenSuse-or-SLES-with-AbiWord\">Enschtallazjuhn met <i lang=\"en\">Abiword<\/i><\/a>.",
"pad.modals.connected": "Verbonge.",
"pad.modals.reconnecting": "Ben wider aam Verbenge&nbsp;&hellip;",
"pad.modals.forcereconnect": "Wider verbenge",
"pad.modals.userdup": "En enem andere Finster en \u00c4rbeid",
"pad.modals.userdup.explanation": "Heh dat Padd schingk en mieh wi einem Finster vun enem Brauser op heh d\u00e4m R\u00e4\u00e4schner op ze sin.",
"pad.modals.userdup.advice": "En heh d\u00e4m Finster wider verbenge.",
"pad.modals.unauth": "Nit ber\u00e4\u00e4schtesch",
@ -72,10 +78,12 @@
"pad.share.emebdcode": "URL enboue",
"pad.chat": "Klaaf",
"pad.chat.title": "Maach d\u00e4 Klaaf f\u00f6r heh dat P\u00e4dd op",
"pad.chat.loadmessages": "Mieh Nohresschte laade...",
"timeslider.pageTitle": "{{appTitle}} - Verjangeheid affschpelle",
"timeslider.toolbar.returnbutton": "Jangk retuur nohm P\u00e4dd",
"timeslider.toolbar.authors": "Schriiver:",
"timeslider.toolbar.authorsList": "Kein Schriivere",
"timeslider.toolbar.exportlink.title": "\u00c4xpoot",
"timeslider.exportCurrent": "Donn de meu\u00dfte V\u00e4sjohn \u00e4xpotteere al\u00df:",
"timeslider.version": "V\u00e4sjon {{version}}",
"timeslider.saved": "Fa\u00dfjehallde aam {{day}}. {{month}} {{year}}",
@ -92,11 +100,19 @@
"timeslider.month.october": "Oktoober",
"timeslider.month.november": "Nov\u00e4mber",
"timeslider.month.december": "Dez\u00e4mber",
"timeslider.unnamedauthor": "{{num}} naameloose Schriever",
"timeslider.unnamedauthors": "{{num}} naameloose Schriever",
"pad.savedrevs.marked": "Heh di V\u00e4sjohn es j\u00e4z fa\u00dfjehallde.",
"pad.userlist.entername": "Jif Dinge Naame en",
"pad.userlist.unnamed": "naamelo\u00df\u00df",
"pad.userlist.guest": "Ja\u00df\u00df",
"pad.userlist.deny": "Aflehne",
"pad.userlist.approve": "Joodhei\u00dfe",
"pad.editbar.clearcolors": "Sulle mer de F\u00e4rve f\u00f6r de Schriiver uss_em janze T\u00e4x fott maache?",
"pad.impexp.importbutton": "J\u00e4z empoteere",
"pad.impexp.importing": "Ben aam Empotteere&nbsp;&hellip;",
"pad.impexp.confirmimport": "En Dattei ze empotteere m\u00e4\u00e4t der janze T\u00e4x em P\u00e4dd fott. Wess De dat verfaftesch hann?",
"pad.impexp.convertFailed": "Mer kunnte di Dattei nit empoteere. Nemm en ander Dattei-Fommaat udder donn d\u00e4 T\u00e4x vun Hand kopeere un ennf\u00f6\u00f6je.",
"pad.impexp.uploadFailed": "Et Huhlaade es don\u00e4vve jejange, bes esu jood un probeer et norr_ens",
"pad.impexp.importfailed": "Et Empoteere es don\u00e4vve jejange",
"pad.impexp.copypaste": "Bes esu jood un donn et koppeere un enf\u00f6\u00f6je",

View file

@ -101,6 +101,8 @@
"timeslider.month.october": "\u043e\u043a\u0442\u043e\u043c\u0432\u0440\u0438",
"timeslider.month.november": "\u043d\u043e\u0435\u043c\u0432\u0440\u0438",
"timeslider.month.december": "\u0434\u0435\u043a\u0435\u043c\u0432\u0440\u0438",
"timeslider.unnamedauthor": "{{num}} \u043d\u0435\u0438\u043c\u0435\u043d\u0443\u0432\u0430\u043d \u0430\u0432\u0442\u043e\u0440",
"timeslider.unnamedauthors": "{{num}} \u043d\u0435\u0438\u043c\u0435\u043d\u0443\u0432\u0430\u043d\u0438 \u0430\u0432\u0442\u043e\u0440\u0438",
"pad.savedrevs.marked": "\u041e\u0432\u0430\u0430 \u0440\u0435\u0432\u0438\u0437\u0438\u0458\u0430 \u0441\u0435\u0433\u0430 \u0435 \u043e\u0437\u043d\u0430\u0447\u0435\u043d\u0430 \u043a\u0430\u043a\u043e \u0437\u0430\u0447\u0443\u0432\u0430\u043d\u0430",
"pad.userlist.entername": "\u0412\u043d\u0435\u0441\u0435\u0442\u0435 \u0433\u043e \u0432\u0430\u0448\u0435\u0442\u043e \u0438\u043c\u0435",
"pad.userlist.unnamed": "\u0431\u0435\u0437 \u0438\u043c\u0435",

View file

@ -79,6 +79,7 @@
"pad.share.emebdcode": "\u0d0e\u0d02\u0d2c\u0d46\u0d21\u0d4d \u0d2f\u0d41.\u0d06\u0d7c.\u0d0e\u0d7d.",
"pad.chat": "\u0d24\u0d24\u0d4d\u0d38\u0d2e\u0d2f\u0d38\u0d02\u0d35\u0d3e\u0d26\u0d02",
"pad.chat.title": "\u0d08 \u0d2a\u0d3e\u0d21\u0d3f\u0d28\u0d4d\u0d31\u0d46 \u0d24\u0d24\u0d4d\u0d38\u0d2e\u0d2f\u0d38\u0d02\u0d35\u0d3e\u0d26\u0d02 \u0d24\u0d41\u0d31\u0d15\u0d4d\u0d15\u0d41\u0d15.",
"pad.chat.loadmessages": "\u0d15\u0d42\u0d1f\u0d41\u0d24\u0d7d \u0d38\u0d28\u0d4d\u0d26\u0d47\u0d36\u0d19\u0d4d\u0d19\u0d7e \u0d0e\u0d1f\u0d41\u0d15\u0d4d\u0d15\u0d41\u0d15",
"timeslider.pageTitle": "{{appTitle}} \u0d38\u0d2e\u0d2f\u0d30\u0d47\u0d16",
"timeslider.toolbar.returnbutton": "\u0d2a\u0d3e\u0d21\u0d3f\u0d32\u0d47\u0d15\u0d4d\u0d15\u0d4d \u0d24\u0d3f\u0d30\u0d3f\u0d1a\u0d4d\u0d1a\u0d41\u0d2a\u0d4b\u0d35\u0d41\u0d15",
"timeslider.toolbar.authors": "\u0d30\u0d1a\u0d2f\u0d3f\u0d24\u0d3e\u0d15\u0d4d\u0d15\u0d7e:",

View file

@ -78,6 +78,7 @@
"pad.share.emebdcode": "Benamkan URL",
"pad.chat": "Sembang",
"pad.chat.title": "Buka ruang sembang untuk pad ini.",
"pad.chat.loadmessages": "Muatkan banyak lagi pesanan",
"timeslider.pageTitle": "Gelangsar Masa {{appTitle}}",
"timeslider.toolbar.returnbutton": "Kembali ke pad",
"timeslider.toolbar.authors": "Pengarang:",
@ -99,6 +100,8 @@
"timeslider.month.october": "Oktober",
"timeslider.month.november": "November",
"timeslider.month.december": "Disember",
"timeslider.unnamedauthor": "{{num}} orang pengarang awanama",
"timeslider.unnamedauthors": "{{num}} orang pengarang awanama",
"pad.savedrevs.marked": "Semakan ini telah ditandai sebagai semakan tersimpan",
"pad.userlist.entername": "Taipkan nama anda",
"pad.userlist.unnamed": "tanpa nama",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "Kleuren auteurs wissen",
"pad.toolbar.import_export.title": "Naar\/van andere opmaak exporteren\/importeren",
"pad.toolbar.timeslider.title": "Tijdlijn",
"pad.toolbar.savedRevision.title": "Opgeslagen versies",
"pad.toolbar.savedRevision.title": "Versie opslaan",
"pad.toolbar.settings.title": "Instellingen",
"pad.toolbar.embed.title": "Pad insluiten",
"pad.toolbar.showusers.title": "Gebruikers van dit pad weergeven",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "\u0424\u044b\u0441\u0441\u04d5\u0434\u0436\u044b \u043d\u044b\u0441\u04d5\u043d\u0442\u0442\u04d5 \u0430\u0439\u0441\u044b\u043d\u04d5\u043d",
"pad.toolbar.import_export.title": "\u0418\u043c\u043f\u043e\u0440\u0442\/\u044d\u043a\u0441\u043f\u043e\u0440\u0442 \u04d5\u043d\u0434\u04d5\u0440 \u0444\u0430\u0439\u043b\u044b \u0444\u043e\u0440\u043c\u0430\u0442\u0442\u04d5\u0439\/\u0444\u043e\u0440\u043c\u0430\u0442\u0442\u04d5\u043c",
"pad.toolbar.timeslider.title": "\u0420\u04d5\u0441\u0442\u04d5\u0434\u0436\u044b \u0445\u0430\u0445\u0445",
"pad.toolbar.savedRevision.title": "\u04d4\u0432\u04d5\u0440\u0434 \u0444\u04d5\u043b\u0442\u04d5\u0440\u0442\u04d5",
"pad.toolbar.savedRevision.title": "\u0424\u04d5\u043b\u0442\u04d5\u0440 \u0431\u0430\u0432\u04d5\u0440\u044b\u043d\u04d5\u043d",
"pad.toolbar.settings.title": "\u0423\u0430\u0433\u04d5\u0432\u04d5\u0440\u0434\u0442\u04d5",
"pad.toolbar.embed.title": "\u0410\u0446\u044b \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0431\u0430\u0444\u0442\u0430\u0443\u044b\u043d",
"pad.toolbar.showusers.title": "\u0410\u0446\u044b \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0430\u0440\u0445\u0430\u0439\u0434\u0436\u044b\u0442\u044b \u0440\u0430\u0432\u0434\u0438\u0441\u044b\u043d",
@ -78,6 +78,7 @@
"pad.share.emebdcode": "URL \u0431\u0430\u0432\u04d5\u0440\u044b\u043d",
"pad.chat": "\u041d\u044b\u0445\u0430\u0441",
"pad.chat.title": "\u041e\u0446\u044b \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u04d5\u043d \u0447\u0430\u0442 \u0431\u0430\u043a\u04d5\u043d.",
"pad.chat.loadmessages": "\u0424\u044b\u043b\u0434\u04d5\u0440 \u0444\u044b\u0441\u0442\u04d5\u0433 \u0440\u0430\u0432\u0433\u04d5\u043d\u044b\u043d",
"timeslider.pageTitle": "{{appTitle}}-\u044b \u0440\u04d5\u0442\u04d5\u0434\u0436\u044b \u0445\u0430\u0445\u0445",
"timeslider.toolbar.returnbutton": "\u0424\u04d5\u0441\u0442\u04d5\u043c\u04d5, \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u043c\u04d5",
"timeslider.toolbar.authors": "\u0424\u044b\u0441\u0441\u04d5\u0434\u0436\u044b\u0442\u04d5:",
@ -99,6 +100,8 @@
"timeslider.month.october": "\u043e\u043a\u0442\u044f\u0431\u0440\u044c",
"timeslider.month.november": "\u043d\u043e\u044f\u0431\u0440\u044c",
"timeslider.month.december": "\u0434\u0435\u043a\u0430\u0431\u0440\u044c",
"timeslider.unnamedauthor": "{{num}} \u04d5\u043d\u04d5\u043d\u043e\u043c \u0444\u044b\u0441\u0441\u04d5\u0433",
"timeslider.unnamedauthors": "{{num}} \u04d5\u043d\u04d5\u043d\u043e\u043c \u0444\u044b\u0441\u0441\u04d5\u0434\u0436\u044b",
"pad.savedrevs.marked": "\u0410\u0446\u044b \u0444\u04d5\u043b\u0442\u04d5\u0440 \u043d\u044b\u0440 \u043a\u0443\u044b\u0434 \u04d5\u0432\u04d5\u0440\u0434 \u0444\u04d5\u043b\u0442\u04d5\u0440 \u043d\u044b\u0441\u0430\u043d\u0433\u043e\u043d\u0434 \u04d5\u0440\u0446\u044b\u0434",
"pad.userlist.entername": "\u0414\u04d5 \u043d\u043e\u043c \u0431\u0430\u0444\u044b\u0441\u0441",
"pad.userlist.unnamed": "\u04d5\u043d\u04d5\u043d\u043e\u043c",

View file

@ -21,7 +21,7 @@
"pad.toolbar.clearAuthorship.title": "Usu\u0144 kolory autor\u00f3w",
"pad.toolbar.import_export.title": "Import\/eksport z\/do r\u00f3\u017cnych format\u00f3w plik\u00f3w",
"pad.toolbar.timeslider.title": "O\u015b czasu",
"pad.toolbar.savedRevision.title": "Zapisane wersje",
"pad.toolbar.savedRevision.title": "Zapisz wersj\u0119",
"pad.toolbar.settings.title": "Ustawienia",
"pad.toolbar.embed.title": "Umie\u015b\u0107 ten Notatnik",
"pad.toolbar.showusers.title": "Poka\u017c u\u017cytkownik\u00f3w",
@ -102,6 +102,8 @@
"timeslider.month.october": "Pa\u017adziernik",
"timeslider.month.november": "Listopad",
"timeslider.month.december": "Grudzie\u0144",
"timeslider.unnamedauthor": "{{num}} nienazwany autor",
"timeslider.unnamedauthors": "{{num}} autor\u00f3w bez nazw",
"pad.savedrevs.marked": "Ta wersja zosta\u0142a w\u0142a\u015bnie oznaczona jako zapisana.",
"pad.userlist.entername": "Wprowad\u017a swoj\u0105 nazw\u0119",
"pad.userlist.unnamed": "bez nazwy",

View file

@ -17,7 +17,9 @@
"pad.settings.fontType": "\u0644\u064a\u06a9\u0628\u06bc\u06d0 \u0689\u0648\u0644:",
"pad.settings.fontType.normal": "\u0646\u0648\u0631\u0645\u0627\u0644",
"pad.settings.fontType.monospaced": "\u0645\u0648\u0646\u0648\u0633\u067e\u06d0\u0633",
"pad.settings.globalView": "\u0646\u0693\u06d0\u0648\u0627\u0644\u0647 \u069a\u06a9\u0627\u0631\u06d0\u062f\u0646\u0647",
"pad.settings.language": "\u0698\u0628\u0647:",
"pad.importExport.importSuccessful": "\u0628\u0631\u064a\u0627\u0644\u06cc \u0634\u0648!",
"pad.importExport.exporthtml": "\u0627\u0686 \u067c\u064a \u0627\u0645 \u0627\u06d0\u0644",
"pad.importExport.exportplain": "\u0633\u0627\u062f\u0647 \u0645\u062a\u0646",
"pad.importExport.exportword": "\u0645\u0627\u064a\u06a9\u0631\u0648\u0633\u0627\u0641\u067c \u0648\u0631\u0689",

View file

@ -19,7 +19,7 @@
"pad.toolbar.clearAuthorship.title": "Limpar as cores de identifica\u00e7\u00e3o de autoria",
"pad.toolbar.import_export.title": "Importar\/Exportar de\/para diferentes formatos de arquivo",
"pad.toolbar.timeslider.title": "Linha do tempo",
"pad.toolbar.savedRevision.title": "Revis\u00f5es Salvas",
"pad.toolbar.savedRevision.title": "Salvar revis\u00e3o",
"pad.toolbar.settings.title": "Configura\u00e7\u00f5es",
"pad.toolbar.embed.title": "Incorporar esta Nota",
"pad.toolbar.showusers.title": "Mostrar os usuarios nesta Nota",
@ -100,6 +100,8 @@
"timeslider.month.october": "Outubro",
"timeslider.month.november": "Novembro",
"timeslider.month.december": "Dezembro",
"timeslider.unnamedauthor": "{{num}} autor desconhecido",
"timeslider.unnamedauthors": "{{num}} autores desconhecidos",
"pad.savedrevs.marked": "Esta revis\u00e3o foi marcada como salva",
"pad.userlist.entername": "Insira o seu nome",
"pad.userlist.unnamed": "Sem t\u00edtulo",

View file

@ -103,6 +103,8 @@
"timeslider.month.october": "\u043e\u043a\u0442\u044f\u0431\u0440\u044c",
"timeslider.month.november": "\u043d\u043e\u044f\u0431\u0440\u044c",
"timeslider.month.december": "\u0434\u0435\u043a\u0430\u0431\u0440\u044c",
"timeslider.unnamedauthor": "{{num}} \u0431\u0435\u0437\u044b\u043c\u044f\u043d\u043d\u044b\u0439 \u0430\u0432\u0442\u043e\u0440",
"timeslider.unnamedauthors": "\u0431\u0435\u0437\u044b\u043c\u044f\u043d\u043d\u044b\u0445 \u0430\u0432\u0442\u043e\u0440\u043e\u0432: {{num}}",
"pad.savedrevs.marked": "\u042d\u0442\u0430 \u0432\u0435\u0440\u0441\u0438\u044f \u0442\u0435\u043f\u0435\u0440\u044c \u043f\u043e\u043c\u0435\u0447\u0435\u043d\u0430 \u043a\u0430\u043a \u0441\u043e\u0445\u0440\u0430\u043d\u0435\u043d\u043d\u0430\u044f",
"pad.userlist.entername": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u0432\u0430\u0448\u0435 \u0438\u043c\u044f",
"pad.userlist.unnamed": "\u0431\u0435\u0437\u044b\u043c\u044f\u043d\u043d\u044b\u0439",

View file

@ -100,6 +100,8 @@
"timeslider.month.october": "Oktober",
"timeslider.month.november": "November",
"timeslider.month.december": "December",
"timeslider.unnamedauthor": "neimenovani avtor {{num}}",
"timeslider.unnamedauthors": "{{num}} neimenovani avtorji",
"pad.savedrevs.marked": "Ta predelava je ozna\u010dena kot shranjena predelava.",
"pad.userlist.entername": "Vpi\u0161ite ime",
"pad.userlist.unnamed": "neimenovana oseba",

View file

@ -100,6 +100,8 @@
"timeslider.month.october": "oktober",
"timeslider.month.november": "november",
"timeslider.month.december": "december",
"timeslider.unnamedauthor": "{{num}} namnl\u00f6s f\u00f6rfattare",
"timeslider.unnamedauthors": "{{num}} namnl\u00f6sa f\u00f6rfattare",
"pad.savedrevs.marked": "Denna revision \u00e4r nu markerad som en sparad revision",
"pad.userlist.entername": "Ange ditt namn",
"pad.userlist.unnamed": "namnl\u00f6s",

View file

@ -38,6 +38,7 @@
"pad.settings.language": "\u0c2d\u0c3e\u0c37",
"pad.importExport.import_export": "\u0c26\u0c3f\u0c17\u0c41\u0c2e\u0c24\u0c3f\/\u0c0e\u0c17\u0c41\u0c2e\u0c24\u0c3f",
"pad.importExport.import": "\u0c2a\u0c3e\u0c20\u0c2e\u0c41 \u0c26\u0c38\u0c4d\u0c24\u0c4d\u0c30\u0c2e\u0c41 \u0c32\u0c47\u0c26\u0c3e \u0c2a\u0c24\u0c4d\u0c30\u0c2e\u0c41\u0c28\u0c41 \u0c26\u0c3f\u0c17\u0c41\u0c2e\u0c24\u0c3f \u0c1a\u0c47\u0c2f\u0c41\u0c2e\u0c41",
"pad.importExport.importSuccessful": "\u0c35\u0c3f\u0c1c\u0c2f\u0c35\u0c02\u0c24\u0c02!",
"pad.importExport.export": "\u0c2a\u0c4d\u0c30\u0c38\u0c4d\u0c24\u0c41\u0c24 \u0c2a\u0c32\u0c15\u0c28\u0c3f \u0c08 \u0c35\u0c3f\u0c27\u0c2e\u0c41\u0c17\u0c3e \u0c0e\u0c17\u0c41\u0c2e\u0c24\u0c3f \u0c1a\u0c47\u0c2f\u0c41\u0c2e\u0c41:",
"pad.importExport.exporthtml": "\u0c39\u0c46\u0c1a\u0c4d \u0c1f\u0c3f \u0c0e\u0c02 \u0c0e\u0c32\u0c4d",
"pad.importExport.exportplain": "\u0c38\u0c3e\u0c26\u0c3e \u0c2a\u0c3e\u0c20\u0c4d\u0c2f\u0c02",

View file

@ -102,6 +102,8 @@
"timeslider.month.october": "\u0416\u043e\u0432\u0442\u0435\u043d\u044c",
"timeslider.month.november": "\u041b\u0438\u0441\u0442\u043e\u043f\u0430\u0434",
"timeslider.month.december": "\u0413\u0440\u0443\u0434\u0435\u043d\u044c",
"timeslider.unnamedauthor": "{{num}} \u0431\u0435\u0437\u0456\u043c\u0435\u043d\u043d\u0438\u0439 \u0430\u0432\u0442\u043e\u0440",
"timeslider.unnamedauthors": "\u0431\u0435\u0437\u0456\u043c\u0435\u043d\u043d\u0438\u0445 \u0430\u0432\u0442\u043e\u0440\u043e\u0432: {{num}}",
"pad.savedrevs.marked": "\u0426\u044e \u0432\u0435\u0440\u0441\u0456\u044e \u043f\u043e\u043c\u0456\u0447\u0435\u043d\u043e \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043d\u043e\u044e \u0432\u0435\u0440\u0441\u0456\u0454\u044e",
"pad.userlist.entername": "\u0412\u0432\u0435\u0434\u0456\u0442\u044c \u0412\u0430\u0448\u0435 \u0456\u043c'\u044f",
"pad.userlist.unnamed": "\u0431\u0435\u0437\u0456\u043c\u0435\u043d\u043d\u0438\u0439",

View file

@ -3,6 +3,8 @@
"authors": [
"Dimension",
"Hydra",
"Yfdyh000",
"\u4e4c\u62c9\u8de8\u6c2a",
"\u71c3\u7389"
]
},
@ -18,8 +20,9 @@
"pad.toolbar.undo.title": "\u64a4\u6d88 (Ctrl-Z)",
"pad.toolbar.redo.title": "\u91cd\u505a (Ctrl-Y)",
"pad.toolbar.clearAuthorship.title": "\u6e05\u9664\u4f5c\u540d\u989c\u8272",
"pad.toolbar.import_export.title": "\u4ee5\u5176\u4ed6\u6587\u4ef6\u683c\u5f0f\u5bfc\u5165\/\u5bfc\u51fa",
"pad.toolbar.timeslider.title": "\u65f6\u95f4\u8f74",
"pad.toolbar.savedRevision.title": "\u5df2\u4fdd\u5b58\u7684\u4fee\u8ba2",
"pad.toolbar.savedRevision.title": "\u4fdd\u5b58\u4fee\u8ba2",
"pad.toolbar.settings.title": "\u8bbe\u7f6e",
"pad.toolbar.embed.title": "\u5d4c\u5165\u6b64\u8bb0\u4e8b\u672c",
"pad.toolbar.showusers.title": "\u663e\u793a\u6b64\u8bb0\u4e8b\u672c\u7684\u7528\u6237",
@ -30,12 +33,19 @@
"pad.permissionDenied": "\u60a8\u6ca1\u6709\u89c2\u770b\u8fd9\u4e2a\u8bb0\u4e8b\u672c\u7684\u6743\u9650",
"pad.wrongPassword": "\u60a8\u7684\u5bc6\u7801\u9519\u4e86",
"pad.settings.padSettings": "\u8bb0\u4e8b\u672c\u8bbe\u7f6e",
"pad.settings.myView": "\u6211\u7684\u89c6\u7a97",
"pad.settings.stickychat": "\u603b\u662f\u5728\u5c4f\u5e55\u4e0a\u663e\u793a\u804a\u5929",
"pad.settings.colorcheck": "\u4f5c\u8005\u989c\u8272",
"pad.settings.linenocheck": "\u884c\u53f7",
"pad.settings.fontType": "\u5b57\u4f53\u7c7b\u578b\uff1a",
"pad.settings.fontType.normal": "\u6b63\u5e38",
"pad.settings.fontType.monospaced": "\u7b49\u5bbd\u5b57\u4f53",
"pad.settings.globalView": "\u6240\u6709\u4eba\u7684\u89c6\u7a97",
"pad.settings.language": "\u8bed\u8a00\uff1a",
"pad.importExport.import_export": "\u5bfc\u5165\/\u5bfc\u51fa",
"pad.importExport.import": "\u4e0a\u8f7d\u4efb\u4f55\u6587\u5b57\u6863\u6216\u6587\u6863",
"pad.importExport.importSuccessful": "\u6210\u529f\uff01",
"pad.importExport.export": "\u5bfc\u51fa\u76ee\u524d\u7684\u8bb0\u4e8b\u7c3f\u4e3a\uff1a",
"pad.importExport.exporthtml": "HTML",
"pad.importExport.exportplain": "\u7eaf\u6587\u672c",
"pad.importExport.exportword": "Microsoft Word",
@ -43,21 +53,36 @@
"pad.importExport.exportopen": "ODF\uff08\u5f00\u653e\u6587\u6863\u683c\u5f0f\uff09",
"pad.importExport.exportdokuwiki": "DokuWiki",
"pad.modals.connected": "\u5df2\u8fde\u63a5\u3002",
"pad.modals.reconnecting": "\u91cd\u65b0\u8fde\u63a5\u5230\u60a8\u7684\u8bb0\u4e8b\u7c3f...",
"pad.modals.forcereconnect": "\u5f3a\u5236\u91cd\u65b0\u8fde\u63a5",
"pad.modals.userdup": "\u5728\u53e6\u4e00\u4e2a\u89c6\u7a97\u4e2d\u6253\u5f00",
"pad.modals.userdup.explanation": "\u6b64\u8bb0\u4e8b\u7c3f\u4f3c\u4e4e\u5728\u6b64\u7535\u8111\u4e0a\u5728\u591a\u4e2a\u6d4f\u89c8\u5668\u89c6\u7a97\u4e2d\u6253\u5f00\u3002",
"pad.modals.userdup.advice": "\u91cd\u65b0\u8fde\u63a5\u5230\u6b64\u89c6\u7a97\u3002",
"pad.modals.unauth": "\u672a\u6388\u6743",
"pad.modals.looping": "\u5df2\u79bb\u7ebf\u3002",
"pad.modals.initsocketfail": "\u65e0\u6cd5\u8bbf\u95ee\u670d\u52a1\u5668\u3002",
"pad.modals.initsocketfail.explanation": "\u65e0\u6cd5\u8fde\u63a5\u5230\u540c\u6b65\u670d\u52a1\u5668\u3002",
"pad.modals.initsocketfail.cause": "\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e\u60a8\u7684\u6d4f\u89c8\u5668\u6216\u60a8\u7684\u4e92\u8054\u7f51\u8fde\u63a5\u7684\u95ee\u9898\u3002",
"pad.modals.slowcommit": "\u5df2\u79bb\u7ebf\u3002",
"pad.modals.slowcommit.explanation": "\u670d\u52a1\u5668\u6ca1\u6709\u54cd\u5e94\u3002",
"pad.modals.slowcommit.cause": "\u8fd9\u53ef\u80fd\u662f\u7531\u4e8e\u7f51\u7edc\u8fde\u63a5\u95ee\u9898\u3002",
"pad.modals.deleted": "\u5df2\u522a\u9664\u3002",
"pad.modals.deleted.explanation": "\u6b64\u8bb0\u4e8b\u672c\u5df2\u88ab\u79fb\u9664\u3002",
"pad.modals.disconnected": "\u60a8\u5df2\u88ab\u79bb\u7ebf\u3002",
"pad.modals.disconnected.explanation": "\u5230\u670d\u52a1\u5668\u7684\u8fde\u63a5\u5df2\u4e22\u5931",
"pad.modals.disconnected.cause": "\u670d\u52a1\u5668\u53ef\u80fd\u65e0\u6cd5\u4f7f\u7528\u3002\u82e5\u6b64\u60c5\u51b5\u6301\u7eed\u53d1\u751f\uff0c\u8bf7\u901a\u77e5\u6211\u4eec\u3002",
"pad.share": "\u5206\u4eab\u6b64\u8bb0\u4e8b\u672c",
"pad.share.readonly": "\u53ea\u80fd\u8bfb",
"pad.share.link": "\u94fe\u63a5",
"pad.share.emebdcode": "\u5d4c\u5165\u7f51\u5740",
"pad.chat": "\u804a\u5929",
"pad.chat.title": "\u6253\u5f00\u6b64\u8bb0\u4e8b\u7c3f\u7684\u804a\u5929\u3002",
"pad.chat.loadmessages": "\u52a0\u8f7d\u66f4\u591a\u4fe1\u606f",
"timeslider.toolbar.returnbutton": "\u8fd4\u56de\u8bb0\u4e8b\u672c",
"timeslider.toolbar.authors": "\u4f5c\u8005\uff1a",
"timeslider.toolbar.authorsList": "\u6ca1\u6709\u4f5c\u8005",
"timeslider.toolbar.exportlink.title": "\u5bfc\u51fa",
"timeslider.exportCurrent": "\u5bfc\u51fa\u76ee\u524d\u7248\u672c\u4e3a\uff1a",
"timeslider.version": "\u7b2c {{version}} \u7248\u672c",
"timeslider.saved": "\u5728{{year}}\u5e74{{month}}{{day}}\u65e5\u4fdd\u5b58",
"timeslider.month.january": "\u4e00\u6708",
@ -72,13 +97,20 @@
"timeslider.month.october": "\u5341\u6708",
"timeslider.month.november": "\u5341\u4e00\u6708",
"timeslider.month.december": "\u5341\u4e8c\u6708",
"timeslider.unnamedauthor": "{{num}}\u533f\u540d\u4f5c\u8005",
"timeslider.unnamedauthors": "{{num}}\u533f\u540d\u4f5c\u8005",
"pad.savedrevs.marked": "\u6b64\u4fee\u8ba2\u5df2\u6807\u8bb0\u4e3a\u4fdd\u5b58\u4fee\u8ba2",
"pad.userlist.entername": "\u8f93\u5165\u60a8\u7684\u59d3\u540d",
"pad.userlist.unnamed": "\u65e0\u540d",
"pad.userlist.guest": "\u8bbf\u5ba2",
"pad.userlist.deny": "\u62d2\u7edd",
"pad.userlist.approve": "\u6279\u51c6",
"pad.editbar.clearcolors": "\u6e05\u9664\u6574\u4e2a\u6587\u6863\u7684\u4f5c\u8005\u989c\u8272\u5417\uff1f",
"pad.impexp.importbutton": "\u73b0\u5728\u5bfc\u5165",
"pad.impexp.importing": "\u6b63\u5728\u5bfc\u5165...",
"pad.impexp.convertFailed": "\u6211\u4eec\u65e0\u6cd5\u5bfc\u5165\u6b64\u6587\u6863\u3002\u8bf7\u4f7f\u7528\u4ed6\u6587\u6863\u683c\u5f0f\u6216\u624b\u52a8\u590d\u5236\u8d34\u4e0a\u3002",
"pad.impexp.uploadFailed": "\u4e0a\u8f7d\u5931\u8d25\uff0c\u8bf7\u91cd\u8bd5",
"pad.impexp.importfailed": "\u5bfc\u5165\u5931\u8d25"
"pad.impexp.importfailed": "\u5bfc\u5165\u5931\u8d25",
"pad.impexp.copypaste": "\u8bf7\u590d\u5236\u7c98\u8d34",
"pad.impexp.exportdisabled": "{{type}} \u683c\u5f0f\u7684\u5bfc\u51fa\u88ab\u7981\u7528\u3002\u6709\u5173\u8be6\u60c5\uff0c\u8bf7\u4e0e\u60a8\u7684\u7cfb\u7edf\u7ba1\u7406\u5458\u8054\u7cfb\u3002"
}

View file

@ -1,7 +1,8 @@
{
"@metadata": {
"authors": {
"1": "Simon Shek"
"0": "Shirayuki",
"2": "Simon Shek"
}
},
"index.newPad": "\u65b0Pad",
@ -19,7 +20,7 @@
"pad.toolbar.clearAuthorship.title": "\u6e05\u9664\u4f5c\u540d\u984f\u8272",
"pad.toolbar.import_export.title": "\u4ee5\u5176\u4ed6\u6a94\u6848\u683c\u5f0f\u5c0e\u5165\uff0f\u532f\u51fa",
"pad.toolbar.timeslider.title": "\u6642\u9593\u8ef8",
"pad.toolbar.savedRevision.title": "\u5df2\u5132\u5b58\u7684\u4fee\u8a02",
"pad.toolbar.savedRevision.title": "\u5132\u5b58\u4fee\u8a02",
"pad.toolbar.settings.title": "\u8a2d\u5b9a",
"pad.toolbar.embed.title": "\u5d4c\u5165\u6b64pad",
"pad.toolbar.showusers.title": "\u986f\u793a\u6b64pad\u7684\u7528\u6236",
@ -86,8 +87,8 @@
"timeslider.toolbar.exportlink.title": "\u532f\u51fa",
"timeslider.exportCurrent": "\u532f\u51fa\u7576\u524d\u7248\u672c\u70ba\uff1a",
"timeslider.version": "\u7248\u672c{{version}}",
"timeslider.saved": "{{year}}{{month}}{{day}}\u4fdd\u5b58",
"timeslider.dateformat": "{{year}}{{month}}{{day}} {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.saved": "{{year}}\u5e74{{month}}{{day}}\u65e5\u4fdd\u5b58",
"timeslider.dateformat": "{{year}}\u5e74{{month}}\u6708{{day}}\u65e5 {{hours}}:{{minutes}}:{{seconds}}",
"timeslider.month.january": "1\u6708",
"timeslider.month.february": "2\u6708",
"timeslider.month.march": "3\u6708",
@ -100,6 +101,8 @@
"timeslider.month.october": "10\u6708",
"timeslider.month.november": "11\u6708",
"timeslider.month.december": "12\u6708",
"timeslider.unnamedauthor": "{{num}} \u533f\u540d\u4f5c\u8005",
"timeslider.unnamedauthors": "{{num}} \u533f\u540d\u4f5c\u8005",
"pad.savedrevs.marked": "\u6b64\u4fee\u8a02\u5df2\u6a19\u8a18\u70ba\u5df2\u4fdd\u5b58\u3002",
"pad.userlist.entername": "\u8f38\u5165\u60a8\u7684\u59d3\u540d",
"pad.userlist.unnamed": "\u672a\u547d\u540d",

View file

@ -253,9 +253,7 @@ exports.getHTML = function(padID, rev, callback)
exportHtml.getPadHTML(pad, undefined, function (err, html)
{
if(ERR(err, callback)) return;
data = {html: html};
callback(null, data);
});
}
@ -325,17 +323,17 @@ exports.getChatHistory = function(padID, start, end, callback)
if(!start || !end)
{
start = 0;
end = pad.chatHead - 1;
end = pad.chatHead;
}
if(start >= chatHead)
if(start >= chatHead && chatHead > 0)
{
callback(new customError("start is higher or equal to the current chatHead","apierror"));
return;
}
if(end >= chatHead)
if(end > chatHead)
{
callback(new customError("end is higher or equal to the current chatHead","apierror"));
callback(new customError("end is higher than the current chatHead","apierror"));
return;
}

View file

@ -151,7 +151,6 @@ exports.getPad = function(id, text, callback)
pad.init(text, function(err)
{
if(ERR(err, callback)) return;
globalPads.set(id, pad);
callback(null, pad);
});

View file

@ -1,5 +1,5 @@
/**
* The Session Manager provides functions to manage session in the database
* The Session Manager provides functions to manage session in the database, it only provides session management for sessions created by the API
*/
/*

View file

@ -0,0 +1,82 @@
/*
* Stores session data in the database
* Source; https://github.com/edy-b/SciFlowWriter/blob/develop/available_plugins/ep_sciflowwriter/db/DirtyStore.js
* This is not used for authors that are created via the API at current
*/
var Store = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/store'),
utils = require('ep_etherpad-lite/node_modules/connect/lib/utils'),
Session = require('ep_etherpad-lite/node_modules/connect/lib/middleware/session/session'),
db = require('ep_etherpad-lite/node/db/DB').db,
log4js = require('ep_etherpad-lite/node_modules/log4js'),
messageLogger = log4js.getLogger("SessionStore");
var SessionStore = module.exports = function SessionStore() {};
SessionStore.prototype.__proto__ = Store.prototype;
SessionStore.prototype.get = function(sid, fn){
messageLogger.debug('GET ' + sid);
var self = this;
db.get("sessionstorage:" + sid, function (err, sess)
{
if (sess) {
sess.cookie.expires = 'string' == typeof sess.cookie.expires ? new Date(sess.cookie.expires) : sess.cookie.expires;
if (!sess.cookie.expires || new Date() < expires) {
fn(null, sess);
} else {
self.destroy(sid, fn);
}
} else {
fn();
}
});
};
SessionStore.prototype.set = function(sid, sess, fn){
messageLogger.debug('SET ' + sid);
db.set("sessionstorage:" + sid, sess);
process.nextTick(function(){
if(fn) fn();
});
};
SessionStore.prototype.destroy = function(sid, fn){
messageLogger.debug('DESTROY ' + sid);
db.remove("sessionstorage:" + sid);
process.nextTick(function(){
if(fn) fn();
});
};
SessionStore.prototype.all = function(fn){
messageLogger.debug('ALL');
var sessions = [];
db.forEach(function(key, value){
if (key.substr(0,15) === "sessionstorage:") {
sessions.push(value);
}
});
fn(null, sessions);
};
SessionStore.prototype.clear = function(fn){
messageLogger.debug('CLEAR');
db.forEach(function(key, value){
if (key.substr(0,15) === "sessionstorage:") {
db.db.remove("session:" + key);
}
});
if(fn) fn();
};
SessionStore.prototype.length = function(fn){
messageLogger.debug('LENGTH');
var i = 0;
db.forEach(function(key, value){
if (key.substr(0,15) === "sessionstorage:") {
i++;
}
});
fn(null, i);
};

View file

@ -216,6 +216,9 @@ var version =
}
};
// set the latest available API version here
exports.latestApiVersion = '1.2.7';
/**
* Handles a HTTP API call
* @param functionName the name of the called function

View file

@ -20,6 +20,7 @@
var ERR = require("async-stacktrace");
var exporthtml = require("../utils/ExportHtml");
var exporttxt = require("../utils/ExportTxt");
var exportdokuwiki = require("../utils/ExportDokuWiki");
var padManager = require("../db/PadManager");
var async = require("async");
@ -48,22 +49,75 @@ exports.doExport = function(req, res, padId, type)
res.attachment(padId + "." + type);
//if this is a plain text export, we can do this directly
// We have to over engineer this because tabs are stored as attributes and not plain text
if(type == "txt")
{
padManager.getPad(padId, function(err, pad)
var txt;
var randNum;
var srcFile, destFile;
async.series([
//render the txt document
function(callback)
{
ERR(err);
if(req.params.rev){
pad.getInternalRevisionAText(req.params.rev, function(junk, text)
exporttxt.getPadTXTDocument(padId, req.params.rev, false, function(err, _txt)
{
res.send(text.text ? text.text : null);
if(ERR(err, callback)) return;
txt = _txt;
callback();
});
},
//decide what to do with the txt export
function(callback)
{
//if this is a txt export, we can send this from here directly
res.send(txt);
callback("stop");
},
//send the convert job to abiword
function(callback)
{
//ensure html can be collected by the garbage collector
txt = null;
destFile = tempDirectory + "/eplite_export_" + randNum + "." + type;
abiword.convertFile(srcFile, destFile, type, callback);
},
//send the file
function(callback)
{
res.sendfile(destFile, null, callback);
},
//clean up temporary files
function(callback)
{
async.parallel([
function(callback)
{
fs.unlink(srcFile, callback);
},
function(callback)
{
//100ms delay to accomidate for slow windows fs
if(os.type().indexOf("Windows") > -1)
{
setTimeout(function()
{
fs.unlink(destFile, callback);
}, 100);
}
else
{
res.send(pad.text());
fs.unlink(destFile, callback);
}
});
}
], callback);
}
], function(err)
{
if(err && err != "stop") ERR(err);
})
}
else if(type == 'dokuwiki')
{

View file

@ -32,14 +32,10 @@ var securityManager = require("../db/SecurityManager");
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins.js");
var log4js = require('log4js');
var messageLogger = log4js.getLogger("message");
var accessLogger = log4js.getLogger("access");
var _ = require('underscore');
var hooks = require("ep_etherpad-lite/static/js/pluginfw/hooks.js");
/**
* A associative array that saves which sessions belong to a pad
*/
var pad2sessions = {};
/**
* A associative array that saves informations about a session
* key = sessionId
@ -83,14 +79,11 @@ exports.handleConnect = function(client)
exports.kickSessionsFromPad = function(padID)
{
//skip if there is nobody on this pad
if(!pad2sessions[padID])
if(socketio.sockets.clients(padID).length == 0)
return;
//disconnect everyone from this pad
for(var i in pad2sessions[padID])
{
socketio.sockets.sockets[pad2sessions[padID][i]].json.send({disconnect:"deleted"});
}
socketio.sockets.in(padID).json.send({disconnect:"deleted"});
}
/**
@ -100,15 +93,13 @@ exports.kickSessionsFromPad = function(padID)
exports.handleDisconnect = function(client)
{
//save the padname of this session
var sessionPad=sessioninfos[client.id].padId;
var session = sessioninfos[client.id];
//if this connection was already etablished with a handshake, send a disconnect message to the others
if(sessioninfos[client.id] && sessioninfos[client.id].author)
if(session && session.author)
{
var author = sessioninfos[client.id].author;
//get the author color out of the db
authorManager.getAuthorColorId(author, function(err, color)
authorManager.getAuthorColorId(session.author, function(err, color)
{
ERR(err);
@ -121,32 +112,19 @@ exports.handleDisconnect = function(client)
"ip": "127.0.0.1",
"colorId": color,
"userAgent": "Anonymous",
"userId": author
"userId": session.author
}
}
};
//Go trough all user that are still on the pad, and send them the USER_LEAVE message
for(i in pad2sessions[sessionPad])
{
var socket = socketio.sockets.sockets[pad2sessions[sessionPad][i]];
if(socket !== undefined){
socket.json.send(messageToTheOtherUsers);
}
}
client.broadcast.to(session.padId).json.send(messageToTheOtherUsers);
});
}
//Go trough all sessions of this pad, search and destroy the entry of this client
for(i in pad2sessions[sessionPad])
{
if(pad2sessions[sessionPad][i] == client.id)
{
pad2sessions[sessionPad].splice(i, 1);
break;
}
}
client.get('remoteAddress', function(er, ip) {
accessLogger.info('[LEAVE] Pad "'+session.padId+'": Author "'+session.author+'" on client '+client.id+' with IP "'+ip+'" left the pad')
})
//Delete the sessioninfos entrys of this session
delete sessioninfos[client.id];
@ -228,11 +206,10 @@ exports.handleMessage = function(client, message)
function(callback)
{
if(!message.padId){
// If the message has a padId we assume the client is already known to the server and needs no re-authorization
callback();
return;
}
if(!message.padId)
return callback();
// Note: message.sessionID is an entirely different kind of
// session from the sessions we use here! Beware! FIXME: Call
// our "sessions" "connections".
@ -292,9 +269,7 @@ exports.handleCustomMessage = function (padID, msg, cb) {
time: time
}
};
for (var i in pad2sessions[padID]) {
socketio.sockets.sockets[pad2sessions[padID][i]].json.send(msg);
}
socketio.sockets.in(padID).json.send(msg);
cb(null, {});
}
@ -352,10 +327,7 @@ function handleChatMessage(client, message)
};
//broadcast the chat message to everyone on the pad
for(var i in pad2sessions[padId])
{
socketio.sockets.sockets[pad2sessions[padId][i]].json.send(msg);
}
socketio.sockets.in(padId).json.send(msg);
callback();
}
@ -422,14 +394,7 @@ function handleGetChatMessages(client, message)
};
// send the messages back to the client
for(var i in pad2sessions[padId])
{
if(pad2sessions[padId][i] == client.id)
{
socketio.sockets.sockets[pad2sessions[padId][i]].json.send(infoMsg);
break;
}
}
client.json.send(infoMsg);
});
}]);
}
@ -453,14 +418,14 @@ function handleSuggestUserName(client, message)
return;
}
var padId = sessioninfos[client.id].padId;
var padId = sessioninfos[client.id].padId,
clients = socketio.sockets.clients(padId);
//search the author and send him this message
for(var i in pad2sessions[padId])
{
if(sessioninfos[pad2sessions[padId][i]].author == message.data.payload.unnamedId)
{
socketio.sockets.sockets[pad2sessions[padId][i]].send(message);
for(var i = 0; i < clients.length; i++) {
var session = sessioninfos[clients[i].id];
if(session && session.author == message.data.payload.unnamedId) {
clients[i].json.send(message);
break;
}
}
@ -501,7 +466,8 @@ function handleUserInfoUpdate(client, message)
type: "USER_NEWINFO",
userInfo: {
userId: author,
name: message.data.userInfo.name,
//set a null name, when there is no name set. cause the client wants it null
name: message.data.userInfo.name || null,
colorId: message.data.userInfo.colorId,
userAgent: "Anonymous",
ip: "127.0.0.1",
@ -509,20 +475,8 @@ function handleUserInfoUpdate(client, message)
}
};
//set a null name, when there is no name set. cause the client wants it null
if(infoMsg.data.userInfo.name == null)
{
infoMsg.data.userInfo.name = null;
}
//Send the other clients on the pad the update message
for(var i in pad2sessions[padId])
{
if(pad2sessions[padId][i] != client.id)
{
socketio.sockets.sockets[pad2sessions[padId][i]].json.send(infoMsg);
}
}
client.broadcast.to(padId).json.send(infoMsg);
}
/**
@ -632,7 +586,14 @@ function handleUserChanges(client, message)
// client) are relative to revision r - 1. The follow function
// rebases "changeset" so that it is relative to revision r
// and can be applied after "c".
try
{
changeset = Changeset.follow(c, changeset, false, apool);
}catch(e){
console.warn("Can't apply USER_CHANGES "+changeset+", possibly because of mismatched follow error");
client.json.send({disconnect:"badChangeset"});
return;
}
if ((r - baseRev) % 200 == 0) { // don't let the stack get too deep
async.nextTick(callback);
@ -682,65 +643,53 @@ function handleUserChanges(client, message)
exports.updatePadClients = function(pad, callback)
{
//skip this step if noone is on this pad
if(!pad2sessions[pad.id])
{
callback();
return;
}
var roomClients = socketio.sockets.clients(pad.id);
if(roomClients.length==0)
return callback();
// since all clients usually get the same set of changesets, store them in local cache
// to remove unnecessary roundtrip to the datalayer
// TODO: in REAL world, if we're working without datalayer cache, all requests to revisions will be fired
// BEFORE first result will be landed to our cache object. The solution is to replace parallel processing
// via async.forEach with sequential for() loop. There is no real benefits of running this in parallel,
// but benefit of reusing cached revision object is HUGE
var revCache = {};
//go trough all sessions on this pad
async.forEach(pad2sessions[pad.id], function(session, callback)
async.forEach(roomClients, function(client, callback)
{
var sid = client.id;
//https://github.com/caolan/async#whilst
//send them all new changesets
async.whilst(
function (){ return sessioninfos[session] && sessioninfos[session].rev < pad.getHeadRevisionNumber()},
function (){ return sessioninfos[sid] && sessioninfos[sid].rev < pad.getHeadRevisionNumber()},
function(callback)
{
var author, revChangeset, currentTime;
var r = sessioninfos[session].rev + 1;
var r = sessioninfos[sid].rev + 1;
async.parallel([
function (callback)
{
pad.getRevisionAuthor(r, function(err, value)
{
if(ERR(err, callback)) return;
author = value;
callback();
});
async.waterfall([
function(callback) {
if(revCache[r])
callback(null, revCache[r]);
else
pad.getRevision(r, callback);
},
function (callback)
function(revision, callback)
{
pad.getRevisionChangeset(r, function(err, value)
{
if(ERR(err, callback)) return;
revChangeset = value;
callback();
});
},
function (callback)
{
pad.getRevisionDate(r, function(err, date)
{
if(ERR(err, callback)) return;
currentTime = date;
callback();
});
}
], function(err)
{
if(ERR(err, callback)) return;
revCache[r] = revision;
var author = revision.meta.author,
revChangeset = revision.changeset,
currentTime = revision.meta.timestamp;
// next if session has not been deleted
if(sessioninfos[session] == null)
if(sessioninfos[sid] == null)
return callback(null);
if(author == sessioninfos[sid].author)
{
callback(null);
return;
}
if(author == sessioninfos[session].author)
{
socketio.sockets.sockets[session].json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
client.json.send({"type":"COLLABROOM","data":{type:"ACCEPT_COMMIT", newRev:r}});
}
else
{
@ -752,20 +701,18 @@ exports.updatePadClients = function(pad, callback)
apool: forWire.pool,
author: author,
currentTime: currentTime,
timeDelta: currentTime - sessioninfos[session].time
timeDelta: currentTime - sessioninfos[sid].time
}};
socketio.sockets.sockets[session].json.send(wireMsg);
client.json.send(wireMsg);
}
if(sessioninfos[session] != null)
{
sessioninfos[session].time = currentTime;
sessioninfos[session].rev = r;
}
sessioninfos[sid].time = currentTime;
sessioninfos[sid].rev = r;
callback(null);
});
}
], callback);
},
callback
);
@ -895,23 +842,14 @@ function handleClientReady(client, message)
function(callback)
{
async.parallel([
//get colorId
//get colorId and name
function(callback)
{
authorManager.getAuthorColorId(author, function(err, value)
authorManager.getAuthor(author, function(err, value)
{
if(ERR(err, callback)) return;
authorColorId = value;
callback();
});
},
//get author name
function(callback)
{
authorManager.getAuthorName(author, function(err, value)
{
if(ERR(err, callback)) return;
authorName = value;
authorColorId = value.colorId;
authorName = value.name;
callback();
});
},
@ -965,21 +903,17 @@ function handleClientReady(client, message)
{
//Check that the client is still here. It might have disconnected between callbacks.
if(sessioninfos[client.id] === undefined)
{
callback();
return;
}
return callback();
//Check if this author is already on the pad, if yes, kick the other sessions!
if(pad2sessions[padIds.padId])
{
for(var i in pad2sessions[padIds.padId])
{
if(sessioninfos[pad2sessions[padIds.padId][i]] && sessioninfos[pad2sessions[padIds.padId][i]].author == author)
{
var socket = socketio.sockets.sockets[pad2sessions[padIds.padId][i]];
if(socket) socket.json.send({disconnect:"userdup"});
}
var roomClients = socketio.sockets.clients(padIds.padId);
for(var i = 0; i < roomClients.length; i++) {
var sinfo = sessioninfos[roomClients[i].id];
if(sinfo && sinfo.author == author) {
// fix user's counter, works on page refresh or if user closes browser window and then rejoins
sessioninfos[roomClients[i].id] = {};
roomClients[i].leave(padIds.padId);
roomClients[i].json.send({disconnect:"userdup"});
}
}
@ -988,18 +922,21 @@ function handleClientReady(client, message)
sessioninfos[client.id].readOnlyPadId = padIds.readOnlyPadId;
sessioninfos[client.id].readonly = padIds.readonly;
//check if there is already a pad2sessions entry, if not, create one
if(!pad2sessions[padIds.padId])
{
pad2sessions[padIds.padId] = [];
//Log creation/(re-)entering of a pad
client.get('remoteAddress', function(er, ip) {
if(pad.head > 0) {
accessLogger.info('[ENTER] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" entered the pad');
}
//Saves in pad2sessions that this session belongs to this pad
pad2sessions[padIds.padId].push(client.id);
else if(pad.head == 0) {
accessLogger.info('[CREATE] Pad "'+padIds.padId+'": Client '+client.id+' with IP "'+ip+'" created the pad');
}
})
//If this is a reconnect, we don't have to send the client the ClientVars again
if(message.reconnect == true)
{
//Join the pad and start receiving updates
client.join(padIds.padId);
//Save the revision in sessioninfos, we take the revision from the info the client send to us
sessioninfos[client.id].rev = message.client_rev;
}
@ -1044,17 +981,12 @@ function handleClientReady(client, message)
// tell the client the number of the latest chat-message, which will be
// used to request the latest 100 chat-messages later (GET_CHAT_MESSAGES)
"chatHead": pad.chatHead,
"numConnectedUsers": pad2sessions[padIds.padId].length,
"isProPad": false,
"numConnectedUsers": roomClients.length,
"readOnlyId": padIds.readOnlyPadId,
"readonly": padIds.readonly,
"serverTimestamp": new Date().getTime(),
"globalPadId": message.padId,
"userId": author,
"cookiePrefsToSet": {
"fullWidth": false,
"hideSidebar": false
},
"abiwordAvailable": settings.abiwordAvailable(),
"plugins": {
"plugins": plugins.plugins,
@ -1080,6 +1012,8 @@ function handleClientReady(client, message)
}
});
//Join the pad and start receiving updates
client.join(padIds.padId);
//Send the clientVars to the Client
client.json.send({type: "CLIENT_VARS", data: clientVars});
//Save the current revision in sessioninfos, should be the same as in clientVars
@ -1109,71 +1043,53 @@ function handleClientReady(client, message)
messageToTheOtherUsers.data.userInfo.name = authorName;
}
// notify all existing users about new user
client.broadcast.to(padIds.padId).json.send(messageToTheOtherUsers);
//Run trough all sessions of this pad
async.forEach(pad2sessions[padIds.padId], function(sessionID, callback)
async.forEach(socketio.sockets.clients(padIds.padId), function(roomClient, callback)
{
var author, socket, sessionAuthorName, sessionAuthorColorId;
var author;
//Jump over, if this session is the connection session
if(roomClient.id == client.id)
return callback();
//Since sessioninfos might change while being enumerated, check if the
//sessionID is still assigned to a valid session
if(sessioninfos[sessionID] !== undefined &&
socketio.sockets.sockets[sessionID] !== undefined){
author = sessioninfos[sessionID].author;
socket = socketio.sockets.sockets[sessionID];
}else {
// If the sessionID is not valid, callback();
callback();
return;
}
async.series([
if(sessioninfos[roomClient.id] !== undefined)
author = sessioninfos[roomClient.id].author;
else // If the client id is not valid, callback();
return callback();
async.waterfall([
//get the authorname & colorId
function(callback)
{
async.parallel([
function(callback)
{
authorManager.getAuthorColorId(author, function(err, value)
{
if(ERR(err, callback)) return;
sessionAuthorColorId = value;
callback();
})
// reuse previously created cache of author's data
if(historicalAuthorData[author])
callback(null, historicalAuthorData[author]);
else
authorManager.getAuthor(author, callback);
},
function(callback)
function (authorInfo, callback)
{
authorManager.getAuthorName(author, function(err, value)
{
if(ERR(err, callback)) return;
sessionAuthorName = value;
callback();
})
}
],callback);
},
function (callback)
{
//Jump over, if this session is the connection session
if(sessionID != client.id)
{
//Send this Session the Notification about the new user
socket.json.send(messageToTheOtherUsers);
//Send the new User a Notification about this other user
var messageToNotifyTheClientAboutTheOthers = {
var msg = {
"type": "COLLABROOM",
"data": {
type: "USER_NEWINFO",
userInfo: {
"ip": "127.0.0.1",
"colorId": sessionAuthorColorId,
"name": sessionAuthorName,
"colorId": authorInfo.colorId,
"name": authorInfo.name,
"userAgent": "Anonymous",
"userId": author
}
}
};
client.json.send(messageToNotifyTheClientAboutTheOthers);
}
client.json.send(msg);
}
], callback);
}, callback);
@ -1521,33 +1437,30 @@ function composePadChangesets(padId, startNum, endNum, callback)
* Get the number of users in a pad
*/
exports.padUsersCount = function (padID, callback) {
if (!pad2sessions[padID] || typeof pad2sessions[padID] != typeof []) {
callback(null, {padUsersCount: 0});
} else {
callback(null, {padUsersCount: pad2sessions[padID].length});
}
callback(null, {
padUsersCount: socketio.sockets.clients(padID).length
});
}
/**
* Get the list of users in a pad
*/
exports.padUsers = function (padID, callback) {
if (!pad2sessions[padID] || typeof pad2sessions[padID] != typeof []) {
callback(null, {padUsers: []});
} else {
var authors = [];
for ( var ix in sessioninfos ) {
if ( sessioninfos[ix].padId !== padID ) {
continue;
}
var aid = sessioninfos[ix].author;
authorManager.getAuthor( aid, function ( err, author ) {
author.id = aid;
authors.push( author );
if ( authors.length === pad2sessions[padID].length ) {
callback(null, {padUsers: authors});
}
var result = [];
async.forEach(socketio.sockets.clients(padId), function(roomClient, callback) {
var s = sessioninfos[roomClient.id];
if(s) {
authorManager.getAuthor(s.author, function(err, author) {
if(ERR(err, callback)) return;
author.id = s.author;
result.push(author);
});
}
}
}, function(err) {
if(ERR(err, callback)) return;
callback(null, {padUsers: result});
});
}

View file

@ -55,13 +55,14 @@ exports.setSocketIO = function(_socket)
socket.sockets.on('connection', function(client)
{
client.set('remoteAddress', client.handshake.address.address);
var clientAuthorized = false;
//wrap the original send function to log the messages
client._send = client.send;
client.send = function(message)
{
messageLogger.info("to " + client.id + ": " + stringifyWithoutPassword(message));
messageLogger.debug("to " + client.id + ": " + stringifyWithoutPassword(message));
client._send(message);
}
@ -79,7 +80,7 @@ exports.setSocketIO = function(_socket)
//check if component is registered in the components array
if(components[message.component])
{
messageLogger.info("from " + client.id + ": " + stringifyWithoutPassword(message));
messageLogger.debug("from " + client.id + ": " + stringifyWithoutPassword(message));
components[message.component].handleMessage(client, message);
}
}

View file

@ -57,4 +57,9 @@ exports.expressCreateServer = function (hook_name, args, cb) {
res.end("OK");
});
});
//Provide a possibility to query the latest available API version
args.app.get('/api', function (req, res) {
res.json({"currentVersion" : apiHandler.latestApiVersion});
});
}

View file

@ -1,14 +1,26 @@
var path = require("path")
, npm = require("npm")
, fs = require("fs");
, fs = require("fs")
, async = require("async");
exports.expressCreateServer = function (hook_name, args, cb) {
args.app.get('/tests/frontend/specs_list.js', function(req, res){
fs.readdir('tests/frontend/specs', function(err, files){
if(err){ return res.send(500); }
async.parallel({
coreSpecs: function(callback){
exports.getCoreTests(callback);
},
pluginSpecs: function(callback){
exports.getPluginTests(callback);
}
},
function(err, results){
var files = results.coreSpecs; // push the core specs to a file object
files = files.concat(results.pluginSpecs); // add the plugin Specs to the core specs
console.debug("Sent browser the following test specs:", files.sort());
res.send("var specs_list = " + JSON.stringify(files.sort()) + ";\n");
});
});
var url2FilePath = function(url){
@ -45,3 +57,28 @@ exports.expressCreateServer = function (hook_name, args, cb) {
res.redirect('/tests/frontend/');
});
}
exports.getPluginTests = function(callback){
var pluginSpecs = [];
var plugins = fs.readdirSync('node_modules');
plugins.forEach(function(plugin){
if(fs.existsSync("node_modules/"+plugin+"/static/tests/frontend/specs")){ // if plugins exists
var specFiles = fs.readdirSync("node_modules/"+plugin+"/static/tests/frontend/specs/");
async.forEach(specFiles, function(spec){ // for each specFile push it to pluginSpecs
pluginSpecs.push("/static/plugins/"+plugin+"/static/tests/frontend/specs/" + spec);
},
function(err){
// blow up if something bad happens!
});
}
});
callback(null, pluginSpecs);
}
exports.getCoreTests = function(callback){
fs.readdir('tests/frontend/specs', function(err, coreSpecs){ // get the core test specs
if(err){ return res.send(500); }
callback(null, coreSpecs);
});
}

View file

@ -4,7 +4,7 @@ var httpLogger = log4js.getLogger("http");
var settings = require('../../utils/Settings');
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var ueberStore = require('../../db/SessionStore');
//checks for basic http auth
exports.basicAuth = function (req, res, next) {
@ -102,15 +102,14 @@ exports.expressConfigure = function (hook_name, args, cb) {
* handling it cleaner :) */
if (!exports.sessionStore) {
exports.sessionStore = new express.session.MemoryStore();
exports.secret = randomString(32);
exports.sessionStore = new ueberStore();
exports.secret = settings.sessionKey; // Isn't this being reset each time the server spawns?
}
args.app.use(express.cookieParser(exports.secret));
args.app.sessionStore = exports.sessionStore;
args.app.use(express.session({store: args.app.sessionStore,
key: 'express_sid' }));
args.app.use(express.session({secret: exports.secret, store: args.app.sessionStore, key: 'express_sid' }));
args.app.use(exports.basicAuth);
}

View file

@ -0,0 +1,87 @@
/**
* Helpers for export requests
*/
/*
* 2011 Peter 'Pita' Martischka (Primary Technology Ltd)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var async = require("async");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace");
var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
exports.getPadPlainText = function(pad, revNum){
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
var textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var apool = pad.pool();
var pieces = [];
for (var i = 0; i < textLines.length; i++){
var line = _analyzeLine(textLines[i], attribLines[i], apool);
if (line.listLevel){
var numSpaces = line.listLevel * 2 - 1;
var bullet = '*';
pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
}
else{
pieces.push(line.text, '\n');
}
}
return pieces.join('');
}
exports._analyzeLine = function(text, aline, apool){
var line = {};
// identify list
var lineMarker = 0;
line.listLevel = 0;
if (aline){
var opIter = Changeset.opIterator(aline);
if (opIter.hasNext()){
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
if (listType){
lineMarker = 1;
listType = /([a-z]+)([12345678])/.exec(listType);
if (listType){
line.listTypeName = listType[1];
line.listLevel = Number(listType[2]);
}
}
}
}
if (lineMarker){
line.text = text.substring(1);
line.aline = Changeset.subattribution(aline, 1);
}
else{
line.text = text;
line.aline = aline;
}
return line;
}
exports._encodeWhitespace = function(s){
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c){
return "&#" +c.charCodeAt(0) + ";"
});
}

View file

@ -21,31 +21,9 @@ var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace");
var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
function getPadPlainText(pad, revNum)
{
var atext = ((revNum !== undefined) ? pad.getInternalRevisionAText(revNum) : pad.atext());
var textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var apool = pad.pool();
var pieces = [];
for (var i = 0; i < textLines.length; i++)
{
var line = _analyzeLine(textLines[i], attribLines[i], apool);
if (line.listLevel)
{
var numSpaces = line.listLevel * 2 - 1;
var bullet = '*';
pieces.push(new Array(numSpaces + 1).join(' '), bullet, ' ', line.text, '\n');
}
else
{
pieces.push(line.text, '\n');
}
}
return pieces.join('');
}
var getPadPlainText = require('./ExportHelper').getPadPlainText
var _analyzeLine = require('./ExportHelper')._analyzeLine;
var _encodeWhitespace = require('./ExportHelper')._encodeWhitespace;
function getPadHTML(pad, revNum, callback)
{
@ -503,45 +481,6 @@ function getHTMLFromAtext(pad, atext, authorColors)
return pieces.join('');
}
function _analyzeLine(text, aline, apool)
{
var line = {};
// identify list
var lineMarker = 0;
line.listLevel = 0;
if (aline)
{
var opIter = Changeset.opIterator(aline);
if (opIter.hasNext())
{
var listType = Changeset.opAttributeValue(opIter.next(), 'list', apool);
if (listType)
{
lineMarker = 1;
listType = /([a-z]+)([12345678])/.exec(listType);
if (listType)
{
line.listTypeName = listType[1];
line.listLevel = Number(listType[2]);
}
}
}
}
if (lineMarker)
{
line.text = text.substring(1);
line.aline = Changeset.subattribution(aline, 1);
}
else
{
line.text = text;
line.aline = aline;
}
return line;
}
exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
{
padManager.getPad(padId, function (err, pad)
@ -578,79 +517,6 @@ exports.getPadHTMLDocument = function (padId, revNum, noDocType, callback)
});
}
function _encodeWhitespace(s) {
return s.replace(/[^\x21-\x7E\s\t\n\r]/g, function(c)
{
return "&#" +c.charCodeAt(0) + ";"
});
}
// copied from ACE
function _processSpaces(s)
{
var doesWrap = true;
if (s.indexOf("<") < 0 && !doesWrap)
{
// short-cut
return s.replace(/ /g, '&nbsp;');
}
var parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function (m)
{
parts.push(m);
});
if (doesWrap)
{
var endOfLine = true;
var beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--)
{
var p = parts[i];
if (p == " ")
{
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
}
else if (p.charAt(0) != "<")
{
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (var i = 0; i < parts.length; i++)
{
var p = parts[i];
if (p == " ")
{
parts[i] = '&nbsp;';
break;
}
else if (p.charAt(0) != "<")
{
break;
}
}
}
else
{
for (var i = 0; i < parts.length; i++)
{
var p = parts[i];
if (p == " ")
{
parts[i] = '&nbsp;';
}
}
}
return parts.join('');
}
// copied from ACE
var _REGEX_WORDCHAR = /[\u0030-\u0039\u0041-\u005A\u0061-\u007A\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u00FF\u0100-\u1FFF\u3040-\u9FFF\uF900-\uFDFF\uFE70-\uFEFE\uFF10-\uFF19\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFDC]/;
@ -676,3 +542,57 @@ function _findURLs(text)
return urls;
}
// copied from ACE
function _processSpaces(s){
var doesWrap = true;
if (s.indexOf("<") < 0 && !doesWrap){
// short-cut
return s.replace(/ /g, '&nbsp;');
}
var parts = [];
s.replace(/<[^>]*>?| |[^ <]+/g, function (m){
parts.push(m);
});
if (doesWrap){
var endOfLine = true;
var beforeSpace = false;
// last space in a run is normal, others are nbsp,
// end of line is nbsp
for (var i = parts.length - 1; i >= 0; i--){
var p = parts[i];
if (p == " "){
if (endOfLine || beforeSpace) parts[i] = '&nbsp;';
endOfLine = false;
beforeSpace = true;
}
else if (p.charAt(0) != "<"){
endOfLine = false;
beforeSpace = false;
}
}
// beginning of line is nbsp
for (var i = 0; i < parts.length; i++){
var p = parts[i];
if (p == " "){
parts[i] = '&nbsp;';
break;
}
else if (p.charAt(0) != "<"){
break;
}
}
}
else
{
for (var i = 0; i < parts.length; i++){
var p = parts[i];
if (p == " "){
parts[i] = '&nbsp;';
}
}
}
return parts.join('');
}

293
src/node/utils/ExportTxt.js Normal file
View file

@ -0,0 +1,293 @@
/**
* TXT export
*/
/*
* 2013 John McLear
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var async = require("async");
var Changeset = require("ep_etherpad-lite/static/js/Changeset");
var padManager = require("../db/PadManager");
var ERR = require("async-stacktrace");
var Security = require('ep_etherpad-lite/static/js/security');
var hooks = require('ep_etherpad-lite/static/js/pluginfw/hooks');
var getPadPlainText = require('./ExportHelper').getPadPlainText;
var _analyzeLine = require('./ExportHelper')._analyzeLine;
// This is slightly different than the HTML method as it passes the output to getTXTFromAText
function getPadTXT(pad, revNum, callback)
{
var atext = pad.atext;
var html;
async.waterfall([
// fetch revision atext
function (callback)
{
if (revNum != undefined)
{
pad.getInternalRevisionAText(revNum, function (err, revisionAtext)
{
if(ERR(err, callback)) return;
atext = revisionAtext;
callback();
});
}
else
{
callback(null);
}
},
// convert atext to html
function (callback)
{
html = getTXTFromAtext(pad, atext); // only this line is different to the HTML function
callback(null);
}],
// run final callback
function (err)
{
if(ERR(err, callback)) return;
callback(null, html);
});
}
exports.getPadTXT = getPadTXT;
// This is different than the functionality provided in ExportHtml as it provides formatting
// functionality that is designed specifically for TXT exports
function getTXTFromAtext(pad, atext, authorColors)
{
var apool = pad.apool();
var textLines = atext.text.slice(0, -1).split('\n');
var attribLines = Changeset.splitAttributionLines(atext.attribs, atext.text);
var tags = ['h1', 'h2', 'strong', 'em', 'u', 's'];
var props = ['heading1', 'heading2', 'bold', 'italic', 'underline', 'strikethrough'];
var anumMap = {};
var css = "";
props.forEach(function (propName, i)
{
var propTrueNum = apool.putAttrib([propName, true], true);
if (propTrueNum >= 0)
{
anumMap[propTrueNum] = i;
}
});
function getLineTXT(text, attribs)
{
var propVals = [false, false, false];
var ENTER = 1;
var STAY = 2;
var LEAVE = 0;
// Use order of tags (b/i/u) as order of nesting, for simplicity
// and decent nesting. For example,
// <b>Just bold<b> <b><i>Bold and italics</i></b> <i>Just italics</i>
// becomes
// <b>Just bold <i>Bold and italics</i></b> <i>Just italics</i>
var taker = Changeset.stringIterator(text);
var assem = Changeset.stringAssembler();
var openTags = [];
var idx = 0;
function processNextChars(numChars)
{
if (numChars <= 0)
{
return;
}
var iter = Changeset.opIterator(Changeset.subattribution(attribs, idx, idx + numChars));
idx += numChars;
while (iter.hasNext())
{
var o = iter.next();
var propChanged = false;
Changeset.eachAttribNumber(o.attribs, function (a)
{
if (a in anumMap)
{
var i = anumMap[a]; // i = 0 => bold, etc.
if (!propVals[i])
{
propVals[i] = ENTER;
propChanged = true;
}
else
{
propVals[i] = STAY;
}
}
});
for (var i = 0; i < propVals.length; i++)
{
if (propVals[i] === true)
{
propVals[i] = LEAVE;
propChanged = true;
}
else if (propVals[i] === STAY)
{
propVals[i] = true; // set it back
}
}
// now each member of propVal is in {false,LEAVE,ENTER,true}
// according to what happens at start of span
if (propChanged)
{
// leaving bold (e.g.) also leaves italics, etc.
var left = false;
for (var i = 0; i < propVals.length; i++)
{
var v = propVals[i];
if (!left)
{
if (v === LEAVE)
{
left = true;
}
}
else
{
if (v === true)
{
propVals[i] = STAY; // tag will be closed and re-opened
}
}
}
var tags2close = [];
for (var i = propVals.length - 1; i >= 0; i--)
{
if (propVals[i] === LEAVE)
{
//emitCloseTag(i);
tags2close.push(i);
propVals[i] = false;
}
else if (propVals[i] === STAY)
{
//emitCloseTag(i);
tags2close.push(i);
}
}
for (var i = 0; i < propVals.length; i++)
{
if (propVals[i] === ENTER || propVals[i] === STAY)
{
propVals[i] = true;
}
}
// propVals is now all {true,false} again
} // end if (propChanged)
var chars = o.chars;
if (o.lines)
{
chars--; // exclude newline at end of line, if present
}
var s = taker.take(chars);
// removes the characters with the code 12. Don't know where they come
// from but they break the abiword parser and are completly useless
// s = s.replace(String.fromCharCode(12), "");
// remove * from s, it's just not needed on a blank line.. This stops
// plugins from being able to display * at the beginning of a line
// s = s.replace("*", ""); // Then remove it
assem.append(s);
} // end iteration over spans in line
var tags2close = [];
for (var i = propVals.length - 1; i >= 0; i--)
{
if (propVals[i])
{
tags2close.push(i);
propVals[i] = false;
}
}
} // end processNextChars
processNextChars(text.length - idx);
return(assem.toString());
} // end getLineHTML
var pieces = [css];
// Need to deal with constraints imposed on HTML lists; can
// only gain one level of nesting at once, can't change type
// mid-list, etc.
// People might use weird indenting, e.g. skip a level,
// so we want to do something reasonable there. We also
// want to deal gracefully with blank lines.
// => keeps track of the parents level of indentation
var lists = []; // e.g. [[1,'bullet'], [3,'bullet'], ...]
for (var i = 0; i < textLines.length; i++)
{
var line = _analyzeLine(textLines[i], attribLines[i], apool);
var lineContent = getLineTXT(line.text, line.aline);
if(line.listTypeName == "bullet"){
lineContent = "* " + lineContent; // add a bullet
}
if(line.listLevel > 0){
for (var j = line.listLevel - 1; j >= 0; j--){
pieces.push('\t');
}
if(line.listTypeName == "number"){
pieces.push(line.listLevel + ". ");
// This is bad because it doesn't truly reflect what the user
// sees because browsers do magic on nested <ol><li>s
}
pieces.push(lineContent, '\n');
}else{
pieces.push(lineContent, '\n');
}
}
return pieces.join('');
}
exports.getTXTFromAtext = getTXTFromAtext;
exports.getPadTXTDocument = function (padId, revNum, noDocType, callback)
{
padManager.getPad(padId, function (err, pad)
{
if(ERR(err, callback)) return;
getPadTXT(pad, revNum, function (err, html)
{
if(ERR(err, callback)) return;
callback(null, html);
});
});
}

View file

@ -26,6 +26,8 @@ var argv = require('./Cli').argv;
var npm = require("npm/lib/npm.js");
var vm = require('vm');
var log4js = require("log4js");
var randomString = require('ep_etherpad-lite/static/js/pad_utils').randomString;
/* Root path of the installation */
exports.root = path.normalize(path.join(npm.dir, ".."));
@ -112,6 +114,11 @@ exports.loglevel = "INFO";
*/
exports.logconfig = { appenders: [{ type: "console" }]};
/*
* Session Key, do not sure this.
*/
exports.sessionKey = false;
/* This setting is used if you need authentication and/or
* authorization. Note: /admin always requires authentication, and
* either authorization by a module, or a user with is_admin set */
@ -132,8 +139,6 @@ exports.abiwordAvailable = function()
}
}
exports.reloadSettings = function reloadSettings() {
// Discover where the settings file lives
var settingsFilename = argv.settings || "settings.json";
@ -152,6 +157,7 @@ exports.reloadSettings = function reloadSettings() {
try {
if(settingsStr) {
settings = vm.runInContext('exports = '+settingsStr, vm.createContext(), "settings.json");
settings = JSON.parse(JSON.stringify(settings)) // fix objects having constructors of other vm.context
}
}catch(e){
console.error('There was an error processing your settings.json file: '+e.message);
@ -184,6 +190,11 @@ exports.reloadSettings = function reloadSettings() {
log4js.setGlobalLogLevel(exports.loglevel);//set loglevel
log4js.replaceConsole();
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");
}
if(exports.dbType === "dirty"){
console.warn("DirtyDB is used. This is fine for testing but not recommended for production.")
}

View file

@ -16,7 +16,7 @@
"require-kernel" : "1.0.5",
"resolve" : "0.2.x",
"socket.io" : "0.9.x",
"ueberDB" : "0.1.9",
"ueberDB" : "0.1.94",
"async" : "0.1.x",
"express" : "3.x",
"connect" : "2.4.x",
@ -40,11 +40,10 @@
},
"bin": { "etherpad-lite": "./node/server.js" },
"devDependencies": {
"jshint" : "*",
"wd" : "0.0.26"
"wd" : "0.0.31"
},
"engines" : { "node" : ">=0.6.0",
"engines" : { "node" : ">=0.6.3",
"npm" : ">=1.0"
},
"version" : "1.2.7"
"version" : "1.2.8"
}

View file

@ -1,65 +1,59 @@
html, body {
height: 100%;
box-sizing: border-box;
}
body {
margin: 0;
color: #333;
font: 14px helvetica, sans-serif;
background: #ddd;
background: -webkit-radial-gradient(circle,#aaa,#eee 60%) center fixed;
background: -moz-radial-gradient(circle,#aaa,#eee 60%) center fixed;
background: -ms-radial-gradient(circle,#aaa,#eee 60%) center fixed;
background: -o-radial-gradient(circle,#aaa,#eee 60%) center fixed;
}
#topborder {
border-top: 8px solid rgba(51, 51, 51, 0.8);
position: fixed;
top: 0px;
width: 100%;
background: #eee;
}
div.menu {
background: none repeat scroll 0% 0% rgba(255, 255, 255, 0.75);
box-shadow: 0px -4px 4px rgba(0, 0, 0, 0.3);
display: block;
float: left;
height: 100%;
padding: 15px;
position: fixed;
width: 220px;
border-right: 1px solid #ccc;
position: fixed;
}
div.menu ul {
padding: 0;
}
div.menu li {
list-style: none;
margin-left: 3px;
line-height: 1.6
line-height: 3;
border-top: 1px solid #ccc;
}
div.menu li:last-child {
border-bottom: 1px solid #ccc;
}
div.innerwrapper {
display: block;
float: right;
opacity: 0.9;
padding: 15px;
max-width: 860px;
border-radius: 0 0 7px 7px;
margin-left:250px;
min-width:400px;
padding-left: 265px;
}
#wrapper {
background: none repeat scroll 0px 0px #FFFFFF;
box-shadow: 0px 1px 8px rgba(0, 0, 0, 0.3);
box-shadow: 0px 1px 10px rgba(0, 0, 0, 0.2);
margin: auto;
max-width: 1150px;
min-height: 100%;
overflow: auto;
padding-left: 15px;
opacity: .9;
}
h1 {
font-size: 29px;
}
h2 {
font-size: 24px;
}
.separator {
margin: 10px 0;
height: 1px;
@ -69,37 +63,45 @@ h2 {
background: -ms-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
background: -o-linear-gradient(left, #fff, #aaa 20%, #aaa 80%, #fff);
}
form {
margin-bottom: 0;
}
#inner {
width: 300px;
margin: 0 auto;
}
input {
font-weight: bold;
font-size: 15px;
}
input[type="button"] {
padding: 4px 6px;
margin: 0;
}
table input[type="button"] {
float: right;
width: 100px;
}
input[type="text"] {
border-radius: 3px;
box-sizing: border-box;
-moz-box-sizing: border-box;
padding: 10px;
*padding: 0; /* IE7 hack */
*padding: 0;
/* IE7 hack */
width: 100%;
outline: none;
border: 1px solid #ddd;
margin: 0 0 5px 0;
max-width: 500px;
}
table {
border: 1px solid #ddd;
border-radius: 3px;
@ -107,24 +109,34 @@ table {
width: 100%;
margin: 20px 0;
}
table thead tr {
background: #eee;
}
td, th {
padding: 5px;
}
.template {
display: none;
}
#progress {
position: absolute;
bottom: 50px;
}
.settings {
margin-top:10px;
width:100%;
min-height:600px;
#progress img {
vertical-align: top;
}
.settings {
outline: none;
width: 100%;
min-height: 500px;
}
#response {
display: inline;
}
@ -132,9 +144,77 @@ td, th {
a:link, a:visited, a:hover, a:focus {
color: #333333;
text-decoration: none;
border-bottom: #333333 1px dotted;
}
a:focus, a:hover {
border-bottom: #333333 1px solid;
}
pre {
white-space: pre-wrap;
word-wrap: break-word;
}
@media (max-width: 720px) {
div.innerwrapper {
padding: 0 15px 15px 15px;
}
div.menu {
padding: 1px 15px 0 15px;
position: static;
height: auto;
border-right: none;
width: auto;
}
table {
border: none;
}
table, thead, tbody, td, tr {
display: block;
}
thead tr {
display: none;
}
tr {
border: 1px solid #ccc;
margin-bottom: 5px;
border-radius: 3px;
}
td {
border: none;
border-bottom: 1px solid #eee;
position: relative;
padding-left: 50%;
white-space: normal;
text-align: left;
}
td.name {
word-wrap: break-word;
}
td:before {
position: absolute;
top: 6px;
left: 6px;
text-align: left;
padding-right: 10px;
white-space: nowrap;
font-weight: bold;
content: attr(data-label);
}
td:last-child {
border-bottom: none;
}
table input[type="button"] {
float: none;
}
}

View file

@ -176,3 +176,11 @@ p {
}
#overlaysdiv { position: absolute; left: -1000px; top: -1000px; }
/* Stops super long lines without being spaces such as aaaaaaaaaaaaaa*100 breaking the editor
Commented out because it stops IE from being able to render the document, crazy IE bug is crazy. */
/*
.ace-line{
overflow:hidden;
}
*/

View file

@ -33,19 +33,6 @@ function object(o)
f.prototype = o;
return new f();
}
var userAgent = (((function () {return this;})().navigator || {}).userAgent || 'node-js').toLowerCase();
// Figure out what browser is being used (stolen from jquery 1.2.1)
var browser = {
version: (userAgent.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
safari: /webkit/.test(userAgent),
opera: /opera/.test(userAgent),
msie: /msie/.test(userAgent) && !/opera/.test(userAgent),
mozilla: /mozilla/.test(userAgent) && !/(compatible|webkit)/.test(userAgent),
windows: /windows/.test(userAgent),
mobile: /mobile/.test(userAgent) || /android/.test(userAgent)
};
function getAssoc(obj, name)
{
@ -97,7 +84,6 @@ var noop = function(){};
exports.isNodeText = isNodeText;
exports.object = object;
exports.browser = browser;
exports.getAssoc = getAssoc;
exports.setAssoc = setAssoc;
exports.binarySearch = binarySearch;

View file

@ -28,7 +28,7 @@ $ = jQuery = require('./rjquery').$;
_ = require("./underscore");
var isNodeText = Ace2Common.isNodeText,
browser = Ace2Common.browser,
browser = $.browser,
getAssoc = Ace2Common.getAssoc,
setAssoc = Ace2Common.setAssoc,
isTextNode = Ace2Common.isTextNode,
@ -154,7 +154,8 @@ function Ace2Inner(){
var dmesg = noop;
window.dmesg = noop;
var scheduler = parent;
var scheduler = parent; // hack for opera required
var textFace = 'monospace';
var textSize = 12;
@ -1621,9 +1622,17 @@ function Ace2Inner(){
lines = ccData.lines;
var lineAttribs = ccData.lineAttribs;
var linesWrapped = ccData.linesWrapped;
var scrollToTheLeftNeeded = false;
if (linesWrapped > 0)
{
if(!browser.ie){
// chrome decides in it's infinite wisdom that its okay to put the browsers visisble window in the middle of the span
// an outcome of this is that the first chars of the string are no longer visible to the user.. Yay chrome..
// Move the browsers visible area to the left hand side of the span
// Firefox isn't quite so bad, but it's still pretty quirky.
var scrollToTheLeftNeeded = true;
}
// console.log("Editor warning: " + linesWrapped + " long line" + (linesWrapped == 1 ? " was" : "s were") + " hard-wrapped into " + ccData.numLinesAfter + " lines.");
}
@ -1691,6 +1700,10 @@ function Ace2Inner(){
//console.log("removed: "+id);
});
if(scrollToTheLeftNeeded){ // needed to stop chrome from breaking the ui when long strings without spaces are pasted
$("#innerdocbody").scrollLeft(0);
}
p.mark("findsel");
// if the nodes that define the selection weren't encountered during
// content collection, figure out where those nodes are now.
@ -1896,7 +1909,7 @@ function Ace2Inner(){
var prevLine = rep.lines.prev(thisLine);
var prevLineText = prevLine.text;
var theIndent = /^ *(?:)/.exec(prevLineText)[0];
if (/[\[\(\{]\s*$/.exec(prevLineText)) theIndent += THE_TAB;
if (/[\[\(\:\{]\s*$/.exec(prevLineText)) theIndent += THE_TAB;
var cs = Changeset.builder(rep.lines.totalWidth()).keep(
rep.lines.offsetOfIndex(lineNum), lineNum).insert(
theIndent, [
@ -2817,7 +2830,6 @@ function Ace2Inner(){
rep.selStart = selectStart;
rep.selEnd = selectEnd;
rep.selFocusAtStart = newSelFocusAtStart;
if (mozillaFakeArrows) mozillaFakeArrows.notifySelectionChanged();
currentCallStack.repChanged = true;
return true;
@ -3317,8 +3329,10 @@ function Ace2Inner(){
function doIndentOutdent(isOut)
{
if (!(rep.selStart && rep.selEnd) ||
((rep.selStart[0] == rep.selEnd[0]) && (rep.selStart[1] == rep.selEnd[1]) && rep.selEnd[1] > 1))
if (!((rep.selStart && rep.selEnd) ||
((rep.selStart[0] == rep.selEnd[0]) && (rep.selStart[1] == rep.selEnd[1]) && rep.selEnd[1] > 1)) &&
(isOut != true)
)
{
return false;
}
@ -3326,7 +3340,6 @@ function Ace2Inner(){
var firstLine, lastLine;
firstLine = rep.selStart[0];
lastLine = Math.max(firstLine, rep.selEnd[0] - ((rep.selEnd[1] === 0) ? 1 : 0));
var mods = [];
for (var n = firstLine; n <= lastLine; n++)
{
@ -3539,7 +3552,6 @@ function Ace2Inner(){
{
// if (DEBUG && window.DONT_INCORP) return;
if (!isEditable) return;
var type = evt.type;
var charCode = evt.charCode;
var keyCode = evt.keyCode;
@ -3561,6 +3573,11 @@ function Ace2Inner(){
var isModKey = ((!charCode) && ((type == "keyup") || (type == "keydown")) && (keyCode == 16 || keyCode == 17 || keyCode == 18 || keyCode == 20 || keyCode == 224 || keyCode == 91));
if (isModKey) return;
// If the key is a keypress and the browser is opera and the key is enter, do nothign at all as this fires twice.
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) ? (type == "keydown") : (type == "keypress"));
var isTypeForCmdKey = ((browser.msie || browser.safari) ? (type == "keydown") : (type == "keypress"));
@ -3690,12 +3707,73 @@ function Ace2Inner(){
doDeleteKey();
specialHandled = true;
}
if (mozillaFakeArrows && mozillaFakeArrows.handleKeyEvent(evt))
{
evt.preventDefault();
specialHandled = true;
if((evt.which == 33 || evt.which == 34) && type == 'keydown'){
var oldVisibleLineRange = getVisibleLineRange();
var topOffset = rep.selStart[0] - oldVisibleLineRange[0];
if(topOffset < 0 ){
topOffset = 0;
}
var isPageDown = evt.which === 34;
var isPageUp = evt.which === 33;
scheduler.setTimeout(function(){
var newVisibleLineRange = getVisibleLineRange();
var linesCount = rep.lines.length();
var newCaretRow = rep.selStart[0];
if(isPageUp){
newCaretRow = oldVisibleLineRange[0];
}
if(isPageDown){
newCaretRow = newVisibleLineRange[0] + topOffset;
}
//ensure min and max
if(newCaretRow < 0){
newCaretRow = 0;
}
if(newCaretRow >= linesCount){
newCaretRow = linesCount-1;
}
rep.selStart[0] = newCaretRow;
rep.selEnd[0] = newCaretRow;
updateBrowserSelectionFromRep();
}, 200);
}
/* Attempt to apply some sanity to cursor handling in Chrome after a copy / paste event
We have to do this the way we do because rep. doesn't hold the value for keyheld events IE if the user
presses and holds the arrow key */
if((evt.which == 37 || evt.which == 38 || evt.which == 39 || evt.which == 40) && $.browser.chrome){
var newVisibleLineRange = getVisibleLineRange(); // get the current visible range -- This works great.
var lineHeight = textLineHeight(); // what Is the height of each line?
var myselection = document.getSelection(); // get the current caret selection, can't use rep. here because that only gives us the start position not the current
var caretOffsetTop = myselection.focusNode.parentNode.offsetTop; // get the carets selection offset in px IE 214
if(caretOffsetTop){ // sometimes caretOffsetTop bugs out and returns 0, not sure why, possible Chrome bug? Either way if it does we don't wanna mess with it
var lineNum = Math.round(caretOffsetTop / lineHeight) ; // Get the current Line Number IE 84
newVisibleLineRange[1] = newVisibleLineRange[1]-1;
var caretIsVisible = (lineNum > newVisibleLineRange[0] && lineNum < newVisibleLineRange[1]); // Is the cursor in the visible Range IE ie 84 > 14 and 84 < 90?
if(!caretIsVisible){ // is the cursor no longer visible to the user?
// Oh boy the caret is out of the visible area, I need to scroll the browser window to lineNum.
// Get the new Y by getting the line number and multiplying by the height of each line.
if(evt.which == 37 || evt.which == 38){ // If left or up
var newY = lineHeight * (lineNum -1); // -1 to go to the line above
}else if(evt.which == 39 || evt.which == 40){ // if down or right
var newY = getScrollY() + (lineHeight*3); // the offset and one additional line
}
setScrollY(newY); // set the scroll height of the browser
}
}
}
}
if (type == "keydown")
@ -3801,7 +3879,6 @@ function Ace2Inner(){
selection.endPoint = getPointForLineAndChar(se);
selection.focusAtStart = !! rep.selFocusAtStart;
setSelection(selection);
}
@ -4119,6 +4196,11 @@ function Ace2Inner(){
selection.startPoint = pointFromRangeBound(range.startContainer, range.startOffset);
selection.endPoint = pointFromRangeBound(range.endContainer, range.endOffset);
selection.focusAtStart = (((range.startContainer != range.endContainer) || (range.startOffset != range.endOffset)) && browserSelection.anchorNode && (browserSelection.anchorNode == range.endContainer) && (browserSelection.anchorOffset == range.endOffset));
if(selection.startPoint.node.ownerDocument !== window.document){
return null;
}
return selection;
}
else return null;
@ -5033,331 +5115,6 @@ function Ace2Inner(){
editorInfo.ace_doInsertUnorderedList = doInsertUnorderedList;
editorInfo.ace_doInsertOrderedList = doInsertOrderedList;
var mozillaFakeArrows = (browser.mozilla && (function()
{
// In Firefox 2, arrow keys are unstable while DOM-manipulating
// operations are going on. Specifically, if an operation
// (computation that ties up the event queue) is going on (in the
// call-stack of some event, like a timeout) that at some point
// mutates nodes involved in the selection, then the arrow
// keypress may (randomly) move the caret to the beginning or end
// of the document. If the operation also mutates the selection
// range, the old selection or the new selection may be used, or
// neither.
// As long as the arrow is pressed during the busy operation, it
// doesn't seem to matter that the keydown and keypress events
// aren't generated until afterwards, or that the arrow movement
// can still be stopped (meaning it hasn't been performed yet);
// Firefox must be preserving some old information about the
// selection or the DOM from when the key was initially pressed.
// However, it also doesn't seem to matter when the key was
// actually pressed relative to the time of the mutation within
// the prolonged operation. Also, even in very controlled tests
// (like a mutation followed by a long period of busyWaiting), the
// problem shows up often but not every time, with no discernable
// pattern. Who knows, it could have something to do with the
// caret-blinking timer, or DOM changes not being applied
// immediately.
// This problem, mercifully, does not show up at all in IE or
// Safari. My solution is to have my own, full-featured arrow-key
// implementation for Firefox.
// Note that the problem addressed here is potentially very subtle,
// especially if the operation is quick and is timed to usually happen
// when the user is idle.
// features:
// - 'up' and 'down' arrows preserve column when passing through shorter lines
// - shift-arrows extend the "focus" point, which may be start or end of range
// - the focus point is kept horizontally and vertically scrolled into view
// - arrows without shift cause caret to move to beginning or end of selection (left,right)
// or move focus point up or down a line (up,down)
// - command-(left,right,up,down) on Mac acts like (line-start, line-end, doc-start, doc-end)
// - takes wrapping into account when doesWrap is true, i.e. up-arrow and down-arrow move
// between the virtual lines within a wrapped line; this was difficult, and unfortunately
// requires mutating the DOM to get the necessary information
var savedFocusColumn = 0; // a value of 0 has no effect
var updatingSelectionNow = false;
function getVirtualLineView(lineNum)
{
var lineNode = rep.lines.atIndex(lineNum).lineNode;
while (lineNode.firstChild && isBlockElement(lineNode.firstChild))
{
lineNode = lineNode.firstChild;
}
return makeVirtualLineView(lineNode);
}
function markerlessLineAndChar(line, chr)
{
return [line, chr - rep.lines.atIndex(line).lineMarker];
}
function markerfulLineAndChar(line, chr)
{
return [line, chr + rep.lines.atIndex(line).lineMarker];
}
return {
notifySelectionChanged: function()
{
if (!updatingSelectionNow)
{
savedFocusColumn = 0;
}
},
handleKeyEvent: function(evt)
{
// returns "true" if handled
if (evt.type != "keypress") return false;
var keyCode = evt.keyCode;
if (keyCode < 37 || keyCode > 40) return false;
incorporateUserChanges();
if (!(rep.selStart && rep.selEnd)) return true;
// {byWord,toEnd,normal}
var moveMode = (evt.altKey ? "byWord" : (evt.ctrlKey ? "byWord" : (evt.metaKey ? "toEnd" : "normal")));
var anchorCaret = markerlessLineAndChar(rep.selStart[0], rep.selStart[1]);
var focusCaret = markerlessLineAndChar(rep.selEnd[0], rep.selEnd[1]);
var wasCaret = isCaret();
if (rep.selFocusAtStart)
{
var tmp = anchorCaret;
anchorCaret = focusCaret;
focusCaret = tmp;
}
var K_UP = 38,
K_DOWN = 40,
K_LEFT = 37,
K_RIGHT = 39;
var dontMove = false;
if (wasCaret && !evt.shiftKey)
{
// collapse, will mutate both together
anchorCaret = focusCaret;
}
else if ((!wasCaret) && (!evt.shiftKey))
{
if (keyCode == K_LEFT)
{
// place caret at beginning
if (rep.selFocusAtStart) anchorCaret = focusCaret;
else focusCaret = anchorCaret;
if (moveMode == "normal") dontMove = true;
}
else if (keyCode == K_RIGHT)
{
// place caret at end
if (rep.selFocusAtStart) focusCaret = anchorCaret;
else anchorCaret = focusCaret;
if (moveMode == "normal") dontMove = true;
}
else
{
// collapse, will mutate both together
anchorCaret = focusCaret;
}
}
if (!dontMove)
{
function lineLength(i)
{
var entry = rep.lines.atIndex(i);
return entry.text.length - entry.lineMarker;
}
function lineText(i)
{
var entry = rep.lines.atIndex(i);
return entry.text.substring(entry.lineMarker);
}
if (keyCode == K_UP || keyCode == K_DOWN)
{
var up = (keyCode == K_UP);
var canChangeLines = ((up && focusCaret[0]) || ((!up) && focusCaret[0] < rep.lines.length() - 1));
var virtualLineView, virtualLineSpot, canChangeVirtualLines = false;
if (doesWrap)
{
virtualLineView = getVirtualLineView(focusCaret[0]);
virtualLineSpot = virtualLineView.getVLineAndOffsetForChar(focusCaret[1]);
canChangeVirtualLines = ((up && virtualLineSpot.vline > 0) || ((!up) && virtualLineSpot.vline < (
virtualLineView.getNumVirtualLines() - 1)));
}
var newColByVirtualLineChange;
if (moveMode == "toEnd")
{
if (up)
{
focusCaret[0] = 0;
focusCaret[1] = 0;
}
else
{
focusCaret[0] = rep.lines.length() - 1;
focusCaret[1] = lineLength(focusCaret[0]);
}
}
else if (moveMode == "byWord")
{
// move by "paragraph", a feature that Firefox lacks but IE and Safari both have
if (up)
{
if (focusCaret[1] === 0 && canChangeLines)
{
focusCaret[0]--;
focusCaret[1] = 0;
}
else focusCaret[1] = 0;
}
else
{
var lineLen = lineLength(focusCaret[0]);
if (browser.windows)
{
if (canChangeLines)
{
focusCaret[0]++;
focusCaret[1] = 0;
}
else
{
focusCaret[1] = lineLen;
}
}
else
{
if (focusCaret[1] == lineLen && canChangeLines)
{
focusCaret[0]++;
focusCaret[1] = lineLength(focusCaret[0]);
}
else
{
focusCaret[1] = lineLen;
}
}
}
savedFocusColumn = 0;
}
else if (canChangeVirtualLines)
{
var vline = virtualLineSpot.vline;
var offset = virtualLineSpot.offset;
if (up) vline--;
else vline++;
if (savedFocusColumn > offset) offset = savedFocusColumn;
else
{
savedFocusColumn = offset;
}
var newSpot = virtualLineView.getCharForVLineAndOffset(vline, offset);
focusCaret[1] = newSpot.lineChar;
}
else if (canChangeLines)
{
if (up) focusCaret[0]--;
else focusCaret[0]++;
var offset = focusCaret[1];
if (doesWrap)
{
offset = virtualLineSpot.offset;
}
if (savedFocusColumn > offset) offset = savedFocusColumn;
else
{
savedFocusColumn = offset;
}
if (doesWrap)
{
var newLineView = getVirtualLineView(focusCaret[0]);
var vline = (up ? newLineView.getNumVirtualLines() - 1 : 0);
var newSpot = newLineView.getCharForVLineAndOffset(vline, offset);
focusCaret[1] = newSpot.lineChar;
}
else
{
var lineLen = lineLength(focusCaret[0]);
if (offset > lineLen) offset = lineLen;
focusCaret[1] = offset;
}
}
else
{
if (up) focusCaret[1] = 0;
else focusCaret[1] = lineLength(focusCaret[0]);
savedFocusColumn = 0;
}
}
else if (keyCode == K_LEFT || keyCode == K_RIGHT)
{
var left = (keyCode == K_LEFT);
if (left)
{
if (moveMode == "toEnd") focusCaret[1] = 0;
else if (focusCaret[1] > 0)
{
if (moveMode == "byWord")
{
focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], false);
}
else
{
focusCaret[1]--;
}
}
else if (focusCaret[0] > 0)
{
focusCaret[0]--;
focusCaret[1] = lineLength(focusCaret[0]);
if (moveMode == "byWord")
{
focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], false);
}
}
}
else
{
var lineLen = lineLength(focusCaret[0]);
if (moveMode == "toEnd") focusCaret[1] = lineLen;
else if (focusCaret[1] < lineLen)
{
if (moveMode == "byWord")
{
focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], true);
}
else
{
focusCaret[1]++;
}
}
else if (focusCaret[0] < rep.lines.length() - 1)
{
focusCaret[0]++;
focusCaret[1] = 0;
if (moveMode == "byWord")
{
focusCaret[1] = moveByWordInLine(lineText(focusCaret[0]), focusCaret[1], true);
}
}
}
savedFocusColumn = 0;
}
}
var newSelFocusAtStart = ((focusCaret[0] < anchorCaret[0]) || (focusCaret[0] == anchorCaret[0] && focusCaret[1] < anchorCaret[1]));
var newSelStart = (newSelFocusAtStart ? focusCaret : anchorCaret);
var newSelEnd = (newSelFocusAtStart ? anchorCaret : focusCaret);
updatingSelectionNow = true;
performSelectionChange(markerfulLineAndChar(newSelStart[0], newSelStart[1]), markerfulLineAndChar(newSelEnd[0], newSelEnd[1]), newSelFocusAtStart);
updatingSelectionNow = false;
currentCallStack.userChangedSelection = true;
return true;
}
};
})());
var lineNumbersShown;
var sideDivInner;

View file

@ -107,12 +107,16 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
{
newpos = Number(newpos);
if (newpos < 0 || newpos > sliderLength) return;
if(!newpos){
newpos = 0; // stops it from displaying NaN if newpos isn't set
}
window.location.hash = "#" + newpos;
$("#ui-slider-handle").css('left', newpos * ($("#ui-slider-bar").width() - 2) / (sliderLength * 1.0));
$("a.tlink").map(function()
{
$(this).attr('href', $(this).attr('thref').replace("%revision%", newpos));
});
$("#revision_label").html(html10n.get("timeslider.version", { "version": newpos}));
if (newpos == 0)
@ -456,31 +460,6 @@ function loadBroadcastSliderJS(fireWhenAllScriptsAreLoaded)
if (clientVars)
{
if (clientVars.fullWidth)
{
$("#padpage").css('width', '100%');
$("#revision").css('position', "absolute")
$("#revision").css('right', "20px")
$("#revision").css('top', "20px")
$("#padmain").css('left', '0px');
$("#padmain").css('right', '197px');
$("#padmain").css('width', 'auto');
$("#rightbars").css('right', '7px');
$("#rightbars").css('margin-right', '0px');
$("#timeslider").css('width', 'auto');
}
if (clientVars.disableRightBar)
{
$("#rightbars").css('display', 'none');
$('#padmain').css('width', 'auto');
if (clientVars.fullWidth) $("#padmain").css('right', '7px');
else $("#padmain").css('width', '860px');
$("#revision").css('position', "absolute");
$("#revision").css('right', "20px");
$("#revision").css('top', "20px");
}
$("#timeslider").show();
var startPos = clientVars.collab_client_vars.rev;

View file

@ -294,8 +294,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
if (newRev != (oldRev + 1))
{
dmesg("bad message revision on NEW_CHANGES: " + newRev + " not " + (oldRev + 1));
setChannelState("DISCONNECTED", "badmessage_newchanges");
top.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (oldRev + 1));
// setChannelState("DISCONNECTED", "badmessage_newchanges");
return;
}
msgQueue.push(msg);
@ -304,8 +304,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
if (newRev != (rev + 1))
{
dmesg("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1));
setChannelState("DISCONNECTED", "badmessage_newchanges");
top.console.warn("bad message revision on NEW_CHANGES: " + newRev + " not " + (rev + 1));
// setChannelState("DISCONNECTED", "badmessage_newchanges");
return;
}
rev = newRev;
@ -318,8 +318,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
{
if (newRev != (msgQueue[msgQueue.length - 1].newRev + 1))
{
dmesg("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1));
setChannelState("DISCONNECTED", "badmessage_acceptcommit");
top.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (msgQueue[msgQueue.length - 1][0] + 1));
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return;
}
msgQueue.push(msg);
@ -328,8 +328,8 @@ function getCollabClient(ace2editor, serverVars, initialUserInfo, options, _pad)
if (newRev != (rev + 1))
{
dmesg("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1));
setChannelState("DISCONNECTED", "badmessage_acceptcommit");
top.console.warn("bad message revision on ACCEPT_COMMIT: " + newRev + " not " + (rev + 1));
// setChannelState("DISCONNECTED", "badmessage_acceptcommit");
return;
}
rev = newRev;

View file

@ -30,8 +30,7 @@ var Security = require('./security');
var hooks = require('./pluginfw/hooks');
var _ = require('./underscore');
var lineAttributeMarker = require('./linestylefilter').lineAttributeMarker;
var Ace2Common = require('./ace2_common');
var noop = Ace2Common.noop;
var noop = function(){};
var domline = {};

View file

@ -23,27 +23,27 @@
window.html10n = (function(window, document, undefined) {
// fix console
var console = window.console
var console = window.console;
function interceptConsole(method){
if (!console) return function() {}
if (!console) return function() {};
var original = console[method]
var original = console[method];
// do sneaky stuff
if (original.bind){
// Do this for normal browsers
return original.bind(console)
return original.bind(console);
}else{
return function() {
// Do this for IE
var message = Array.prototype.slice.apply(arguments).join(' ')
original(message)
var message = Array.prototype.slice.apply(arguments).join(' ');
original(message);
}
}
}
var consoleLog = interceptConsole('log')
, consoleWarn = interceptConsole('warn')
, consoleError = interceptConsole('warn')
, consoleError = interceptConsole('warn');
// fix Array.prototype.instanceOf in, guess what, IE! <3
@ -100,7 +100,7 @@ window.html10n = (function(window, document, undefined) {
this._events = this._events || {};
if( event in this._events === false ) return;
for(var i = 0; i < this._events[event].length; i++){
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1))
this._events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
}
}
};
@ -122,50 +122,50 @@ window.html10n = (function(window, document, undefined) {
* and caching all necessary resources
*/
function Loader(resources) {
this.resources = resources
this.cache = {} // file => contents
this.langs = {} // lang => strings
this.resources = resources;
this.cache = {}; // file => contents
this.langs = {}; // lang => strings
}
Loader.prototype.load = function(lang, cb) {
if(this.langs[lang]) return cb()
if(this.langs[lang]) return cb();
if (this.resources.length > 0) {
var reqs = 0;
for (var i=0, n=this.resources.length; i < n; i++) {
this.fetch(this.resources[i], lang, function(e) {
reqs++;
if(e) return setTimeout(function(){ throw e }, 0)
if(e) return setTimeout(function(){ throw e }, 0);
if (reqs < n) return;// Call back once all reqs are completed
cb && cb()
cb && cb();
})
}
}
}
Loader.prototype.fetch = function(href, lang, cb) {
var that = this
var that = this;
if (this.cache[href]) {
this.parse(lang, href, this.cache[href], cb)
return;
}
var xhr = new XMLHttpRequest()
xhr.open('GET', href, /*async: */true)
var xhr = new XMLHttpRequest();
xhr.open('GET', href, /*async: */true);
if (xhr.overrideMimeType) {
xhr.overrideMimeType('application/json; charset=utf-8');
}
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200 || xhr.status === 0) {
var data = JSON.parse(xhr.responseText)
that.cache[href] = data
var data = JSON.parse(xhr.responseText);
that.cache[href] = data;
// Pass on the contents for parsing
that.parse(lang, href, data, cb)
that.parse(lang, href, data, cb);
} else {
cb(new Error('Failed to load '+href))
cb(new Error('Failed to load '+href));
}
}
};
@ -174,39 +174,39 @@ window.html10n = (function(window, document, undefined) {
Loader.prototype.parse = function(lang, currHref, data, cb) {
if ('object' != typeof data) {
cb(new Error('A file couldn\'t be parsed as json.'))
return
cb(new Error('A file couldn\'t be parsed as json.'));
return;
}
if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-'))
if (!data[lang]) lang = lang.substr(0, lang.indexOf('-') == -1? lang.length : lang.indexOf('-'));
if (!data[lang]) {
cb(new Error('Couldn\'t find translations for '+lang))
return
cb(new Error('Couldn\'t find translations for '+lang));
return;
}
if ('string' == typeof data[lang]) {
// Import rule
// absolute path
var importUrl = data[lang]
var importUrl = data[lang];
// relative path
if(data[lang].indexOf("http") != 0 && data[lang].indexOf("/") != 0) {
importUrl = currHref+"/../"+data[lang]
importUrl = currHref+"/../"+data[lang];
}
this.fetch(importUrl, lang, cb)
return
this.fetch(importUrl, lang, cb);
return;
}
if ('object' != typeof data[lang]) {
cb(new Error('Translations should be specified as JSON objects!'))
return
cb(new Error('Translations should be specified as JSON objects!'));
return;
}
this.langs[lang] = data[lang]
this.langs[lang] = data[lang];
// TODO: Also store accompanying langs
cb()
cb();
}
@ -216,11 +216,11 @@ window.html10n = (function(window, document, undefined) {
var html10n =
{ language : null
}
MicroEvent.mixin(html10n)
MicroEvent.mixin(html10n);
html10n.macros = {}
html10n.macros = {};
html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"]
html10n.rtl = ["ar","dv","fa","ha","he","ks","ku","ps","ur","yi"];
/**
* Get rules for plural forms (shared with JetPack), see:
@ -664,14 +664,14 @@ window.html10n = (function(window, document, undefined) {
* @param langs An array of lang codes defining fallbacks
*/
html10n.localize = function(langs) {
var that = this
var that = this;
// if only one string => create an array
if ('string' == typeof langs) langs = [langs]
if ('string' == typeof langs) langs = [langs];
this.build(langs, function(er, translations) {
html10n.translations = translations
html10n.translateElement(translations)
that.trigger('localized')
html10n.translations = translations;
html10n.translateElement(translations);
that.trigger('localized');
})
}
@ -682,78 +682,78 @@ window.html10n = (function(window, document, undefined) {
* @param element A DOM element, if omitted, the document element will be used
*/
html10n.translateElement = function(translations, element) {
element = element || document.documentElement
element = element || document.documentElement;
var children = element? getTranslatableChildren(element) : document.childNodes;
for (var i=0, n=children.length; i < n; i++) {
this.translateNode(translations, children[i])
this.translateNode(translations, children[i]);
}
// translate element itself if necessary
this.translateNode(translations, element)
this.translateNode(translations, element);
}
function asyncForEach(list, iterator, cb) {
var i = 0
, n = list.length
, n = list.length;
iterator(list[i], i, function each(err) {
if(err) consoleLog(err)
i++
if(err) consoleLog(err);
i++;
if (i < n) return iterator(list[i],i, each);
cb()
cb();
})
}
function getTranslatableChildren(element) {
if(!document.querySelectorAll) {
if (!element) return []
if (!element) return [];
var nodes = element.getElementsByTagName('*')
, l10nElements = []
, l10nElements = [];
for (var i=0, n=nodes.length; i < n; i++) {
if (nodes[i].getAttribute('data-l10n-id'))
l10nElements.push(nodes[i]);
}
return l10nElements
return l10nElements;
}
return element.querySelectorAll('*[data-l10n-id]')
return element.querySelectorAll('*[data-l10n-id]');
}
html10n.get = function(id, args) {
var translations = html10n.translations
if(!translations) return consoleWarn('No translations available (yet)')
if(!translations[id]) return consoleWarn('Could not find string '+id)
var translations = html10n.translations;
if(!translations) return consoleWarn('No translations available (yet)');
if(!translations[id]) return consoleWarn('Could not find string '+id);
// apply args
var str = substArguments(translations[id], args)
var str = substArguments(translations[id], args);
// apply macros
return substMacros(id, str, args)
return substMacros(id, str, args);
// replace {{arguments}} with their values or the
// associated translation string (based on its key)
function substArguments(str, args) {
var reArgs = /\{\{\s*([a-zA-Z\.]+)\s*\}\}/
, match
, match;
while (match = reArgs.exec(str)) {
if (!match || match.length < 2)
return str // argument key not found
return str; // argument key not found
var arg = match[1]
, sub = ''
, sub = '';
if (arg in args) {
sub = args[arg]
sub = args[arg];
} else if (arg in translations) {
sub = translations[arg]
sub = translations[arg];
} else {
consoleWarn('Could not find argument {{' + arg + '}}')
return str
consoleWarn('Could not find argument {{' + arg + '}}');
return str;
}
str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length)
str = str.substring(0, match.index) + sub + str.substr(match.index + match[0].length);
}
return str
return str;
}
// replace {[macros]} with their values
@ -766,21 +766,21 @@ window.html10n = (function(window, document, undefined) {
// a macro has been found
// Note: at the moment, only one parameter is supported
var macroName = reMatch[1]
, paramName = reMatch[2]
, paramName = reMatch[2];
if (!(macroName in gMacros)) return str
if (!(macroName in gMacros)) return str;
var param
var param;
if (args && paramName in args) {
param = args[paramName]
param = args[paramName];
} else if (paramName in translations) {
param = translations[paramName]
param = translations[paramName];
}
// there's no macro parser yet: it has to be defined in gMacros
var macro = html10n.macros[macroName]
str = macro(translations, key, str, param)
return str
var macro = html10n.macros[macroName];
str = macro(translations, key, str, param);
return str;
}
}
@ -788,26 +788,26 @@ window.html10n = (function(window, document, undefined) {
* Applies translations to a DOM node (recursive)
*/
html10n.translateNode = function(translations, node) {
var str = {}
var str = {};
// get id
str.id = node.getAttribute('data-l10n-id')
if (!str.id) return
str.id = node.getAttribute('data-l10n-id');
if (!str.id) return;
if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id)
if(!translations[str.id]) return consoleWarn('Couldn\'t find translation key '+str.id);
// get args
if(window.JSON) {
str.args = JSON.parse(node.getAttribute('data-l10n-args'))
str.args = JSON.parse(node.getAttribute('data-l10n-args'));
}else{
try{
str.args = eval(node.getAttribute('data-l10n-args'))
str.args = eval(node.getAttribute('data-l10n-args'));
}catch(e) {
consoleWarn('Couldn\'t parse args for '+str.id)
consoleWarn('Couldn\'t parse args for '+str.id);
}
}
str.str = html10n.get(str.id, str.args)
str.str = html10n.get(str.id, str.args);
// get attribute name to apply str to
var prop
@ -817,31 +817,31 @@ window.html10n = (function(window, document, undefined) {
, "innerHTML": 1
, "alt": 1
, "textContent": 1
}
};
if (index > 0 && str.id.substr(index + 1) in attrList) { // an attribute has been specified
prop = str.id.substr(index + 1)
prop = str.id.substr(index + 1);
} else { // no attribute: assuming text content by default
prop = document.body.textContent ? 'textContent' : 'innerText'
prop = document.body.textContent ? 'textContent' : 'innerText';
}
// Apply translation
if (node.children.length === 0 || prop != 'textContent') {
node[prop] = str.str
node[prop] = str.str;
} else {
var children = node.childNodes,
found = false
found = false;
for (var i=0, n=children.length; i < n; i++) {
if (children[i].nodeType === 3 && /\S/.test(children[i].textContent)) {
if (!found) {
children[i].nodeValue = str.str
found = true
children[i].nodeValue = str.str;
found = true;
} else {
children[i].nodeValue = ''
children[i].nodeValue = '';
}
}
}
if (!found) {
consoleWarn('Unexpected error: could not translate element content for key '+str.id, node)
consoleWarn('Unexpected error: could not translate element content for key '+str.id, node);
}
}
}
@ -852,32 +852,32 @@ window.html10n = (function(window, document, undefined) {
*/
html10n.build = function(langs, cb) {
var that = this
, build = {}
, build = {};
asyncForEach(langs, function (lang, i, next) {
if(!lang) return next();
that.loader.load(lang, next)
that.loader.load(lang, next);
}, function() {
var lang
langs.reverse()
var lang;
langs.reverse();
// loop through priority array...
for (var i=0, n=langs.length; i < n; i++) {
lang = langs[i]
lang = langs[i];
if(!lang || !(lang in that.loader.langs)) continue;
// ... and apply all strings of the current lang in the list
// to our build object
for (var string in that.loader.langs[lang]) {
build[string] = that.loader.langs[lang][string]
build[string] = that.loader.langs[lang][string];
}
// the last applied lang will be exposed as the
// lang the page was translated to
that.language = lang
that.language = lang;
}
cb(null, build)
cb(null, build);
})
}
@ -893,8 +893,8 @@ window.html10n = (function(window, document, undefined) {
* Returns the direction of the language returned be html10n#getLanguage
*/
html10n.getDirection = function() {
var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-'))
return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl'
var langCode = this.language.indexOf('-') == -1? this.language : this.language.substr(0, this.language.indexOf('-'));
return html10n.rtl.indexOf(langCode) == -1? 'ltr' : 'rtl';
}
/**
@ -903,28 +903,28 @@ window.html10n = (function(window, document, undefined) {
html10n.index = function () {
// Find all <link>s
var links = document.getElementsByTagName('link')
, resources = []
, resources = [];
for (var i=0, n=links.length; i < n; i++) {
if (links[i].type != 'application/l10n+json')
continue;
resources.push(links[i].href)
resources.push(links[i].href);
}
this.loader = new Loader(resources)
this.trigger('indexed')
this.loader = new Loader(resources);
this.trigger('indexed');
}
if (document.addEventListener) // modern browsers and IE9+
document.addEventListener('DOMContentLoaded', function() {
html10n.index()
}, false)
html10n.index();
}, false);
else if (window.attachEvent)
window.attachEvent('onload', function() {
html10n.index()
}, false)
html10n.index();
}, false);
// gettext-like shortcut
if (window._ === undefined)
window._ = html10n.get;
return html10n
})(window, document)
return html10n;
})(window, document);

9961
src/static/js/jquery.js vendored

File diff suppressed because it is too large Load diff

50
src/static/js/jquery_browser.js vendored Normal file
View file

@ -0,0 +1,50 @@
/*
Copied from jQuery 1.8, the last jquery version with browser recognition support
*/
(function(){
// Use of jQuery.browser is frowned upon.
// More details: http://api.jquery.com/jQuery.browser
// jQuery.uaMatch maintained for back-compat
var uaMatch = function( ua ) {
ua = ua.toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
/(msie) ([\w.]+)/.exec( ua ) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
[];
return {
browser: match[ 1 ] || "",
version: match[ 2 ] || "0"
};
};
var userAgent = navigator.userAgent;
var matched = uaMatch(userAgent);
var browser = {};
if ( matched.browser ) {
browser[ matched.browser ] = true;
browser.version = matched.version;
}
// Chrome is Webkit, but Webkit is also Safari.
if ( browser.chrome ) {
browser.webkit = true;
} else if ( browser.webkit ) {
browser.safari = true;
}
//custom extensions, the original jquery didn't have these
browser.windows = /windows/i.test(userAgent);
browser.mobile = /mobile/i.test(userAgent) || /android/i.test(userAgent);
if(typeof exports !== 'undefined'){
exports.browser = browser;
} else{
$.browser = browser;
}
})();

View file

@ -1,6 +1,6 @@
(function(document) {
// Set language for l10n
var language = document.cookie.match(/language=((\w{2,3})(-w+)?)/);
var language = document.cookie.match(/language=((\w{2,3})(-\w+)?)/);
if(language) language = language[1];
html10n.bind('indexed', function() {

View file

@ -35,7 +35,6 @@ var chat = require('./chat').chat;
var getCollabClient = require('./collab_client').getCollabClient;
var padconnectionstatus = require('./pad_connectionstatus').padconnectionstatus;
var padcookie = require('./pad_cookie').padcookie;
var paddocbar = require('./pad_docbar').paddocbar;
var padeditbar = require('./pad_editbar').padeditbar;
var padeditor = require('./pad_editor').padeditor;
var padimpexp = require('./pad_impexp').padimpexp;
@ -391,10 +390,6 @@ var pad = {
{
return clientVars.clientIp;
},
getIsProPad: function()
{
return clientVars.isProPad;
},
getColorPalette: function()
{
return clientVars.colorPalette;
@ -467,11 +462,6 @@ var pad = {
}
// order of inits is important here:
padcookie.init(clientVars.cookiePrefsToSet, this);
$("#widthprefcheck").click(pad.toggleWidthPref);
// $("#sidebarcheck").click(pad.togglewSidebar);
pad.myUserInfo = {
userId: clientVars.userId,
name: clientVars.userName,
@ -488,20 +478,12 @@ var pad = {
$("#specialkeyarea").html("mode: " + String(clientVars.specialKeyTranslation).toUpperCase());
}
}
paddocbar.init(
{
isTitleEditable: pad.getIsProPad(),
initialTitle: clientVars.initialTitle,
initialPassword: clientVars.initialPassword,
guestPolicy: pad.padOptions.guestPolicy
}, this);
padimpexp.init(this);
padsavedrevs.init(this);
padeditor.init(postAceInit, pad.padOptions.view || {}, this);
paduserlist.init(pad.myUserInfo, this);
// padchat.init(clientVars.chatHistory, pad.myUserInfo);
padconnectionstatus.init();
padmodals.init(this);
@ -553,31 +535,11 @@ var pad = {
{
pad.myUserInfo.name = newName;
pad.collabClient.updateUserInfo(pad.myUserInfo);
//padchat.handleUserJoinOrUpdate(pad.myUserInfo);
},
notifyChangeColor: function(newColorId)
{
pad.myUserInfo.colorId = newColorId;
pad.collabClient.updateUserInfo(pad.myUserInfo);
//padchat.handleUserJoinOrUpdate(pad.myUserInfo);
},
notifyChangeTitle: function(newTitle)
{
pad.collabClient.sendClientMessage(
{
type: 'padtitle',
title: newTitle,
changedBy: pad.myUserInfo.name || "unnamed"
});
},
notifyChangePassword: function(newPass)
{
pad.collabClient.sendClientMessage(
{
type: 'padpassword',
password: newPass,
changedBy: pad.myUserInfo.name || "unnamed"
});
},
changePadOption: function(key, value)
{
@ -619,7 +581,6 @@ var pad = {
{
// order important here
pad.padOptions.guestPolicy = opts.guestPolicy;
paddocbar.setGuestPolicy(opts.guestPolicy);
}
},
getPadOptions: function()
@ -629,7 +590,7 @@ var pad = {
},
isPadPublic: function()
{
return (!pad.getIsProPad()) || (pad.getPadOptions().guestPolicy == 'allow');
return pad.getPadOptions().guestPolicy == 'allow';
},
suggestUserName: function(userId, name)
{
@ -643,17 +604,14 @@ var pad = {
handleUserJoin: function(userInfo)
{
paduserlist.userJoinOrUpdate(userInfo);
//padchat.handleUserJoinOrUpdate(userInfo);
},
handleUserUpdate: function(userInfo)
{
paduserlist.userJoinOrUpdate(userInfo);
//padchat.handleUserJoinOrUpdate(userInfo);
},
handleUserLeave: function(userInfo)
{
paduserlist.userLeave(userInfo);
//padchat.handleUserLeave(userInfo);
},
handleClientMessage: function(msg)
{
@ -665,18 +623,6 @@ var pad = {
paduserlist.setMyUserInfo(pad.myUserInfo);
}
}
else if (msg.type == 'chat')
{
//padchat.receiveChat(msg);
}
else if (msg.type == 'padtitle')
{
paddocbar.changeTitle(msg.title);
}
else if (msg.type == 'padpassword')
{
paddocbar.changePassword(msg.password);
}
else if (msg.type == 'newRevisionList')
{
padsavedrevs.newRevisionList(msg.revisionList);
@ -769,7 +715,6 @@ var pad = {
}
padeditor.disable();
padeditbar.disable();
paddocbar.disable();
padimpexp.disable();
padconnectionstatus.disconnected(message);
@ -796,28 +741,10 @@ var pad = {
}, 1000);
}
// pad.determineSidebarVisibility(isConnected && !isInitialConnect);
pad.determineChatVisibility(isConnected && !isInitialConnect);
pad.determineAuthorshipColorsVisibility();
},
/* determineSidebarVisibility: function(asNowConnectedFeedback)
{
if (pad.isFullyConnected())
{
var setSidebarVisibility = padutils.getCancellableAction("set-sidebar-visibility", function()
{
// $("body").toggleClass('hidesidebar', !! padcookie.getPref('hideSidebar'));
});
window.setTimeout(setSidebarVisibility, asNowConnectedFeedback ? 3000 : 0);
}
else
{
padutils.cancelActions("set-sidebar-visibility");
$("body").removeClass('hidesidebar');
}
},
*/
determineChatVisibility: function(asNowConnectedFeedback){
var chatVisCookie = padcookie.getPref('chatAlwaysVisible');
if(chatVisCookie){ // if the cookie is set for chat always visible
@ -879,37 +806,6 @@ var pad = {
$('form#reconnectform input.missedChanges').val(JSON.stringify(pad.collabClient.getMissedChanges()));
$('form#reconnectform').submit();
},
toggleWidthPref: function()
{
var newValue = !padcookie.getPref('fullWidth');
padcookie.setPref('fullWidth', newValue);
$("#widthprefcheck").toggleClass('widthprefchecked', !! newValue).toggleClass('widthprefunchecked', !newValue);
pad.handleWidthChange();
},
/*
toggleSidebar: function()
{
var newValue = !padcookie.getPref('hideSidebar');
padcookie.setPref('hideSidebar', newValue);
$("#sidebarcheck").toggleClass('sidebarchecked', !newValue).toggleClass('sidebarunchecked', !! newValue);
pad.determineSidebarVisibility();
},
*/
handleWidthChange: function()
{
var isFullWidth = padcookie.getPref('fullWidth');
if (isFullWidth)
{
$("body").addClass('fullwidth').removeClass('limwidth').removeClass('squish1width').removeClass('squish2width');
}
else
{
$("body").addClass('limwidth').removeClass('fullwidth');
var pageWidth = $(window).width();
$("body").toggleClass('squish1width', (pageWidth < 912 && pageWidth > 812)).toggleClass('squish2width', (pageWidth <= 812));
}
},
// this is called from code put into a frame from the server:
handleImportExportFrameCall: function(callName, varargs)
{

View file

@ -43,9 +43,8 @@ var padconnectionstatus = (function()
status = {
what: 'connected'
};
padmodals.showModal('connected');
padmodals.hideOverlay(500);
padmodals.hideOverlay();
},
reconnecting: function()
{
@ -54,7 +53,7 @@ var padconnectionstatus = (function()
};
padmodals.showModal('reconnecting');
padmodals.showOverlay(500);
padmodals.showOverlay();
},
disconnected: function(msg)
{
@ -73,10 +72,11 @@ var padconnectionstatus = (function()
}
padmodals.showModal(k);
padmodals.showOverlay(500);
padmodals.showOverlay();
},
isFullyConnected: function()
{
padmodals.hideOverlay();
return status.what == 'connected';
},
getStatus: function()

View file

@ -73,7 +73,7 @@ var padcookie = (function()
}
setRawCookie(stringifyCookie(cookieData));
if (pad.getIsProPad() && (!getRawCookie()) && (!alreadyWarnedAboutNoCookies))
if ((!getRawCookie()) && (!alreadyWarnedAboutNoCookies))
{
alert("Warning: it appears that your browser does not have cookies enabled." + " EtherPad uses cookies to keep track of unique users for the purpose" + " of putting a quota on the number of active users. Using EtherPad without " + " cookies may fill up your server's user quota faster than expected.");
alreadyWarnedAboutNoCookies = true;

View file

@ -1,466 +0,0 @@
/**
* 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
*/
/**
* Copyright 2009 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS-IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var padutils = require('./pad_utils').padutils;
var paddocbar = (function()
{
var isTitleEditable = false;
var isEditingTitle = false;
var isEditingPassword = false;
var enabled = false;
function getPanelOpenCloseAnimator(panelName, panelHeight)
{
var wrapper = $("#" + panelName + "-wrapper");
var openingClass = "docbar" + panelName + "-opening";
var openClass = "docbar" + panelName + "-open";
var closingClass = "docbar" + panelName + "-closing";
function setPanelState(action)
{
$("#docbar").removeClass(openingClass).removeClass(openClass).
removeClass(closingClass);
if (action != "closed")
{
$("#docbar").addClass("docbar" + panelName + "-" + action);
}
}
function openCloseAnimate(state)
{
function pow(x)
{
x = 1 - x;
x *= x * x;
return 1 - x;
}
if (state == -1)
{
// startng to open
setPanelState("opening");
wrapper.css('height', '0');
}
else if (state < 0)
{
// opening
var height = Math.round(pow(state + 1) * (panelHeight - 1)) + 'px';
wrapper.css('height', height);
}
else if (state == 0)
{
// open
setPanelState("open");
wrapper.css('height', panelHeight - 1);
}
else if (state < 1)
{
// closing
setPanelState("closing");
var height = Math.round((1 - pow(state)) * (panelHeight - 1)) + 'px';
wrapper.css('height', height);
}
else if (state == 1)
{
// closed
setPanelState("closed");
wrapper.css('height', '0');
}
}
return padutils.makeShowHideAnimator(openCloseAnimate, false, 25, 500);
}
var currentPanel = null;
function setCurrentPanel(newCurrentPanel)
{
if (currentPanel != newCurrentPanel)
{
currentPanel = newCurrentPanel;
padutils.cancelActions("hide-docbar-panel");
}
}
var panels;
function changePassword(newPass)
{
if ((newPass || null) != (self.password || null))
{
self.password = (newPass || null);
pad.notifyChangePassword(newPass);
}
self.renderPassword();
}
var pad = undefined;
var self = {
title: null,
password: null,
init: function(opts, _pad)
{
pad = _pad;
panels = {
impexp: {
animator: getPanelOpenCloseAnimator("impexp", 160)
},
savedrevs: {
animator: getPanelOpenCloseAnimator("savedrevs", 79)
},
options: {
animator: getPanelOpenCloseAnimator("options", 114)
},
security: {
animator: getPanelOpenCloseAnimator("security", 130)
}
};
isTitleEditable = opts.isTitleEditable;
self.title = opts.initialTitle;
self.password = opts.initialPassword;
$("#docbarimpexp").click(function()
{
self.togglePanel("impexp");
});
$("#docbarsavedrevs").click(function()
{
self.togglePanel("savedrevs");
});
$("#docbaroptions").click(function()
{
self.togglePanel("options");
});
$("#docbarsecurity").click(function()
{
self.togglePanel("security");
});
$("#docbarrenamelink").click(self.editTitle);
$("#padtitlesave").click(function()
{
self.closeTitleEdit(true);
});
$("#padtitlecancel").click(function()
{
self.closeTitleEdit(false);
});
padutils.bindEnterAndEscape($("#padtitleedit"), function()
{
$("#padtitlesave").trigger('click');
}, function()
{
$("#padtitlecancel").trigger('click');
});
$("#options-close").click(function()
{
self.setShownPanel(null);
});
$("#security-close").click(function()
{
self.setShownPanel(null);
});
if (pad.getIsProPad())
{
self.initPassword();
}
enabled = true;
self.render();
// public/private
$("#security-access input").bind("change click", function(evt)
{
pad.changePadOption('guestPolicy', $("#security-access input[name='padaccess']:checked").val());
});
self.setGuestPolicy(opts.guestPolicy);
},
setGuestPolicy: function(newPolicy)
{
$("#security-access input[value='" + newPolicy + "']").attr("checked", "checked");
self.render();
},
initPassword: function()
{
self.renderPassword();
$("#password-clearlink").click(function()
{
changePassword(null);
});
$("#password-setlink, #password-display").click(function()
{
self.enterPassword();
});
$("#password-cancellink").click(function()
{
self.exitPassword(false);
});
$("#password-savelink").click(function()
{
self.exitPassword(true);
});
padutils.bindEnterAndEscape($("#security-passwordedit"), function()
{
self.exitPassword(true);
}, function()
{
self.exitPassword(false);
});
},
enterPassword: function()
{
isEditingPassword = true;
$("#security-passwordedit").val(self.password || '');
self.renderPassword();
$("#security-passwordedit").focus().select();
},
exitPassword: function(accept)
{
isEditingPassword = false;
if (accept)
{
changePassword($("#security-passwordedit").val());
}
else
{
self.renderPassword();
}
},
renderPassword: function()
{
if (isEditingPassword)
{
$("#password-nonedit").hide();
$("#password-inedit").show();
}
else
{
$("#password-nonedit").toggleClass('nopassword', !self.password);
$("#password-setlink").html(self.password ? "Change..." : "Set...");
if (self.password)
{
$("#password-display").html(self.password.replace(/./g, '&#8226;'));
}
else
{
$("#password-display").html("None");
}
$("#password-inedit").hide();
$("#password-nonedit").show();
}
},
togglePanel: function(panelName)
{
if (panelName in panels)
{
if (currentPanel == panelName)
{
self.setShownPanel(null);
}
else
{
self.setShownPanel(panelName);
}
}
},
setShownPanel: function(panelName)
{
function animateHidePanel(panelName, next)
{
var delay = 0;
if (panelName == 'options' && isEditingPassword)
{
// give user feedback that the password they've
// typed in won't actually take effect
self.exitPassword(false);
delay = 500;
}
window.setTimeout(function()
{
panels[panelName].animator.hide();
if (next)
{
next();
}
}, delay);
}
if (!panelName)
{
if (currentPanel)
{
animateHidePanel(currentPanel);
setCurrentPanel(null);
}
}
else if (panelName in panels)
{
if (currentPanel != panelName)
{
if (currentPanel)
{
animateHidePanel(currentPanel, function()
{
panels[panelName].animator.show();
setCurrentPanel(panelName);
});
}
else
{
panels[panelName].animator.show();
setCurrentPanel(panelName);
}
}
}
},
isPanelShown: function(panelName)
{
if (!panelName)
{
return !currentPanel;
}
else
{
return (panelName == currentPanel);
}
},
changeTitle: function(newTitle)
{
self.title = newTitle;
self.render();
},
editTitle: function()
{
if (!enabled)
{
return;
}
$("#padtitleedit").val(self.title);
isEditingTitle = true;
self.render();
$("#padtitleedit").focus().select();
},
closeTitleEdit: function(accept)
{
if (!enabled)
{
return;
}
if (accept)
{
var newTitle = $("#padtitleedit").val();
if (newTitle)
{
newTitle = newTitle.substring(0, 80);
self.title = newTitle;
pad.notifyChangeTitle(newTitle);
}
}
isEditingTitle = false;
self.render();
},
changePassword: function(newPass)
{
if (newPass)
{
self.password = newPass;
}
else
{
self.password = null;
}
self.renderPassword();
},
render: function()
{
if (isEditingTitle)
{
$("#docbarpadtitle").hide();
$("#docbarrenamelink").hide();
$("#padtitleedit").show();
$("#padtitlebuttons").show();
if (!enabled)
{
$("#padtitleedit").attr('disabled', 'disabled');
}
else
{
$("#padtitleedit").removeAttr('disabled');
}
}
else
{
$("#padtitleedit").hide();
$("#padtitlebuttons").hide();
var titleSpan = $("#docbarpadtitle span");
titleSpan.html(padutils.escapeHtml(self.title));
$("#docbarpadtitle").attr('title', (pad.isPadPublic() ? "Public Pad: " : "") + self.title);
$("#docbarpadtitle").show();
if (isTitleEditable)
{
var titleRight = $("#docbarpadtitle").position().left + $("#docbarpadtitle span").position().left + Math.min($("#docbarpadtitle").width(), $("#docbarpadtitle span").width());
$("#docbarrenamelink").css('left', titleRight + 10).show();
}
if (pad.isPadPublic())
{
$("#docbar").addClass("docbar-public");
}
else
{
$("#docbar").removeClass("docbar-public");
}
}
},
disable: function()
{
enabled = false;
self.render();
},
handleResizePage: function()
{
// Side-step circular reference. This should be injected.
var padsavedrevs = require('./pad_savedrevs');
padsavedrevs.handleResizePage();
},
hideLaterIfNoOtherInteraction: function()
{
return padutils.getCancellableAction('hide-docbar-panel', function()
{
self.setShownPanel(null);
});
}
};
return self;
}());
exports.paddocbar = paddocbar;

View file

@ -156,10 +156,7 @@ var padeditbar = (function()
else if (cmd == 'insertorderedlist') ace.ace_doInsertOrderedList();
else if (cmd == 'indent')
{
if (!ace.ace_doIndentOutdent(false))
{
ace.ace_doInsertUnorderedList();
}
ace.ace_doIndentOutdent(false);
}
else if (cmd == 'outdent')
{

View file

@ -20,14 +20,11 @@
* limitations under the License.
*/
var paddocbar = require('./pad_docbar').paddocbar;
var padimpexp = (function()
{
///// import
var currentImportTimer = null;
var hidePanelCall = null;
function addImportFrames()
{
@ -72,7 +69,6 @@ var padimpexp = (function()
var ret = window.confirm(html10n.get("pad.impexp.confirmimport"));
if (ret)
{
hidePanelCall = paddocbar.hideLaterIfNoOtherInteraction();
currentImportTimer = window.setTimeout(function()
{
if (!currentImportTimer)
@ -255,11 +251,6 @@ var padimpexp = (function()
$("#exportopena").attr("href", pad_root_path + "/export/odt");
}
$("#impexp-close").click(function()
{
paddocbar.setShownPanel(null);
});
addImportFrames();
$("#importfileinput").change(fileInputUpdated);
$('#importform').submit(fileInputSubmit);

View file

@ -107,14 +107,9 @@ var paduserlist = (function()
function getUserRowHtml(height, data)
{
var nameHtml;
var isGuest = (data.id.charAt(0) != 'p');
if (data.name)
{
nameHtml = padutils.escapeHtml(data.name);
if (isGuest && pad.getIsProPad())
{
nameHtml += ' ('+_(pad.userlist.guest)+')';
}
}
else
{

View file

@ -520,11 +520,11 @@ function setupGlobalExceptionHandler() {
$("#editorloadingbox").css("padding", "10px");
$("#editorloadingbox").css("padding-top", "45px");
$("#editorloadingbox").html("<div style='text-align:left;color:red;font-size:16px;'><b>An error occured</b><br>The error was reported with the following id: '" + errorId + "'<br><br><span style='color:black;font-weight:bold;font-size:16px'>Please send this error message to us: </span><div style='color:black;font-size:14px'>'"
+ "ErrorId: " + errorId + "<br>UserAgent: " + navigator.userAgent + "<br>" + msg + " in " + url + " at line " + linenumber + "'</div></div>");
+ "ErrorId: " + errorId + "<br>URL: " + window.location.href + "<br>UserAgent: " + navigator.userAgent + "<br>" + msg + " in " + url + " at line " + linenumber + "'</div></div>");
}
//send javascript errors to the server
var errObj = {errorInfo: JSON.stringify({errorId: errorId, msg: msg, url: url, linenumber: linenumber, userAgent: navigator.userAgent})};
var errObj = {errorInfo: JSON.stringify({errorId: errorId, msg: msg, url: window.location.href, linenumber: linenumber, userAgent: navigator.userAgent})};
var loc = document.location;
var url = loc.protocol + "//" + loc.hostname + ":" + loc.port + "/" + loc.pathname.substr(1, loc.pathname.indexOf("/p/")) + "jserror";

View file

@ -70,11 +70,13 @@ exports.flatten = function (lst) {
exports.callAll = function (hook_name, args) {
if (!args) args = {};
if (exports.plugins){
if (exports.plugins.hooks[hook_name] === undefined) return [];
return _.flatten(_.map(exports.plugins.hooks[hook_name], function (hook) {
return hookCallWrapper(hook, hook_name, args);
}), true);
}
}
exports.aCallAll = function (hook_name, args, cb) {
if (!args) args = {};

View file

@ -94,11 +94,12 @@ exports.search = function(query, cache, cb) {
if (er) return cb(er);
var res = {};
var i = 0;
var pattern = query.pattern.toLowerCase();
for (key in data) { // for every plugin in the data from npm
if ( key.indexOf(plugins.prefix) == 0
&& key.indexOf(query.pattern) != -1
&& key.indexOf(pattern) != -1
|| key.indexOf(plugins.prefix) == 0
&& data[key].description.indexOf(query.pattern) != -1
&& data[key].description.indexOf(pattern) != -1
) { // If the name contains ep_ and the search string is in the name or description
i++;
if (i > query.offset

View file

@ -2,4 +2,9 @@
// Proviedes a require'able version of jQuery without leaking $ and jQuery;
require('./jquery');
exports.jQuery = exports.$ = $.noConflict(true);
var jq = window.$.noConflict(true);
//added the old browser recognition
jq.browser = require('./jquery_browser').browser;
exports.jQuery = exports.$ = jq;

View file

@ -29,8 +29,9 @@ var createCookie = require('./pad_utils').createCookie;
var readCookie = require('./pad_utils').readCookie;
var randomString = require('./pad_utils').randomString;
var _ = require('./underscore');
var hooks = require('./pluginfw/hooks');
var socket, token, padId, export_links;
var token, padId, export_links;
function init() {
$(document).ready(function ()
@ -106,6 +107,9 @@ function init() {
window.location.reload();
});
exports.socket = socket; // make the socket available
hooks.aCallAll("postTimesliderInit");
});
}

View file

@ -1,7 +1,8 @@
<!doctype html>
<html>
<head>
<title>Admin Dashboard - Etherpad lite</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<title>Admin Dashboard - Etherpad</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="../static/css/admin.css">
<script src="../static/js/jquery.js"></script>
<script src="../socket.io/socket.io.js"></script>
@ -9,13 +10,15 @@
<body>
<div id="wrapper">
<div class="menu">
<h1>Etherpad lite</h1>
<li><a href="admin/plugins">Plugin manager</a> </li>
<li><a href="admin/settings">Settings</a> </li>
<li><a href="admin/plugins/info">Troubleshooting information</a> </li>
<h1>Etherpad</h1>
<ul>
<% e.begin_block("adminMenu"); %>
<li><a href="plugins">Plugin manager</a> </li>
<li><a href="settings">Settings</a> </li>
<li><a href="plugins/info">Troubleshooting information</a> </li>
<% e.end_block(); %>
</ul>
</div>
</div>
<div id="topborder"></div>
</body>
</html>

View file

@ -1,20 +1,24 @@
<%
var plugins = require("ep_etherpad-lite/static/js/pluginfw/plugins");
%>
<!doctype html>
<html>
<head>
<title>Plugin information - Etherpad lite</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<title>Plugin information - Etherpad</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="../../static/css/admin.css">
</head>
<body>
<div id="wrapper">
<div class="menu">
<h1>Etherpad lite</h1>
<h1>Etherpad</h1>
<ul>
<% e.begin_block("adminMenu"); %>
<li><a href="../plugins">Plugin manager</a> </li>
<li><a href="../settings">Settings</a> </li>
<li><a href="../plugins/info">Troubleshooting information</a> </li>
<% e.end_block(); %>
</ul>
</div>
<div class="innerwrapper">
@ -34,6 +38,5 @@
</div>
</div>
<div id="topborder"></div>
</body>
</html>

View file

@ -1,7 +1,8 @@
<!doctype html>
<html>
<head>
<title>Plugin manager - Etherpad lite</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<title>Plugin manager - Etherpad</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="../static/css/admin.css">
<script src="../static/js/jquery.js"></script>
<script src="../socket.io/socket.io.js"></script>
@ -19,12 +20,15 @@
<% } %>
<div class="menu">
<h1>Etherpad lite</h1>
<h1>Etherpad</h1>
<ul>
<% e.begin_block("adminMenu"); %>
<li><a href="plugins">Plugin manager</a> </li>
<li><a href="settings">Settings</a> </li>
<li><a href="plugins/info">Troubleshooting information</a> </li>
<div id="progress"><img src="../static/img/loading.gif" alt=""/>&nbsp;&nbsp;<span class="message"></span></div>
<% e.end_block(); %>
</ul>
<div id="progress"><img src="../static/img/loading.gif">&nbsp;&nbsp;<span class="message"></span></div>
</div>
<div class="innerwrapper">
@ -40,9 +44,9 @@
</thead>
<tbody class="template">
<tr id="installed-plugin-template">
<td class="name"></td>
<td class="description"></td>
<td class="version"></td>
<td class="name" data-label="Name"></td>
<td class="description" data-label="Description"></td>
<td class="version" data-label="Version"></td>
<td class="actions">
<input type="button" value="Uninstall" class="do-uninstall">
</td>
@ -71,9 +75,9 @@
</thead>
<tbody class="template">
<tr>
<td class="name"></td>
<td class="description"></td>
<td class="version"></td>
<td class="name" data-label="Name"></td>
<td class="description" data-label="Description"></td>
<td class="version" data-label="Version"></td>
<td class="actions">
<input type="button" value="Install" class="do-install">
</td>
@ -89,6 +93,5 @@
</div>
</div>
<div id="topborder"></div>
</body>
</html>

View file

@ -1,7 +1,8 @@
<!doctype html>
<html>
<head>
<title>Settings - Etherpad lite</title>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0">
<title>Settings - Etherpad</title>
<meta name="viewport" content="width=device-width">
<link rel="stylesheet" href="../static/css/admin.css">
<script src="../static/js/jquery.js"></script>
<script src="../socket.io/socket.io.js"></script>
@ -23,22 +24,26 @@
<div class="menu">
<h1>Etherpad lite</h1>
<h1>Etherpad</h1>
<ul>
<% e.begin_block("adminMenu"); %>
<li><a href="plugins">Plugin manager</a> </li>
<li><a href="settings">Settings</a> </li>
<li><a href="plugins/info">Troubleshooting information</a> </li>
<% e.end_block(); %>
</ul>
</div>
<div class="innerwrapper">
<a href='https://github.com/ether/etherpad-lite/wiki/Example-Production-Settings.JSON'>Example production settings template</a>
<a href='https://github.com/ether/etherpad-lite/wiki/Example-Development-Settings.JSON'>Example development settings template</a>
<h2>Current configuration</h2>
<textarea class="settings"></textarea>
<input type="button" class="settingsButton" id="saveSettings" value="Save Settings">
<input type="button" class="settingsButton" id="restartEtherpad" value="Restart Etherpad">
<div id="response"></div>
<div class="separator"></div>
<a href='https://github.com/ether/etherpad-lite/wiki/Example-Production-Settings.JSON'>Example production settings template</a>
<a href='https://github.com/ether/etherpad-lite/wiki/Example-Development-Settings.JSON'>Example development settings template</a>
</div>
</div>
<div id="topborder"></div>
</body>
</html>

View file

@ -64,7 +64,8 @@
box-shadow: 0px 1px 8px rgba(0,0,0,0.3);
}
#inner {
width: 300px;
position:relative;
max-width: 300px;
margin: 0 auto;
}
#button {
@ -100,6 +101,10 @@
text-shadow: 0 1px 1px #fff;
margin: 16px auto 0;
}
#padname{
height:38px;
max-width:280px;
}
form {
height: 38px;
background: #fff;
@ -115,7 +120,8 @@
border-radius: 3px;
box-sizing: border-box;
-moz-box-sizing: border-box;
padding: 0 45px 0 10px;
line-height:36px; /* IE8 hack */
padding: 0px 45px 0 10px;
*padding: 0; /* IE7 hack */
width: 100%;
height: 100%;
@ -125,7 +131,7 @@
}
button[type="submit"] {
position: absolute;
right: 0;
left:253px;
width: 45px;
height: 38px;
}

View file

@ -40,8 +40,10 @@
<% e.end_block(); %>
<link rel="localizations" type="application/l10n+json" href="../../locales.json" />
<% e.begin_block("timesliderScripts"); %>
<script type="text/javascript" src="../../static/js/html10n.js"></script>
<script type="text/javascript" src="../../static/js/l10n.js"></script>
<% e.end_block(); %>
</head>
<% e.begin_block("timesliderBody"); %>
@ -211,6 +213,8 @@
}
var plugins = require('ep_etherpad-lite/static/js/pluginfw/client_plugins');
var socket = require('ep_etherpad-lite/static/js/timeslider').socket;
plugins.baseURL = baseURL;
plugins.update(function () {

View file

@ -85,8 +85,8 @@ var helper = {};
return !$iframe.contents().find("#editorloadingbox").is(":visible");
}, 50000).done(function(){
helper.padChrome$ = getFrameJQuery( $('#iframe-container iframe'));
helper.padOuter$ = getFrameJQuery(helper.padChrome$('iframe.[name="ace_outer"]'));
helper.padInner$ = getFrameJQuery( helper.padOuter$('iframe.[name="ace_inner"]'));
helper.padOuter$ = getFrameJQuery(helper.padChrome$('iframe[name="ace_outer"]'));
helper.padInner$ = getFrameJQuery( helper.padOuter$('iframe[name="ace_inner"]'));
//disable all animations, this makes tests faster and easier
helper.padChrome$.fx.off = true;

View file

@ -10,6 +10,7 @@
<div id="iframe-container"></div>
<script src="/static/js/jquery.js"></script>
<script src="/static/js/jquery_browser.js"></script>
<script src="lib/underscore.js"></script>
<script src="lib/mocha.js"></script>

View file

@ -179,7 +179,11 @@ $(function(){
//inject spec scripts into the dom
var $body = $('body');
$.each(specs, function(i, spec){
if(spec[0] != "/"){ // if the spec isn't a plugin spec which means the spec file might be in a different subfolder
$body.append('<script src="specs/' + spec + '"></script>')
}else{
$body.append('<script src="' + spec + '"></script>')
}
});
//initalize the test helper

View file

@ -100,8 +100,8 @@ describe("embed links", function(){
//open share dropdown
chrome$(".buttonicon-embed").click();
//check read only checkbox, a bit hacky
chrome$('#readonlyinput').attr('checked','checked').click().attr('checked','checked');
chrome$('#readonlyinput').click();
chrome$('#readonlyinput:checkbox:not(:checked)').attr('checked', 'checked');
//get the link of the share field + the actual pad url and compare them
var shareLink = chrome$("#linkinput").val();
@ -119,7 +119,9 @@ describe("embed links", function(){
//open share dropdown
chrome$(".buttonicon-embed").click();
//check read only checkbox, a bit hacky
chrome$('#readonlyinput').attr('checked','checked').click().attr('checked','checked');
chrome$('#readonlyinput').click();
chrome$('#readonlyinput:checkbox:not(:checked)').attr('checked', 'checked');
//get the link of the share field + the actual pad url and compare them
var embedCode = chrome$("#embedinput").val();
@ -129,5 +131,6 @@ describe("embed links", function(){
done();
});
});
});
});

View file

@ -19,6 +19,7 @@ describe("font select", function(){
//select monospace and fire change event
$monospaceoption.attr('selected','selected');
$viewfontmenu.val("monospace");
$viewfontmenu.change();
//check if font changed to monospace

View file

@ -56,10 +56,8 @@ describe("Language select and change", function(){
//click the language button
var $language = chrome$("#languagemenu");
var $languageoption = $language.find("[value=en]");
//select german
$languageoption.attr('selected','selected');
//select english
$language.val("en");
$language.change();
//get the value of the bold button

View file

@ -0,0 +1,64 @@
describe("timeslider", function(){
//create a new pad before each test run
beforeEach(function(cb){
helper.newPad(cb);
this.timeout(60000);
});
it("Shows a date and time in the timeslider and make sure it doesn't include NaN", function(done) {
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
// make some changes to produce 100 revisions
var revs = 10;
this.timeout(60000);
for(var i=0; i < revs; i++) {
setTimeout(function() {
// enter 'a' in the first text element
inner$("div").first().sendkeys('a');
}, 200);
}
setTimeout(function() {
// go to timeslider
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider');
setTimeout(function() {
var timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
var $sliderBar = timeslider$('#ui-slider-bar');
var latestContents = timeslider$('#padcontent').text();
// Expect the date and time to be shown
// Click somewhere on the timeslider
var e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150;
e.clientY = e.pageY = 45;
$sliderBar.trigger(e);
e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150;
e.clientY = e.pageY = 40;
$sliderBar.trigger(e);
e = new jQuery.Event('mousedown');
e.clientX = e.pageX = 150;
e.clientY = e.pageY = 50;
$sliderBar.trigger(e);
$sliderBar.trigger('mouseup')
setTimeout(function() {
//make sure the text has changed
expect( timeslider$('#timer').text() ).not.to.eql( "" );
expect( timeslider$('#revision_date').text() ).not.to.eql( "" );
expect( timeslider$('#revision_label').text() ).not.to.eql( "" );
var includesNaN = timeslider$('#revision_label').text().indexOf("NaN"); // NaN is bad. Naan ist gut
expect( includesNaN ).to.eql( -1 ); // not quite so tasty, I like curry.
done();
}, 400);
}, 2000);
}, 2000);
});
});

View file

@ -4,8 +4,7 @@ describe("timeslider", function(){
helper.newPad(cb);
this.timeout(6000);
});
xit("loads adds a hundred revisions", function(done) {
it("loads adds a hundred revisions", function(done) {
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
@ -57,14 +56,13 @@ describe("timeslider", function(){
}, 6000);
}, revs*timePerRev);
});
it("changes the url when clicking on the timeslider", function(done) {
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
// make some changes to produce 7 revisions
var timePerRev = 900
, revs = 7;
var timePerRev = 1000
, revs = 20;
this.timeout(revs*timePerRev+10000);
for(var i=0; i < revs; i++) {
setTimeout(function() {
@ -100,28 +98,48 @@ describe("timeslider", function(){
}, 6000);
}, revs*timePerRev);
});
it("jumps to a revision given in the url", function(done) {
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
this.timeout(11000);
this.timeout(20000);
// wait for the text to be loaded
helper.waitFor(function(){
return inner$('body').text().length != 0;
}, 6000).always(function() {
var newLines = inner$('body div').length;
var oldLength = inner$('body').text().length + newLines / 2;
expect( oldLength ).to.not.eql( 0 );
inner$("div").first().sendkeys('a');
setTimeout(function() {
// go to timeslider
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0');
var timeslider$;
// wait for our additional revision to be added
helper.waitFor(function(){
timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
return timeslider$ && timeslider$('#padcontent').text().length == 230;
// newLines takes the new lines into account which are strippen when using
// inner$('body').text(), one <div> is used for one line in ACE.
var lenOkay = inner$('body').text().length + newLines / 2 != oldLength;
// this waits for the color to be added to our <span>, which means that the revision
// was accepted by the server.
var colorOkay = inner$('span').first().attr('class').indexOf("author-") == 0;
return lenOkay && colorOkay;
}, 6000).always(function() {
expect( timeslider$('#padcontent').text().length ).to.eql( 230 );
// go to timeslider with a specific revision set
$('#iframe-container iframe').attr('src', $('#iframe-container iframe').attr('src')+'/timeslider#0');
// wait for the timeslider to be loaded
helper.waitFor(function(){
try {
timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
} catch(e){}
if(timeslider$){
return timeslider$('#padcontent').text().length == oldLength;
}
}, 6000).always(function(){
expect( timeslider$('#padcontent').text().length ).to.eql( oldLength );
done();
});
}, 2500);
});
});
});
it("checks the export url", function(done) {
var inner$ = helper.padInner$;
var chrome$ = helper.padChrome$;
@ -135,7 +153,9 @@ describe("timeslider", function(){
var exportLink;
helper.waitFor(function(){
try{
timeslider$ = $('#iframe-container iframe')[0].contentWindow.$;
}catch(e){}
if(!timeslider$)
return false;
exportLink = timeslider$('#exportplaina').attr('href');