summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--AUTHORS9
-rw-r--r--LICENSE2
-rw-r--r--README.md6
-rw-r--r--app.js5
-rw-r--r--app.json7
-rw-r--r--config.json.example6
-rw-r--r--lib/config.js4
-rw-r--r--lib/migrations/20160607060246-support-revision.js5
-rw-r--r--lib/models/index.js13
-rw-r--r--lib/models/note.js41
-rw-r--r--lib/models/revision.js43
-rwxr-xr-xlib/ot/editor-socketio-server.js8
-rw-r--r--lib/realtime.js14
-rwxr-xr-xlib/response.js20
-rw-r--r--lib/workers/dmpWorker.js14
-rw-r--r--locales/eo.json104
-rw-r--r--locales/fr.json32
-rw-r--r--package.json14
-rw-r--r--public/docs/features.md2
-rwxr-xr-xpublic/docs/release-notes.md29
-rw-r--r--public/js/common.js2
-rw-r--r--public/js/index.js24
-rw-r--r--public/vendor/ot/ot.min.js2
-rwxr-xr-x[-rw-r--r--]public/vendor/ot/socketio-adapter.js3
-rw-r--r--public/views/index.ejs3
-rw-r--r--webpackBaseConfig.js6
26 files changed, 307 insertions, 111 deletions
diff --git a/AUTHORS b/AUTHORS
index e366b8e9..b155d3fa 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,13 +1,19 @@
List of HackMD contributors.
+bananaapple
Bartlomiej Szala
+Colin Maudry
Dmytro Kytsmen
Fabien Meghazi
+Florian Rhiem
Ikumi Shimizu
ivanorsolic
Jason Croft
Jannik Lorenz
+James Stephenson
Jordan Matelsky
+Kenji Doi
+Lars Kajes
Lapinot
Laura Kyle
Marcelo Alencar
@@ -17,10 +23,13 @@ Max Wu
Ömer Erdinç Yağmurlu
p0v1n0m
Pablo Guerrero
+paraschadha2052
Peter Dave Hello
Qubo
Sergio Valverde
+Tom Wyckhuys
Yukai Huang
Zacharias Traianos
Zankio
+Xavier
葉家郡 \ No newline at end of file
diff --git a/LICENSE b/LICENSE
index 13c020c7..f573a0a9 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
The MIT License (MIT)
-Copyright (c) 2016 Max Wu <jackymaxj@gmail.com> and others
+Copyright (c) 2017 Max Wu <jackymaxj@gmail.com> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/README.md b/README.md
index a9270fb2..a2980ee4 100644
--- a/README.md
+++ b/README.md
@@ -22,6 +22,12 @@ You can quickly setup a sample heroku hackmd application by clicking the button
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
+[migration-to-0.5.0](https://github.com/hackmdio/migration-to-0.5.0)
+---
+
+We don't use LZString to compress socket.io data and DB data after version 0.5.0.
+Please run the migration tool if you're upgrading from the old version.
+
[migration-to-0.4.0](https://github.com/hackmdio/migration-to-0.4.0)
---
diff --git a/app.js b/app.js
index c6ee8ace..f096ab64 100644
--- a/app.js
+++ b/app.js
@@ -11,6 +11,7 @@ var compression = require('compression')
var session = require('express-session');
var SequelizeStore = require('connect-session-sequelize')(session.Store);
var fs = require('fs');
+var url = require('url');
var path = require('path');
var imgur = require('imgur');
var formidable = require('formidable');
@@ -102,7 +103,7 @@ app.use(helmet.hsts({
}));
i18n.configure({
- locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv'],
+ locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo'],
cookie: 'locale',
directory: __dirname + '/locales'
});
@@ -487,7 +488,7 @@ app.post('/uploadimage', function (req, res) {
switch (config.imageUploadType) {
case 'filesystem':
res.send({
- link: path.join(config.serverurl, files.image.path.match(/^public(.+$)/)[1])
+ link: url.resolve(config.serverurl, files.image.path.match(/^public(.+$)/)[1])
});
break;
diff --git a/app.json b/app.json
index fcdc60a7..6025bba9 100644
--- a/app.json
+++ b/app.json
@@ -35,11 +35,16 @@
"description": "sub url path, like `www.example.com/<URL_PATH>`",
"required": false
},
- "HMD_ALLOW_ORIGIN": {
+ "HMD_PORT": {
"description": "web app port",
"required": false,
"value": "80"
},
+ "HMD_ALLOW_ORIGIN": {
+ "description": "domain name whitelist (use comma to separate)",
+ "required": false,
+ "value": "localhost"
+ },
"HMD_PROTOCOL_USESSL": {
"description": "set to use ssl protocol for resources path (only applied when domain is set)",
"required": false
diff --git a/config.json.example b/config.json.example
index 7e4ac0b7..e5e3fc56 100644
--- a/config.json.example
+++ b/config.json.example
@@ -1,7 +1,9 @@
{
"test": {
- "dialect": "sqlite",
- "storage": "./db.hackmd.sqlite"
+ "db": {
+ "dialect": "sqlite",
+ "storage": "./db.hackmd.sqlite"
+ }
},
"development": {
"domain": "localhost",
diff --git a/lib/config.js b/lib/config.js
index f8df0a73..53497f1f 100644
--- a/lib/config.js
+++ b/lib/config.js
@@ -111,8 +111,8 @@ function getserverurl() {
return url;
}
-var version = '0.4.6';
-var minimumCompatibleVersion = '0.4.5';
+var version = '0.5.0';
+var minimumCompatibleVersion = '0.5.0';
var maintenance = true;
var cwd = path.join(__dirname, '..');
diff --git a/lib/migrations/20160607060246-support-revision.js b/lib/migrations/20160607060246-support-revision.js
index 9721d7fc..fa647d93 100644
--- a/lib/migrations/20160607060246-support-revision.js
+++ b/lib/migrations/20160607060246-support-revision.js
@@ -4,7 +4,10 @@ module.exports = {
up: function (queryInterface, Sequelize) {
queryInterface.addColumn('Notes', 'savedAt', Sequelize.DATE);
queryInterface.createTable('Revisions', {
- id: Sequelize.UUID,
+ id: {
+ type: Sequelize.UUID,
+ primaryKey: true
+ },
noteId: Sequelize.UUID,
patch: Sequelize.TEXT,
lastContent: Sequelize.TEXT,
diff --git a/lib/models/index.js b/lib/models/index.js
index de6cd13c..e83956e5 100644
--- a/lib/models/index.js
+++ b/lib/models/index.js
@@ -20,6 +20,19 @@ if (config.dburl)
else
sequelize = new Sequelize(dbconfig.database, dbconfig.username, dbconfig.password, dbconfig);
+// [Postgres] Handling NULL bytes
+// https://github.com/sequelize/sequelize/issues/6485
+function stripNullByte(value) {
+ return value ? value.replace(/\u0000/g, "") : value;
+}
+sequelize.stripNullByte = stripNullByte;
+
+function processData(data, _default, process) {
+ if (data === undefined) return data;
+ else return data === null ? _default : (process ? process(data) : data);
+}
+sequelize.processData = processData;
+
var db = {};
fs
diff --git a/lib/models/note.js b/lib/models/note.js
index 5727046c..5c63dc1a 100644
--- a/lib/models/note.js
+++ b/lib/models/note.js
@@ -52,13 +52,31 @@ module.exports = function (sequelize, DataTypes) {
defaultValue: 0
},
title: {
- type: DataTypes.TEXT
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('title'), "");
+ },
+ set: function (value) {
+ this.setDataValue('title', sequelize.stripNullByte(value));
+ }
},
content: {
- type: DataTypes.TEXT
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('content'), "");
+ },
+ set: function (value) {
+ this.setDataValue('content', sequelize.stripNullByte(value));
+ }
},
authorship: {
- type: DataTypes.TEXT
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse);
+ },
+ set: function (value) {
+ this.setDataValue('authorship', JSON.stringify(value));
+ }
},
lastchangeAt: {
type: DataTypes.DATE
@@ -124,8 +142,6 @@ module.exports = function (sequelize, DataTypes) {
var body = fs.readFileSync(filePath, 'utf8');
var contentLength = body.length;
var title = Note.parseNoteTitle(body);
- body = LZString.compressToBase64(body);
- title = LZString.compressToBase64(title);
if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) {
note.update({
title: title,
@@ -135,14 +151,14 @@ module.exports = function (sequelize, DataTypes) {
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
if (err) return _callback(err, null);
// update authorship on after making revision of docs
- var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
+ var patch = dmp.patch_fromText(revision.patch);
var operations = Note.transformPatchToOperations(patch, contentLength);
- var authorship = note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : [];
+ var authorship = note.authorship;
for (var i = 0; i < operations.length; i++) {
authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship);
}
note.update({
- authorship: LZString.compressToBase64(JSON.stringify(authorship))
+ authorship: JSON.stringify(authorship)
}).then(function (note) {
return callback(null, note.id);
}).catch(function (err) {
@@ -264,10 +280,7 @@ module.exports = function (sequelize, DataTypes) {
return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ');
},
decodeTitle: function (title) {
- var decodedTitle = LZString.decompressFromBase64(title);
- if (decodedTitle) title = decodedTitle;
- else title = 'Untitled';
- return title;
+ return title ? title : 'Untitled';
},
generateWebTitle: function (title) {
title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD";
@@ -496,8 +509,8 @@ module.exports = function (sequelize, DataTypes) {
if (Note.checkFileExist(filePath)) {
var fsCreatedTime = moment(fs.statSync(filePath).ctime);
body = fs.readFileSync(filePath, 'utf8');
- note.title = LZString.compressToBase64(Note.parseNoteTitle(body));
- note.content = LZString.compressToBase64(body);
+ note.title = Note.parseNoteTitle(body);
+ note.content = body;
if (filePath !== config.defaultnotepath) {
note.createdAt = fsCreatedTime;
}
diff --git a/lib/models/revision.js b/lib/models/revision.js
index 8b8eba94..c7360fed 100644
--- a/lib/models/revision.js
+++ b/lib/models/revision.js
@@ -2,7 +2,6 @@
// external modules
var Sequelize = require("sequelize");
-var LZString = require('lz-string');
var async = require('async');
var moment = require('moment');
var childProcess = require('child_process');
@@ -60,19 +59,43 @@ module.exports = function (sequelize, DataTypes) {
defaultValue: Sequelize.UUIDV4
},
patch: {
- type: DataTypes.TEXT
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('patch'), "");
+ },
+ set: function (value) {
+ this.setDataValue('patch', sequelize.stripNullByte(value));
+ }
},
lastContent: {
- type: DataTypes.TEXT
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('lastContent'), "");
+ },
+ set: function (value) {
+ this.setDataValue('lastContent', sequelize.stripNullByte(value));
+ }
},
content: {
- type: DataTypes.TEXT
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('content'), "");
+ },
+ set: function (value) {
+ this.setDataValue('content', sequelize.stripNullByte(value));
+ }
},
length: {
type: DataTypes.INTEGER
},
authorship: {
- type: DataTypes.TEXT
+ type: DataTypes.TEXT,
+ get: function () {
+ return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse);
+ },
+ set: function (value) {
+ this.setDataValue('authorship', value ? JSON.stringify(value) : value);
+ }
}
}, {
classMethods: {
@@ -214,7 +237,7 @@ module.exports = function (sequelize, DataTypes) {
Revision.create({
noteId: note.id,
lastContent: note.content,
- length: LZString.decompressFromBase64(note.content).length,
+ length: note.content.length,
authorship: note.authorship
}).then(function (revision) {
Revision.finishSaveNoteRevision(note, revision, callback);
@@ -223,8 +246,8 @@ module.exports = function (sequelize, DataTypes) {
});
} else {
var latestRevision = revisions[0];
- var lastContent = LZString.decompressFromBase64(latestRevision.content || latestRevision.lastContent);
- var content = LZString.decompressFromBase64(note.content);
+ var lastContent = latestRevision.content || latestRevision.lastContent;
+ var content = note.content;
sendDmpWorker({
msg: 'create patch',
lastDoc: lastContent,
@@ -244,9 +267,9 @@ module.exports = function (sequelize, DataTypes) {
} else {
Revision.create({
noteId: note.id,
- patch: LZString.compressToBase64(patch),
+ patch: patch,
content: note.content,
- length: LZString.decompressFromBase64(note.content).length,
+ length: note.content.length,
authorship: note.authorship
}).then(function (revision) {
// clear last revision content to reduce db size
diff --git a/lib/ot/editor-socketio-server.js b/lib/ot/editor-socketio-server.js
index d062fa19..7b204539 100755
--- a/lib/ot/editor-socketio-server.js
+++ b/lib/ot/editor-socketio-server.js
@@ -7,7 +7,6 @@ var Server = require('./server');
var Selection = require('./selection');
var util = require('util');
-var LZString = require('lz-string');
var logger = require('../logger');
function EditorSocketIOServer(document, operations, docId, mayWrite, operationCallback) {
@@ -40,10 +39,8 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
revision: this.operations.length,
clients: this.users
};
- socket.emit('doc', LZString.compressToUTF16(JSON.stringify(docOut)));
+ socket.emit('doc', docOut);
socket.on('operation', function (revision, operation, selection) {
- operation = LZString.decompressFromUTF16(operation);
- operation = JSON.parse(operation);
socket.origin = 'operation';
self.mayWrite(socket, function (mayWrite) {
if (!mayWrite) {
@@ -62,7 +59,7 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
clients: self.users,
force: true
};
- socket.emit('doc', LZString.compressToUTF16(JSON.stringify(docOut)));
+ socket.emit('doc', docOut);
}, 100);
}
});
@@ -129,7 +126,6 @@ EditorSocketIOServer.prototype.onGetOperations = function (socket, base, head) {
var operations = this.operations.slice(base, head).map(function (op) {
return op.wrapped.toJSON();
});
- operations = LZString.compressToUTF16(JSON.stringify(operations));
socket.emit('operations', head, operations);
};
diff --git a/lib/realtime.js b/lib/realtime.js
index c243ffc1..a662deeb 100644
--- a/lib/realtime.js
+++ b/lib/realtime.js
@@ -71,7 +71,6 @@ function emitCheck(note) {
authors: note.authors,
authorship: note.authorship
};
- out = LZString.compressToUTF16(JSON.stringify(out));
realtime.io.to(note.id).emit('check', out);
}
@@ -153,12 +152,10 @@ function finishUpdateNote(note, _note, callback) {
if (!note || !note.server) return callback(null, null);
var body = note.server.document;
var title = note.title = models.Note.parseNoteTitle(body);
- title = LZString.compressToBase64(title);
- body = LZString.compressToBase64(body);
var values = {
title: title,
content: body,
- authorship: LZString.compressToBase64(JSON.stringify(note.authorship)),
+ authorship: note.authorship,
lastchangeuserId: note.lastchangeuser,
lastchangeAt: Date.now()
};
@@ -301,7 +298,6 @@ function emitOnlineUsers(socket) {
var out = {
users: users
};
- out = LZString.compressToUTF16(JSON.stringify(out));
realtime.io.to(noteId).emit('online users', out);
}
@@ -330,7 +326,6 @@ function emitRefresh(socket) {
createtime: note.createtime,
updatetime: note.updatetime
};
- out = LZString.compressToUTF16(JSON.stringify(out));
socket.emit('refresh', out);
}
@@ -462,7 +457,7 @@ function startConnection(socket) {
var lastchangeuser = note.lastchangeuserId;
var lastchangeuserprofile = note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null;
- var body = LZString.decompressFromBase64(note.content);
+ var body = note.content;
var createtime = note.createdAt;
var updatetime = note.lastchangeAt;
var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback);
@@ -482,7 +477,7 @@ function startConnection(socket) {
notes[noteId] = {
id: noteId,
alias: note.alias,
- title: LZString.decompressFromBase64(note.title),
+ title: note.title,
owner: owner,
ownerprofile: ownerprofile,
permission: note.permission,
@@ -494,7 +489,7 @@ function startConnection(socket) {
updatetime: moment(updatetime).valueOf(),
server: server,
authors: authors,
- authorship: note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : []
+ authorship: note.authorship
};
return finishConnection(socket, notes[noteId], users[socket.id]);
@@ -863,7 +858,6 @@ function connection(socket) {
var out = {
users: users
};
- out = LZString.compressToUTF16(JSON.stringify(out));
socket.emit('online users', out);
});
diff --git a/lib/response.js b/lib/response.js
index 2b38cf25..54e2a337 100755
--- a/lib/response.js
+++ b/lib/response.js
@@ -75,7 +75,7 @@ function showIndex(req, res, next) {
}
function responseHackMD(res, note) {
- var body = LZString.decompressFromBase64(note.content);
+ var body = note.content;
var meta = null;
try {
meta = models.Note.parseMeta(metaMarked(body).meta);
@@ -191,7 +191,7 @@ function showPublishNote(req, res, next) {
if (!note) {
return response.errorNotFound(res);
}
- var body = LZString.decompressFromBase64(note.content);
+ var body = note.content;
var meta = null;
var markdown = null;
try {
@@ -209,7 +209,7 @@ function showPublishNote(req, res, next) {
var origin = config.serverurl;
var data = {
title: title,
- description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
+ description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
viewcount: note.viewcount,
createtime: createtime,
updatetime: updatetime,
@@ -248,7 +248,7 @@ function actionSlide(req, res, note) {
}
function actionDownload(req, res, note) {
- var body = LZString.decompressFromBase64(note.content);
+ var body = note.content;
var title = models.Note.decodeTitle(note.title);
var filename = title;
filename = encodeURIComponent(filename);
@@ -265,7 +265,7 @@ function actionDownload(req, res, note) {
}
function actionInfo(req, res, note) {
- var body = LZString.decompressFromBase64(note.content);
+ var body = note.content;
var meta = null;
var markdown = null;
try {
@@ -281,7 +281,7 @@ function actionInfo(req, res, note) {
var title = models.Note.decodeTitle(note.title);
var data = {
title: meta.title || title,
- description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
+ description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
viewcount: note.viewcount,
createtime: createtime,
updatetime: updatetime
@@ -297,7 +297,7 @@ function actionInfo(req, res, note) {
}
function actionPDF(req, res, note) {
- var body = LZString.decompressFromBase64(note.content);
+ var body = note.content;
try {
body = metaMarked(body).markdown;
} catch(err) {
@@ -479,7 +479,7 @@ function githubActionGist(req, res, note) {
if (!error && httpResponse.statusCode == 200) {
var access_token = body.access_token;
if (access_token) {
- var content = LZString.decompressFromBase64(note.content);
+ var content = note.content;
var title = models.Note.decodeTitle(note.title);
var filename = title.replace('/', ' ') + '.md';
var gist = {
@@ -579,7 +579,7 @@ function showPublishSlide(req, res, next) {
if (!note) {
return response.errorNotFound(res);
}
- var body = LZString.decompressFromBase64(note.content);
+ var body = note.content;
var meta = null;
var markdown = null;
try {
@@ -597,7 +597,7 @@ function showPublishSlide(req, res, next) {
var origin = config.serverurl;
var data = {
title: title,
- description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
+ description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
viewcount: note.viewcount,
createtime: createtime,
updatetime: updatetime,
diff --git a/lib/workers/dmpWorker.js b/lib/workers/dmpWorker.js
index fae36191..8e69636e 100644
--- a/lib/workers/dmpWorker.js
+++ b/lib/workers/dmpWorker.js
@@ -1,5 +1,4 @@
// external modules
-var LZString = require('lz-string');
var DiffMatchPatch = require('diff-match-patch');
var dmp = new DiffMatchPatch();
@@ -58,7 +57,6 @@ process.on('message', function(data) {
function createPatch(lastDoc, currDoc) {
var ms_start = (new Date()).getTime();
var diff = dmp.diff_main(lastDoc, currDoc);
- dmp.diff_cleanupSemantic(diff);
var patch = dmp.patch_make(lastDoc, diff);
patch = dmp.patch_toText(patch);
var ms_end = (new Date()).getTime();
@@ -80,10 +78,10 @@ function getRevision(revisions, count) {
for (var i = 0; i < count; i++) {
var revision = revisions[i];
if (i == 0) {
- startContent = LZString.decompressFromBase64(revision.content || revision.lastContent);
+ startContent = revision.content || revision.lastContent;
}
if (i != count - 1) {
- var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
+ var patch = dmp.patch_fromText(revision.patch);
applyPatches = applyPatches.concat(patch);
}
lastPatch = revision.patch;
@@ -105,11 +103,11 @@ function getRevision(revisions, count) {
for (var i = l; i >= count - 1; i--) {
var revision = revisions[i];
if (i == l) {
- startContent = LZString.decompressFromBase64(revision.lastContent);
+ startContent = revision.lastContent;
authorship = revision.authorship;
}
if (revision.patch) {
- var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
+ var patch = dmp.patch_fromText(revision.patch);
applyPatches = applyPatches.concat(patch);
}
lastPatch = revision.patch;
@@ -123,8 +121,8 @@ function getRevision(revisions, count) {
}
var data = {
content: finalContent,
- patch: dmp.patch_fromText(LZString.decompressFromBase64(lastPatch)),
- authorship: authorship ? JSON.parse(LZString.decompressFromBase64(authorship)) : null
+ patch: dmp.patch_fromText(lastPatch),
+ authorship: authorship
};
var ms_end = (new Date()).getTime();
if (config.debug) {
diff --git a/locales/eo.json b/locales/eo.json
new file mode 100644
index 00000000..c7c8cad2
--- /dev/null
+++ b/locales/eo.json
@@ -0,0 +1,104 @@
+{
+ "Collaborative markdown notes": "Kunlaborataj marksubenaj notoj",
+ "Realtime collaborative markdown notes on all platforms.": "Tujkunlaborataj marksubenaj notoj ĉe ĉiuj sistemoj.",
+ "Best way to write and share your knowledge in markdown.": "La plej bona maniero skribi kaj havigi vian scion marksubene.",
+ "Intro": "Enkonduko",
+ "History": "Historio",
+ "New guest note": "Novan gastan noton",
+ "Collaborate with URL": "Kunlaboru per URL",
+ "Support charts and MathJax": "Ebleco por skemoj kaj MathJax",
+ "Support slide mode": "Ebleco por bildvica modo",
+ "Sign In": "Ensalutu",
+ "Below is the history from browser": "Malsupre estas la historio de la retumilo",
+ "Welcome!": "Bonvenon!",
+ "New note": "Novan Noton",
+ "or": "aŭ",
+ "Sign Out": "Elsalutu",
+ "Explore all features": "Esploru ĉiujn eblecojn",
+ "Select tags...": "Elektu etikedojn..",
+ "Search keyword...": "Serĉu ĉefvorton...",
+ "Sort by title": "Ordigu laŭ titolo",
+ "Title": "Titolo",
+ "Sort by time": "Ordigu laŭ tempo",
+ "Time": "Tempo",
+ "Export history": "Elportu historion",
+ "Import history": "Alportu historion",
+ "Clear history": "Malplenigu historion",
+ "Refresh history": "Refreŝigu historion",
+ "No history": "Neniu historio",
+ "Import from browser": "Alportu de retumilo",
+ "Releases": "Eldonoj",
+ "Are you sure?": "Ĉu vi certas?",
+ "Cancel": "Nuligu",
+ "Yes, do it!": "Jes, faru ĝin!",
+ "Choose method": "Elektu metodon",
+ "Sign in via %s": "Ensalutu per %s",
+ "New": "Nova",
+ "Publish": "Dissendu",
+ "Extra": "Plia",
+ "Revision": "Versio",
+ "Slide Mode": "Bildvica modo",
+ "Export": "Elportu",
+ "Import": "Alportu",
+ "Clipboard": "Poŝo",
+ "Download": "Elŝuti",
+ "Raw HTML": "Kruda HTML",
+ "Edit": "Redaktu",
+ "View": "Vidu",
+ "Both": "Ambaŭ",
+ "Help": "Helpo",
+ "Upload Image": "Alŝutu bildon",
+ "Menu": "Menuo",
+ "This page need refresh": "Ĉi tiu paĝo bezonas refreŝiĝi",
+ "You have an incompatible client version.": "Vi havas malkongruan klientversion.",
+ "Refresh to update.": "Refreŝigu por ĝisdatigi",
+ "New version available!": "Nova versio disponeblas!",
+ "See releases notes here": "Vidu elsendajn notojn ĉi tie",
+ "Refresh to enjoy new features.": "Refreŝigu por ĝui novajn eblecojn.",
+ "Your user state has changed.": "Via uzantstato ŝanĝiĝis.",
+ "Refresh to load new user state.": "Refreŝigu por ŝargi novan uzantstaton.",
+ "Refresh": "Refreŝigu",
+ "Contacts": "Kontaktuloj",
+ "Report an issue": "Raportu problemon",
+ "Send us email": "Sendu al ni retpoŝton",
+ "Documents": "Dosieroj",
+ "Features": "Eblecoj",
+ "YAML Metadata": "YAML metadateno",
+ "Slide Example": "Bildvica ekzemplo",
+ "Cheatsheet": "Gvidfolio",
+ "Example": "Ekzemplo",
+ "Syntax": "Sintakso",
+ "Header": "Paĝokapo",
+ "Unordered List": "Neordita Listo",
+ "Ordered List": "Ordita Listo",
+ "Todo List": "Farenda Listo",
+ "Blockquote": "Deŝovita cito",
+ "Bold font": "Dika tiparo",
+ "Italics font": "Kursiva tiparo",
+ "Strikethrough": "Trastrekita",
+ "Inserted text": "Enmetita teksto",
+ "Marked text": "Markita teksto",
+ "Link": "Ligilo",
+ "Image": "Bildo",
+ "Code": "Kodo",
+ "Externals": "Eksteraĵoj",
+ "This is a alert area.": "Ĉi tiu estas avertzono.",
+ "Revert": "Malfaru ŝanĝojn",
+ "Import from clipboard": "Alportu de la poŝo",
+ "Paste your markdown or webpage here...": "Algluu vian marksubenon aŭ retpaĝaron ĉi tie...",
+ "Clear": "Malplenigu",
+ "This note is locked": "Ĉi tiu noto estas ŝlosita",
+ "Sorry, only owner can edit this note.": "Bedaŭrinde, nur la proprulo povas redakti ĉi tiun noton.",
+ "OK": "Bone",
+ "Reach the limit": "Atingi la limigon",
+ "Sorry, you've reached the max length this note can be.": "Pardonon, ĉi tiu noto jam atingis maksimuman longecon.",
+ "Please reduce the content or divide it to more notes, thank you!": "Bonvolu malpligrandigi la enhavaĵon, aŭ dividi ĝin en pliajn notojn!",
+ "Import from Gist": "Alportu el Gist",
+ "Paste your gist url here...": "Algluu vian gist-an URL-n ĉi tie...",
+ "Import from Snippet": "Alportu el tekstero",
+ "Select From Available Projects": "Elektu el disponeblaj projektoj",
+ "Select From Available Snippets": "Elektu el disponeblaj teksteroj",
+ "OR": "AŬ",
+ "Export to Snippet": "Elportu al Snippet",
+ "Select Visibility Level": "Elektu videblecan nivelon"
+}
diff --git a/locales/fr.json b/locales/fr.json
index 98c13360..8fd2b5b2 100644
--- a/locales/fr.json
+++ b/locales/fr.json
@@ -9,12 +9,12 @@
"Support charts and MathJax": "Supporte les graphiques et MathJax",
"Support slide mode": "Supporte le mode présentation",
"Sign In": "Se connecter",
- "Below is the history from browser": "En dessous ce situe l'historique du navigateur",
- "Welcome!": "Bienvenue!",
+ "Below is the history from browser": "Ci-dessous, l'historique du navigateur",
+ "Welcome!": "Bienvenue !",
"New note": "Nouvelle note",
"or": "ou",
"Sign Out": "Se déconnecter",
- "Explore all features": "Explorer toutes les fonctionnalitées",
+ "Explore all features": "Explorer toutes les fonctionnalités",
"Select tags...": "Selectionner les tags...",
"Search keyword...": "Chercher un mot-clef...",
"Sort by title": "Trier par titre",
@@ -28,22 +28,22 @@
"No history": "Pas d'historique",
"Import from browser": "Importer depuis le navigateur",
"Releases": "Versions",
- "Are you sure?": "Etes-vous sûr?",
+ "Are you sure?": "Ëtes-vous sûr ?",
"Cancel": "Annuler",
- "Yes, do it!": "Oui, je suis sûr!",
+ "Yes, do it!": "Oui, je suis sûr !",
"Choose method": "Choisir la méthode",
"Sign in via %s": "Se connecter depuis %s",
"New": "Nouvelle",
"Publish": "Publier",
"Extra": "Extra",
"Revision": "Historique",
- "Slide Mode": "Mode Présentation",
+ "Slide Mode": "Mode présentation",
"Export": "Exporter",
"Import": "Importer",
"Clipboard": "Presse-papier",
"Download": "Télécharger",
- "Raw HTML": "HTML Brut",
- "Edit": "Editer",
+ "Raw HTML": "HTML brut",
+ "Edit": "Éditer",
"View": "Voir",
"Both": "Les deux",
"Help": "Aide",
@@ -52,25 +52,25 @@
"This page need refresh": "Cette page doit être rechargée",
"You have an incompatible client version.": "Vous avez une version client incompatible.",
"Refresh to update.": "Recharger pour mettre à jour.",
- "New version available!": "Nouvelle version disponible!",
+ "New version available!": "Nouvelle version disponible !",
"See releases notes here": "Voir les commentaires de version ici",
- "Refresh to enjoy new features.": "Recharger pour bénéficier des nouvelles fonctionnalitées.",
- "Your user state has changed.": "Votre status utilisateur a changé.",
+ "Refresh to enjoy new features.": "Recharger pour bénéficier des nouvelles fonctionnalités.",
+ "Your user state has changed.": "Votre statut utilisateur a changé.",
"Refresh to load new user state.": "Recharger pour avoir le nouveau statut utilisateur.",
"Refresh": "Recharger",
"Contacts": "Contacts",
"Report an issue": "Signaler un problème",
"Send us email": "Envoyez-nous un mail",
"Documents": "Documents",
- "Features": "Fonctionnalitées",
+ "Features": "Fonctionnalités",
"YAML Metadata": "Métadonnées YAML",
"Slide Example": "Exemple de présentation",
"Cheatsheet": "Pense-bête",
"Example": "Exemple",
"Syntax": "Syntaxe",
- "Header": "Entête",
- "Unordered List": "Liste non-ordonnée",
- "Ordered List": "List ordonnée",
+ "Header": "En-tête",
+ "Unordered List": "Liste à puce",
+ "Ordered List": "List numérotée",
"Todo List": "Liste de tâches",
"Blockquote": "Citation",
"Bold font": "Gras",
@@ -94,7 +94,7 @@
"Sorry, you've reached the max length this note can be.": "Désolé, vous avez atteint la longueur maximale que cette note peut avoir.",
"Please reduce the content or divide it to more notes, thank you!": "Merci de réduire le contenu ou de le diviser en plusieurs notes!",
"Import from Gist": "Importer depuis Gist",
- "Paste your gist url here...": "Coller votre URL Gist ici...",
+ "Paste your gist url here...": "Coller l'URL de votre Gist ici...",
"Import from Snippet": "Importer depuis Snippet",
"Select From Available Projects": "Sélectionner depuis les projets disponibles",
"Select From Available Snippets": "Sélectionner depuis les Snippets disponibles",
diff --git a/package.json b/package.json
index d44313cd..edf03649 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "hackmd",
- "version": "0.4.6",
+ "version": "0.5.0",
"description": "Realtime collaborative markdown notes on all platforms.",
"main": "app.js",
"license": "MIT",
@@ -13,7 +13,7 @@
"dependencies": {
"Idle.Js": "github:shawnmclean/Idle.js",
"async": "^2.1.4",
- "aws-sdk": "^2.7.15",
+ "aws-sdk": "^2.7.20",
"blueimp-md5": "^2.6.0",
"body-parser": "^1.15.2",
"bootstrap": "^3.3.7",
@@ -38,7 +38,7 @@
"formidable": "^1.0.17",
"gist-embed": "~2.6.0",
"handlebars": "^4.0.6",
- "helmet": "^3.1.0",
+ "helmet": "^3.3.0",
"highlight.js": "~9.9.0",
"i18n": "^0.8.3",
"imgur": "git+https://github.com/hackmdio/node-imgur.git",
@@ -54,7 +54,7 @@
"keymaster": "^1.6.2",
"list.js": "^1.3.0",
"list.pagination.js": "^0.1.1",
- "lodash": "^4.17.2",
+ "lodash": "^4.17.4",
"lz-string": "1.4.4",
"markdown-it": "^8.2.2",
"markdown-it-abbr": "^1.0.4",
@@ -98,7 +98,7 @@
"reveal.js": "^3.3.0",
"scrypt": "^6.0.3",
"select2": "^3.5.2-browserify",
- "sequelize": "^3.27.0",
+ "sequelize": "^3.28.0",
"sequelize-cli": "^2.5.1",
"shortid": "2.2.6",
"socket.io": "~1.7.2",
@@ -118,7 +118,7 @@
"vue": "^2.1.6",
"vue-loader": "^10.0.2",
"winston": "^2.3.0",
- "xss": "^0.3.2"
+ "xss": "^0.3.3"
},
"engines": {
"node": ">=4.x"
@@ -154,7 +154,7 @@
"expose-loader": "^0.7.1",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
- "html-webpack-plugin": "^2.24.1",
+ "html-webpack-plugin": "^2.25.0",
"imports-loader": "^0.7.0",
"json-loader": "^0.5.4",
"less": "^2.7.1",
diff --git a/public/docs/features.md b/public/docs/features.md
index 270bf77b..1c25bfea 100644
--- a/public/docs/features.md
+++ b/public/docs/features.md
@@ -34,7 +34,7 @@ This will automatically upload the image to **[imgur](http://imgur.com)**, nothi
## Share Notes:
If you want to share an **editable** note, just copy the URL.
-If you want to share a **read-only** note, simply press share button <i class="fa fa-share-alt"></i> and copy the URL.
+If you want to share a **read-only** note, simply press publish button <i class="fa fa-share-square-o"></i> and copy the URL.
## Save a Note:
Currently, you can save to **Dropbox** <i class="fa fa-dropbox"></i> or save an `.md` file <i class="fa fa-file-text"></i> locally.
diff --git a/public/docs/release-notes.md b/public/docs/release-notes.md
index b0410ced..8c8e454b 100755
--- a/public/docs/release-notes.md
+++ b/public/docs/release-notes.md
@@ -1,6 +1,35 @@
Release Notes
===
+<i class="fa fa-tag"></i> 0.5.0 `Ristretto` <i class="fa fa-clock-o"></i> 2017-01-02 02:35
+---
+### Enhancements
+* Update year to 2017 (Happy New Year!)
+* Update to improve editor performance by debounce checkEditorScrollbar event
+* Refactor data processing to model definition
+* Update to remove null byte on editor changes
+* Update to remove null byte before saving to DB
+* Update to support Esperanto locale
+* Little improvements (typos, uppercase + accents, better case) for French locale
+* Update features.md publish button name and icon
+
+### Fixes
+* Fix authorship might losing update event because of throttling
+* Fix migration script of revision lacks of definition of primary key
+* Fix to not use diff_cleanupSemantic
+* Fix URL concatenation when uploading images to local filesystem
+* Fix js-url not import correctly
+* Fixed typo: anonmyous
+* Fix codemirror spell checker not considering abbreviation which contain apostrophe in word
+* Fix possible user is undefined in realtime events
+* Fix wrong package name reference in webpack config for bootstrap-validator
+* Fix email option in config not parse correctly
+* Fix mathjax not able to render issue
+
+### Removes
+- Remove LZString compression for data storage
+- Remove LZString compression for some socket.io event data
+
<i class="fa fa-tag"></i> 0.4.6 `Melya` <i class="fa fa-clock-o"></i> 2016-12-19 17:20
---
### Features
diff --git a/public/js/common.js b/public/js/common.js
index 52839caf..f5bfc8ec 100644
--- a/public/js/common.js
+++ b/public/js/common.js
@@ -12,7 +12,7 @@ window.serverurl = window.location.protocol + '//' + (domain ? domain : window.l
var noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1];
var noteurl = serverurl + '/' + noteid;
-var version = '0.4.6';
+var version = '0.5.0';
var checkAuth = false;
var profile = null;
diff --git a/public/js/index.js b/public/js/index.js
index 4a466245..8921eda3 100644
--- a/public/js/index.js
+++ b/public/js/index.js
@@ -11,7 +11,6 @@ require('highlight.js/styles/github-gist.css');
var toMarkdown = require('to-markdown');
var saveAs = require('file-saver').saveAs;
-var url = require('js-url');
var randomColor = require('randomcolor');
var _ = require("lodash");
@@ -1225,7 +1224,11 @@ function checkSyncToggle() {
}
}
-function checkEditorScrollbar() {
+var checkEditorScrollbar = _.debounce(function () {
+ editor.operation(checkEditorScrollbarInner);
+}, 50);
+
+function checkEditorScrollbarInner() {
// workaround simple scroll bar knob
// will get wrong position when editor height changed
var scrollInfo = editor.getScrollInfo();
@@ -2445,7 +2448,7 @@ function updateInfo(data) {
updateAuthorship();
}
}
-var updateAuthorship = _.throttle(function () {
+var updateAuthorship = _.debounce(function () {
editor.operation(updateAuthorshipInner);
}, 50);
function initMark() {
@@ -2647,8 +2650,6 @@ editor.on('update', function () {
});
});
socket.on('check', function (data) {
- data = LZString.decompressFromUTF16(data);
- data = JSON.parse(data);
//console.log(data);
updateInfo(data);
});
@@ -2658,8 +2659,6 @@ socket.on('permission', function (data) {
var docmaxlength = null;
var permission = null;
socket.on('refresh', function (data) {
- data = LZString.decompressFromUTF16(data);
- data = JSON.parse(data);
//console.log(data);
docmaxlength = data.docmaxlength;
editor.setOption("maxLength", docmaxlength);
@@ -2706,8 +2705,6 @@ var CodeMirrorAdapter = ot.CodeMirrorAdapter;
var cmClient = null;
socket.on('doc', function (obj) {
- obj = LZString.decompressFromUTF16(obj);
- obj = JSON.parse(obj);
var body = obj.str;
var bodyMismatch = editor.getValue() !== body;
var havePendingOperation = cmClient && Object.keys(cmClient.state).length > 0;
@@ -2768,8 +2765,6 @@ socket.on('operation', function () {
});
socket.on('online users', function (data) {
- data = LZString.decompressFromUTF16(data);
- data = JSON.parse(data);
if (debug)
console.debug(data);
onlineUsers = data.users;
@@ -3217,6 +3212,12 @@ function buildCursor(user) {
}
//editor actions
+function removeNullByte(cm, change) {
+ var str = change.text.join("\n");
+ if (/\u0000/g.test(str) && change.update) {
+ change.update(change.from, change.to, str.replace(/\u0000/g, "").split("\n"));
+ }
+}
function enforceMaxLength(cm, change) {
var maxLength = cm.getOption("maxLength");
if (maxLength && change.update) {
@@ -3238,6 +3239,7 @@ var ignoreEmitEvents = ['setValue', 'ignoreHistory'];
editor.on('beforeChange', function (cm, change) {
if (debug)
console.debug(change);
+ removeNullByte(cm, change);
if (enforceMaxLength(cm, change)) {
$('.limit-modal').modal('show');
}
diff --git a/public/vendor/ot/ot.min.js b/public/vendor/ot/ot.min.js
index a784b35f..84f030cd 100644
--- a/public/vendor/ot/ot.min.js
+++ b/public/vendor/ot/ot.min.js
@@ -1 +1 @@
-function hex2rgb(t){if("#"==t[0]&&(t=t.substr(1)),3==t.length){var e=t;t="",e=/^([a-f0-9])([a-f0-9])([a-f0-9])$/i.exec(e).slice(1);for(var n=0;n<3;n++)t+=e[n]+e[n]}var o=/^([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i.exec(t).slice(1);return{red:parseInt(o[0],16),green:parseInt(o[1],16),blue:parseInt(o[2],16)}}if("undefined"==typeof ot)var ot={};if(ot.TextOperation=function(){"use strict";function t(){return this&&this.constructor===t?(this.ops=[],this.baseLength=0,void(this.targetLength=0)):new t}function e(e,n){var o=e.ops,r=t.isRetain;switch(o.length){case 1:return o[0];case 2:return r(o[0])?o[1]:r(o[1])?o[0]:null;case 3:if(r(o[0])&&r(o[2]))return o[1]}return null}function n(t){return o(t.ops[0])?t.ops[0]:0}t.prototype.equals=function(t){if(this.baseLength!==t.baseLength)return!1;if(this.targetLength!==t.targetLength)return!1;if(this.ops.length!==t.ops.length)return!1;for(var e=0;e<this.ops.length;e++)if(this.ops[e]!==t.ops[e])return!1;return!0};var o=t.isRetain=function(t){return"number"==typeof t&&t>0},r=t.isInsert=function(t){return"string"==typeof t},i=t.isDelete=function(t){return"number"==typeof t&&t<0};return t.prototype.retain=function(t){if("number"!=typeof t)throw new Error("retain expects an integer");return 0===t?this:(this.baseLength+=t,this.targetLength+=t,o(this.ops[this.ops.length-1])?this.ops[this.ops.length-1]+=t:this.ops.push(t),this)},t.prototype.insert=function(t){if("string"!=typeof t)throw new Error("insert expects a string");if(""===t)return this;this.targetLength+=t.length;var e=this.ops;return r(e[e.length-1])?e[e.length-1]+=t:i(e[e.length-1])?r(e[e.length-2])?e[e.length-2]+=t:(e[e.length]=e[e.length-1],e[e.length-2]=t):e.push(t),this},t.prototype["delete"]=function(t){if("string"==typeof t&&(t=t.length),"number"!=typeof t)throw new Error("delete expects an integer or a string");return 0===t?this:(t>0&&(t=-t),this.baseLength-=t,i(this.ops[this.ops.length-1])?this.ops[this.ops.length-1]+=t:this.ops.push(t),this)},t.prototype.isNoop=function(){return 0===this.ops.length||1===this.ops.length&&o(this.ops[0])},t.prototype.toString=function(){var t=Array.prototype.map||function(t){for(var e=this,n=[],o=0,r=e.length;o<r;o++)n[o]=t(e[o]);return n};return t.call(this.ops,function(t){return o(t)?"retain "+t:r(t)?"insert '"+t+"'":"delete "+-t}).join(", ")},t.prototype.toJSON=function(){return this.ops},t.fromJSON=function(e){for(var n=new t,s=0,a=e.length;s<a;s++){var h=e[s];if(o(h))n.retain(h);else if(r(h))n.insert(h);else{if(!i(h))throw new Error("unknown operation: "+JSON.stringify(h));n["delete"](h)}}return n},t.prototype.apply=function(t){var e=this;if(t.length!==e.baseLength)throw new Error("The operation's base length must be equal to the string's length.");for(var n=[],i=0,s=0,a=this.ops,h=0,p=a.length;h<p;h++){var c=a[h];if(o(c)){if(s+c>t.length)throw new Error("Operation can't retain more characters than are left in the string.");n[i++]=t.slice(s,s+c),s+=c}else r(c)?n[i++]=c:s-=c}if(s!==t.length)throw new Error("The operation didn't operate on the whole string.");return n.join("")},t.prototype.invert=function(e){for(var n=0,i=new t,s=this.ops,a=0,h=s.length;a<h;a++){var p=s[a];o(p)?(i.retain(p),n+=p):r(p)?i["delete"](p.length):(i.insert(e.slice(n,n-p)),n-=p)}return i},t.prototype.compose=function(e){var n=this;if(n.targetLength!==e.baseLength)throw new Error("The base length of the second operation has to be the target length of the first operation");for(var s=new t,a=n.ops,h=e.ops,p=0,c=0,l=a[p++],u=h[c++];;){if("undefined"==typeof l&&"undefined"==typeof u)break;if(i(l))s["delete"](l),l=a[p++];else if(r(u))s.insert(u),u=h[c++];else{if("undefined"==typeof l)throw new Error("Cannot compose operations: first operation is too short.");if("undefined"==typeof u)throw new Error("Cannot compose operations: first operation is too long.");if(o(l)&&o(u))l>u?(s.retain(u),l-=u,u=h[c++]):l===u?(s.retain(l),l=a[p++],u=h[c++]):(s.retain(l),u-=l,l=a[p++]);else if(r(l)&&i(u))l.length>-u?(l=l.slice(-u),u=h[c++]):l.length===-u?(l=a[p++],u=h[c++]):(u+=l.length,l=a[p++]);else if(r(l)&&o(u))l.length>u?(s.insert(l.slice(0,u)),l=l.slice(u),u=h[c++]):l.length===u?(s.insert(l),l=a[p++],u=h[c++]):(s.insert(l),u-=l.length,l=a[p++]);else{if(!o(l)||!i(u))throw new Error("This shouldn't happen: op1: "+JSON.stringify(l)+", op2: "+JSON.stringify(u));l>-u?(s["delete"](u),l+=u,u=h[c++]):l===-u?(s["delete"](u),l=a[p++],u=h[c++]):(s["delete"](l),u+=l,l=a[p++])}}}return s},t.prototype.shouldBeComposedWith=function(t){if(this.isNoop()||t.isNoop())return!0;var o=n(this),s=n(t),a=e(this),h=e(t);return!(!a||!h)&&(r(a)&&r(h)?o+a.length===s:!(!i(a)||!i(h))&&(s-h===o||o===s))},t.prototype.shouldBeComposedWithInverted=function(t){if(this.isNoop()||t.isNoop())return!0;var o=n(this),s=n(t),a=e(this),h=e(t);return!(!a||!h)&&(r(a)&&r(h)?o+a.length===s||o===s:!(!i(a)||!i(h))&&s-h===o)},t.transform=function(e,n){if(e.baseLength!==n.baseLength)throw new Error("Both operations have to have the same base length");for(var s=new t,a=new t,h=e.ops,p=n.ops,c=0,l=0,u=h[c++],f=p[l++];;){if("undefined"==typeof u&&"undefined"==typeof f)break;if(r(u))s.insert(u),a.retain(u.length),u=h[c++];else if(r(f))s.retain(f.length),a.insert(f),f=p[l++];else{if("undefined"==typeof u)throw new Error("Cannot compose operations: first operation is too short.");if("undefined"==typeof f)throw new Error("Cannot compose operations: first operation is too long.");var d;if(o(u)&&o(f))u>f?(d=f,u-=f,f=p[l++]):u===f?(d=f,u=h[c++],f=p[l++]):(d=u,f-=u,u=h[c++]),s.retain(d),a.retain(d);else if(i(u)&&i(f))-u>-f?(u-=f,f=p[l++]):u===f?(u=h[c++],f=p[l++]):(f-=u,u=h[c++]);else if(i(u)&&o(f))-u>f?(d=f,u+=f,f=p[l++]):-u===f?(d=f,u=h[c++],f=p[l++]):(d=-u,f+=u,u=h[c++]),s["delete"](d);else{if(!o(u)||!i(f))throw new Error("The two operations aren't compatible");u>-f?(d=-f,u+=f,f=p[l++]):u===-f?(d=u,u=h[c++],f=p[l++]):(d=u,f+=u,u=h[c++]),a["delete"](d)}}}return[s,a]},t}(),"object"==typeof module&&(module.exports=ot.TextOperation),"undefined"==typeof ot)var ot={};if(ot.Selection=function(t){"use strict";function e(t,e){this.anchor=t,this.head=e}function n(t){this.ranges=t||[]}var o=t.ot?t.ot.TextOperation:require("./text-operation");return e.fromJSON=function(t){return new e(t.anchor,t.head)},e.prototype.equals=function(t){return this.anchor===t.anchor&&this.head===t.head},e.prototype.isEmpty=function(){return this.anchor===this.head},e.prototype.transform=function(t){function n(e){for(var n=e,r=t.ops,i=0,s=t.ops.length;i<s&&(o.isRetain(r[i])?e-=r[i]:o.isInsert(r[i])?n+=r[i].length:(n-=Math.min(e,-r[i]),e+=r[i]),!(e<0));i++);return n}var r=n(this.anchor);return this.anchor===this.head?new e(r,r):new e(r,n(this.head))},n.Range=e,n.createCursor=function(t){return new n([new e(t,t)])},n.fromJSON=function(t){for(var o=t.ranges||t,r=0,i=[];r<o.length;r++)i[r]=e.fromJSON(o[r]);return new n(i)},n.prototype.equals=function(t){if(this.position!==t.position)return!1;if(this.ranges.length!==t.ranges.length)return!1;for(var e=0;e<this.ranges.length;e++)if(!this.ranges[e].equals(t.ranges[e]))return!1;return!0},n.prototype.somethingSelected=function(){for(var t=0;t<this.ranges.length;t++)if(!this.ranges[t].isEmpty())return!0;return!1},n.prototype.compose=function(t){return t},n.prototype.transform=function(t){for(var e=0,o=[];e<this.ranges.length;e++)o[e]=this.ranges[e].transform(t);return new n(o)},n}(this),"object"==typeof module&&(module.exports=ot.Selection),"undefined"==typeof ot)var ot={};if(ot.WrappedOperation=function(t){"use strict";function e(t,e){this.wrapped=t,this.meta=e}function n(t,e){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])}function o(t,e){if(t&&"object"==typeof t){if("function"==typeof t.compose)return t.compose(e);var o={};return n(t,o),n(e,o),o}return e}function r(t,e){return t&&"object"==typeof t&&"function"==typeof t.transform?t.transform(e):t}return e.prototype.apply=function(){return this.wrapped.apply.apply(this.wrapped,arguments)},e.prototype.invert=function(){var t=this.meta;return new e(this.wrapped.invert.apply(this.wrapped,arguments),t&&"object"==typeof t&&"function"==typeof t.invert?t.invert.apply(t,arguments):t)},e.prototype.compose=function(t){return new e(this.wrapped.compose(t.wrapped),o(this.meta,t.meta))},e.transform=function(t,n){var o=t.wrapped.constructor.transform,i=o(t.wrapped,n.wrapped);return[new e(i[0],r(t.meta,n.wrapped)),new e(i[1],r(n.meta,t.wrapped))]},e}(this),"object"==typeof module&&(module.exports=ot.WrappedOperation),"undefined"==typeof ot)var ot={};if(ot.UndoManager=function(){"use strict";function t(t){this.maxItems=t||50,this.state=n,this.dontCompose=!1,this.undoStack=[],this.redoStack=[]}function e(t,e){for(var n=[],o=e.constructor,r=t.length-1;r>=0;r--){var i=o.transform(t[r],e);"function"==typeof i[0].isNoop&&i[0].isNoop()||n.push(i[0]),e=i[1]}return n.reverse()}var n="normal",o="undoing",r="redoing";return t.prototype.add=function(t,e){if(this.state===o)this.redoStack.push(t),this.dontCompose=!0;else if(this.state===r)this.undoStack.push(t),this.dontCompose=!0;else{var n=this.undoStack;!this.dontCompose&&e&&n.length>0?n.push(t.compose(n.pop())):(n.push(t),n.length>this.maxItems&&n.shift()),this.dontCompose=!1,this.redoStack=[]}},t.prototype.transform=function(t){this.undoStack=e(this.undoStack,t),this.redoStack=e(this.redoStack,t)},t.prototype.performUndo=function(t){if(this.state=o,0===this.undoStack.length)throw new Error("undo not possible");t(this.undoStack.pop()),this.state=n},t.prototype.performRedo=function(t){if(this.state=r,0===this.redoStack.length)throw new Error("redo not possible");t(this.redoStack.pop()),this.state=n},t.prototype.canUndo=function(){return 0!==this.undoStack.length},t.prototype.canRedo=function(){return 0!==this.redoStack.length},t.prototype.isUndoing=function(){return this.state===o},t.prototype.isRedoing=function(){return this.state===r},t}(),"object"==typeof module&&(module.exports=ot.UndoManager),"undefined"==typeof ot)var ot={};ot.Client=function(t){"use strict";function e(t){this.revision=t,this.setState(a)}function n(){}function o(t){this.outstanding=t}function r(t,e){this.outstanding=t,this.buffer=e}function i(t,e,n){this.acknowlaged=t,this.client=e,this.revision=n}function s(t,e,n,o){this.acknowlaged=t,this.buffer=e,this.client=n,this.revision=o}e.prototype.setState=function(t){this.state=t},e.prototype.applyClient=function(t){this.setState(this.state.applyClient(this,t))},e.prototype.applyServer=function(t,e){this.setState(this.state.applyServer(this,t,e))},e.prototype.applyOperations=function(t,e){this.setState(this.state.applyOperations(this,t,e))},e.prototype.serverAck=function(t){this.setState(this.state.serverAck(this,t))},e.prototype.serverReconnect=function(){"function"==typeof this.state.resend&&this.state.resend(this)},e.prototype.transformSelection=function(t){return this.state.transformSelection(t)},e.prototype.sendOperation=function(t,e){throw new Error("sendOperation must be defined in child class")},e.prototype.applyOperation=function(t){throw new Error("applyOperation must be defined in child class")},e.Synchronized=n,n.prototype.applyClient=function(t,e){return t.sendOperation(t.revision,e),new o(e)},n.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");return t.revision=e,t.applyOperation(n),this},n.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},n.prototype.transformSelection=function(t){return t};var a=new n;return e.AwaitingConfirm=o,o.prototype.applyClient=function(t,e){return new r(this.outstanding,e)},o.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");t.revision=e;var r=n.constructor.transform(this.outstanding,n);return t.applyOperation(r[1]),new o(r[0])},o.prototype.serverAck=function(t,e){return e-t.revision>1?new i(this.outstanding,t,e).getOperations():(t.revision=e,a)},o.prototype.transformSelection=function(t){return t.transform(this.outstanding)},o.prototype.resend=function(t){t.sendOperation(t.revision,this.outstanding)},e.AwaitingWithBuffer=r,r.prototype.applyClient=function(t,e){var n=this.buffer.compose(e);return new r(this.outstanding,n)},r.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");t.revision=e;var o=n.constructor.transform,i=o(this.outstanding,n),s=o(this.buffer,i[1]);return t.applyOperation(s[1]),new r(i[0],s[0])},r.prototype.serverAck=function(t,e){return e-t.revision>1?new s(this.outstanding,this.buffer,t,e).getOperations():(t.revision=e,t.sendOperation(t.revision,this.buffer),new o(this.buffer))},r.prototype.transformSelection=function(t){return t.transform(this.outstanding).transform(this.buffer)},r.prototype.resend=function(t){t.sendOperation(t.revision,this.outstanding)},e.Stale=i,i.prototype.applyClient=function(t,e){return new s(this.acknowlaged,e,t,this.revision)},i.prototype.applyServer=function(t,e,n){throw new Error("Ignored server-side change.")},i.prototype.applyOperations=function(t,e,n){for(var o=this.acknowlaged.constructor.transform,r=0;r<n.length;r++){var i=ot.TextOperation.fromJSON(n[r]),s=o(this.acknowlaged,i);t.applyOperation(s[1]),this.acknowlaged=s[0]}return t.revision=this.revision,a},i.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},i.prototype.transformSelection=function(t){return t},i.prototype.getOperations=function(){return this.client.getOperations(this.client.revision,this.revision-1),this},e.StaleWithBuffer=s,s.prototype.applyClient=function(t,e){var n=this.buffer.compose(e);return new s(this.acknowlaged,n,t,this.revision)},s.prototype.applyServer=function(t,e,n){throw new Error("Ignored server-side change.")},s.prototype.applyOperations=function(t,e,n){for(var r=this.acknowlaged.constructor.transform,i=0;i<n.length;i++){var s=ot.TextOperation.fromJSON(n[i]),a=r(this.acknowlaged,s),h=r(this.buffer,a[1]);t.applyOperation(h[1]),this.acknowlaged=a[0],this.buffer=h[0]}return t.revision=this.revision,t.sendOperation(t.revision,this.buffer),new o(this.buffer)},s.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},s.prototype.transformSelection=function(t){return t},s.prototype.getOperations=function(){return this.client.getOperations(this.client.revision,this.revision-1),this},e}(this),"object"==typeof module&&(module.exports=ot.Client),ot.CodeMirrorAdapter=function(t){"use strict";function e(t){this.cm=t,this.ignoreNextChange=!1,this.changeInProgress=!1,this.selectionChanged=!1,a(this,"onChanges"),a(this,"onChange"),a(this,"onCursorActivity"),a(this,"onFocus"),a(this,"onBlur"),t.on("changes",this.onChanges),t.on("change",this.onChange),t.on("cursorActivity",this.onCursorActivity),t.on("focus",this.onFocus),t.on("blur",this.onBlur)}function n(t,e){return t.line<e.line?-1:t.line>e.line?1:t.ch<e.ch?-1:t.ch>e.ch?1:0}function o(t,e){return n(t,e)<=0}function r(t,e){return o(t,e)?t:e}function i(t,e){return o(t,e)?e:t}function s(t){return t.indexFromPos({line:t.lastLine(),ch:0})+t.getLine(t.lastLine()).length}function a(t,e){var n=t[e];t[e]=function(){n.apply(t,arguments)}}var h=ot.TextOperation,p=ot.Selection;e.prototype.detach=function(){this.cm.off("changes",this.onChanges),this.cm.off("change",this.onChange),this.cm.off("cursorActivity",this.onCursorActivity),this.cm.off("focus",this.onFocus),this.cm.off("blur",this.onBlur)},e.operationFromCodeMirrorChanges=function(t,e){function n(t){return t[t.length-1]}function r(t){if(0===t.length)return 0;for(var e=0,n=0;n<t.length;n++)e+=t[n].length;return e+t.length-1}function i(t,e){return function(i){return o(i,e.from)?t(i):o(e.to,i)?t({line:i.line+e.text.length-1-(e.to.line-e.from.line),ch:e.to.line<i.line?i.ch:e.text.length<=1?i.ch-(e.to.ch-e.from.ch)+r(e.text):i.ch-e.to.ch+n(e.text).length})+r(e.removed)-r(e.text):e.from.line===i.line?t(e.from)+i.ch-e.from.ch:t(e.from)+r(e.removed.slice(0,i.line-e.from.line))+1+i.ch}}for(var a=s(e),p=(new h).retain(a),c=(new h).retain(a),l=function(t){return e.indexFromPos(t)},u=t.length-1;u>=0;u--){var f=t[u];l=i(l,f);var d=l(f.from),g=a-d-r(f.text);p=(new h).retain(d)["delete"](r(f.removed)).insert(f.text.join("\n")).retain(g).compose(p),c=c.compose((new h).retain(d)["delete"](r(f.text)).insert(f.removed.join("\n")).retain(g)),a+=r(f.removed)-r(f.text)}return[p,c]},e.operationFromCodeMirrorChange=e.operationFromCodeMirrorChanges,e.applyOperationToCodeMirror=function(t,e){e.operation(function(){for(var n=t.ops,o=0,r=0,i=n.length;r<i;r++){var s=n[r];if(h.isRetain(s))o+=s;else if(h.isInsert(s))e.replaceRange(s,e.posFromIndex(o),null,"ignoreHistory"),o+=s.length;else if(h.isDelete(s)){var a=e.posFromIndex(o),p=e.posFromIndex(o-s);e.replaceRange("",a,p,"ignoreHistory")}}})},e.prototype.registerCallbacks=function(t){this.callbacks=t},e.prototype.onChange=function(){this.changeInProgress=!0},e.prototype.onChanges=function(t,n){if(!this.ignoreNextChange){var o=e.operationFromCodeMirrorChanges(n,this.cm);this.trigger("change",o[0],o[1])}this.selectionChanged&&this.trigger("selectionChange"),this.changeInProgress=!1,this.ignoreNextChange=!1},e.prototype.onCursorActivity=e.prototype.onFocus=function(){this.changeInProgress?this.selectionChanged=!0:this.trigger("selectionChange")},e.prototype.onBlur=function(){this.cm.somethingSelected()||this.trigger("blur")},e.prototype.getValue=function(){return this.cm.getValue()},e.prototype.getSelection=function(){for(var t=this.cm,e=t.listSelections(),n=[],o=0;o<e.length;o++)n[o]=new p.Range(t.indexFromPos(e[o].anchor),t.indexFromPos(e[o].head));return new p(n)},e.prototype.setSelection=function(t){for(var e=[],n=0;t&&n<t.ranges.length;n++){var o=t.ranges[n];e[n]={anchor:this.cm.posFromIndex(o.anchor),head:this.cm.posFromIndex(o.head)}}this.cm.setSelections(e)};var c=function(){var t={},e=document.createElement("style");document.documentElement.getElementsByTagName("head")[0].appendChild(e);var n=e.sheet;return function(e){t[e]||(t[e]=!0,n.insertRule(e,(n.cssRules||n.rules).length))}}();return e.prototype.setOtherCursor=function(t,e,n){var o=this.cm.posFromIndex(t),r=(this.cm.cursorCoords(o),document.createElement("span"));return r.className="other-client",r.style.display="none",r.setAttribute("data-clientid",n),this.cm.setBookmark(o,{widget:r,insertLeft:!0})},e.prototype.setOtherSelectionRange=function(t,e,n){var o=/^#([0-9a-fA-F]{6})$/.exec(e);if(!o)throw new Error("only six-digit hex colors are allowed.");var s="selection-"+o[1],a=hex2rgb(e),h="."+s+" { background: rgba("+a.red+","+a.green+","+a.blue+",0.2); }";c(h);var p=this.cm.posFromIndex(t.anchor),l=this.cm.posFromIndex(t.head);return this.cm.markText(r(p,l),i(p,l),{className:s})},e.prototype.setOtherSelection=function(t,e,n){for(var o=[],r=0;r<t.ranges.length;r++){var i=t.ranges[r];i.isEmpty()||(o[r]=this.setOtherSelectionRange(i,e,n))}return{clear:function(){for(var t=0;t<o.length;t++)o[t].clear()}}},e.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},e.prototype.applyOperation=function(t){this.ignoreNextChange=!0,e.applyOperationToCodeMirror(t,this.cm)},e.prototype.registerUndo=function(t){this.cm.undo=t},e.prototype.registerRedo=function(t){this.cm.redo=t},e}(this),ot.SocketIOAdapter=function(){"use strict";function t(t){this.socket=t;var e=this;t.on("client_left",function(t){e.trigger("client_left",t)}),t.on("set_name",function(t,n){e.trigger("set_name",t,n)}),t.on("set_color",function(t,n){e.trigger("set_color",t,n)}),t.on("ack",function(t){e.trigger("ack",t)}),t.on("operation",function(t,n,o,r){e.trigger("operation",n,o),e.trigger("selection",t,r)}),t.on("operations",function(t,n){n=LZString.decompressFromUTF16(n),n=JSON.parse(n),e.trigger("operations",t,n)}),t.on("selection",function(t,n){e.trigger("selection",t,n)}),t.on("reconnect",function(){e.trigger("reconnect")})}return t.prototype.sendOperation=function(t,e,n){e=LZString.compressToUTF16(JSON.stringify(e)),this.socket.emit("operation",t,e,n)},t.prototype.sendSelection=function(t){this.socket.emit("selection",t)},t.prototype.getOperations=function(t,e){this.socket.emit("get_operations",t,e)},t.prototype.registerCallbacks=function(t){this.callbacks=t},t.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},t}(),ot.AjaxAdapter=function(){"use strict";function t(t,e,n){"/"!==t[t.length-1]&&(t+="/"),this.path=t,this.ownUserName=e,this.majorRevision=n.major||0,this.minorRevision=n.minor||0,this.poll()}return t.prototype.renderRevisionPath=function(){return"revision/"+this.majorRevision+"-"+this.minorRevision},t.prototype.handleResponse=function(t){var e,n=t.operations;for(e=0;e<n.length;e++)n[e].user===this.ownUserName?this.trigger("ack"):this.trigger("operation",n[e].operation);n.length>0&&(this.majorRevision+=n.length,this.minorRevision=0);var o=t.events;if(o){for(e=0;e<o.length;e++){var r=o[e].user;if(r!==this.ownUserName)switch(o[e].event){case"joined":this.trigger("set_name",r,r);break;case"left":this.trigger("client_left",r);break;case"selection":this.trigger("selection",r,o[e].selection)}}this.minorRevision+=o.length}var i=t.users;i&&(delete i[this.ownUserName],this.trigger("clients",i)),t.revision&&(this.majorRevision=t.revision.major,this.minorRevision=t.revision.minor)},t.prototype.poll=function(){var t=this;$.ajax({url:this.path+this.renderRevisionPath(),type:"GET",dataType:"json",timeout:5e3,success:function(e){t.handleResponse(e),t.poll()},error:function(){setTimeout(function(){t.poll()},500)}})},t.prototype.sendOperation=function(t,e,n){if(t!==this.majorRevision)throw new Error("Revision numbers out of sync");var o=this;$.ajax({url:this.path+this.renderRevisionPath(),type:"POST",data:JSON.stringify({operation:e,selection:n}),contentType:"application/json",processData:!1,success:function(t){},error:function(){setTimeout(function(){o.sendOperation(t,e,n)},500)}})},t.prototype.sendSelection=function(t){$.ajax({url:this.path+this.renderRevisionPath()+"/selection",type:"POST",data:JSON.stringify(t),contentType:"application/json",processData:!1,timeout:1e3})},t.prototype.registerCallbacks=function(t){this.callbacks=t},t.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},t}(),ot.EditorClient=function(){"use strict";function t(t,e){this.selectionBefore=t,this.selectionAfter=e}function e(t,e){this.clientId=t,this.selection=e}function n(t,e,n,o,r,i){this.id=t,this.listEl=e,this.editorAdapter=n,this.name=o,this.color=r,this.li=document.createElement("li"),o&&(this.li.textContent=o,this.listEl.appendChild(this.li)),r?this.setForceColor(r):this.setColor(o?s(o):Math.random()),i&&this.updateSelection(i)}function o(t,e,n,o){c.call(this,t),this.serverAdapter=n,this.editorAdapter=o,this.undoManager=new u,this.initializeClientList(),this.initializeClients(e);var r=this;this.editorAdapter.registerCallbacks({change:function(t,e){r.onChange(t,e)},selectionChange:function(){r.onSelectionChange()},blur:function(){r.onBlur()}}),this.editorAdapter.registerUndo(function(){r.undo()}),this.editorAdapter.registerRedo(function(){r.redo()}),this.serverAdapter.registerCallbacks({client_left:function(t){r.onClientLeft(t)},set_name:function(t,e){r.getClientObject(t).setName(e)},set_color:function(t,e){r.getClientObject(t).setForceColor(e)},ack:function(t){r.serverAck(t)},operation:function(t,e){r.applyServer(t,f.fromJSON(e))},operations:function(t,e){r.applyOperations(t,e)},selection:function(t,e){e?r.getClientObject(t).updateSelection(r.transformSelection(l.fromJSON(e))):r.getClientObject(t).removeSelection()},clients:function(t){var e;for(e in r.clients)r.clients.hasOwnProperty(e)&&!t.hasOwnProperty(e)&&r.onClientLeft(e);for(e in t)if(t.hasOwnProperty(e)){var n=r.getClientObject(e);t[e].name&&n.setName(t[e].name);var o=t[e].selection;o?r.clients[e].updateSelection(r.transformSelection(l.fromJSON(o))):r.clients[e].removeSelection()}},reconnect:function(){r.serverReconnect()}})}function r(t,e,n){function o(t){var e=Math.round(255*t).toString(16);return 1===e.length?"0"+e:e}return"#"+o(t)+o(e)+o(n)}function i(t,e,n){if(0===e)return r(n,n,n);var o=n<.5?n*(1+e):n+e-e*n,i=2*n-o,s=function(t){return t<0&&(t+=1),t>1&&(t-=1),6*t<1?i+6*(o-i)*t:2*t<1?o:3*t<2?i+6*(o-i)*(2/3-t):i};return r(s(t+1/3),s(t),s(t-1/3))}function s(t){for(var e=1,n=0;n<t.length;n++)e=17*(e+t.charCodeAt(n))%360;return e/360}function a(t,e){function n(){}n.prototype=e.prototype,t.prototype=new n,t.prototype.constructor=t}function h(t){return t[t.length-1]}function p(t){t.parentNode&&t.parentNode.removeChild(t)}var c=ot.Client,l=ot.Selection,u=ot.UndoManager,f=ot.TextOperation,d=ot.WrappedOperation;return t.prototype.invert=function(){return new t(this.selectionAfter,this.selectionBefore)},t.prototype.compose=function(e){return new t(this.selectionBefore,e.selectionAfter)},t.prototype.transform=function(e){return new t(this.selectionBefore?this.selectionBefore.transform(e):null,this.selectionAfter?this.selectionAfter.transform(e):null)},e.fromJSON=function(t){return new e(t.clientId,t.selection&&l.fromJSON(t.selection))},e.prototype.transform=function(t){return new e(this.clientId,this.selection&&this.selection.transform(t))},n.prototype.setColor=function(t){this.hue=t,this.color=i(t,.75,.5),this.lightColor=i(t,.5,.9),this.li&&(this.li.style.color=this.color)},n.prototype.setForceColor=function(t){this.hue=null,this.color=t,this.lightColor=t,this.li&&(this.li.style.color=this.color)},n.prototype.setName=function(t){this.name!==t&&(this.name=t,this.li.textContent=t,this.li.parentNode||this.listEl.appendChild(this.li),this.setColor(s(t)))},n.prototype.updateSelection=function(t){this.removeSelection(),this.selection=t,this.mark=this.editorAdapter.setOtherSelection(t,t.position===t.selectionEnd?this.color:this.lightColor,this.id)},n.prototype.remove=function(){this.li&&p(this.li),this.removeSelection()},n.prototype.removeSelection=function(){this.mark&&(this.mark.clear(),this.mark=null)},a(o,c),o.prototype.addClient=function(t,e){this.clients[t]=new n(t,this.clientListEl,this.editorAdapter,e.name||t,e.color||null,e.selection?l.fromJSON(e.selection):null)},o.prototype.initializeClients=function(t){this.clients={};for(var e in t)t.hasOwnProperty(e)&&this.addClient(e,t[e])},o.prototype.getClientObject=function(t){var e=this.clients[t];return e?e:this.clients[t]=new n(t,this.clientListEl,this.editorAdapter)},o.prototype.onClientLeft=function(t){var e=this.clients[t];e&&(e.remove(),delete this.clients[t])},o.prototype.initializeClientList=function(){this.clientListEl=document.createElement("ul")},o.prototype.applyUnredo=function(t){this.undoManager.add(t.invert(this.editorAdapter.getValue())),this.editorAdapter.applyOperation(t.wrapped),this.selection=t.meta.selectionAfter,this.editorAdapter.setSelection(this.selection),this.applyClient(t.wrapped)},o.prototype.undo=function(){var t=this;this.undoManager.canUndo()&&this.undoManager.performUndo(function(e){t.applyUnredo(e)})},o.prototype.redo=function(){var t=this;this.undoManager.canRedo()&&this.undoManager.performRedo(function(e){t.applyUnredo(e)})},o.prototype.onChange=function(e,n){var o=this.selection;this.updateSelection();var r=new t(o,this.selection),i=(new d(e,r),this.undoManager.undoStack.length>0&&n.shouldBeComposedWithInverted(h(this.undoManager.undoStack).wrapped)),s=new t(this.selection,o);this.undoManager.add(new d(n,s),i),this.applyClient(e)},o.prototype.updateSelection=function(){this.selection=this.editorAdapter.getSelection()},o.prototype.onSelectionChange=function(){var t=this.selection;this.updateSelection(),t&&this.selection.equals(t)||this.sendSelection(this.selection)},o.prototype.onBlur=function(){this.selection=null,this.sendSelection(null)},o.prototype.sendSelection=function(t){this.state instanceof c.AwaitingWithBuffer||this.serverAdapter.sendSelection(t)},o.prototype.sendOperation=function(t,e){this.serverAdapter.sendOperation(t,e.toJSON(),this.selection)},o.prototype.getOperations=function(t,e){this.serverAdapter.getOperations(t,e)},o.prototype.applyOperation=function(t){this.editorAdapter.applyOperation(t),this.updateSelection(),this.undoManager.transform(new d(t,null))},o}(); \ No newline at end of file
+function hex2rgb(t){if("#"==t[0]&&(t=t.substr(1)),3==t.length){var e=t;t="",e=/^([a-f0-9])([a-f0-9])([a-f0-9])$/i.exec(e).slice(1);for(var n=0;n<3;n++)t+=e[n]+e[n]}var o=/^([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i.exec(t).slice(1);return{red:parseInt(o[0],16),green:parseInt(o[1],16),blue:parseInt(o[2],16)}}if("undefined"==typeof ot)var ot={};if(ot.TextOperation=function(){"use strict";function t(){return this&&this.constructor===t?(this.ops=[],this.baseLength=0,void(this.targetLength=0)):new t}function e(e,n){var o=e.ops,r=t.isRetain;switch(o.length){case 1:return o[0];case 2:return r(o[0])?o[1]:r(o[1])?o[0]:null;case 3:if(r(o[0])&&r(o[2]))return o[1]}return null}function n(t){return o(t.ops[0])?t.ops[0]:0}t.prototype.equals=function(t){if(this.baseLength!==t.baseLength)return!1;if(this.targetLength!==t.targetLength)return!1;if(this.ops.length!==t.ops.length)return!1;for(var e=0;e<this.ops.length;e++)if(this.ops[e]!==t.ops[e])return!1;return!0};var o=t.isRetain=function(t){return"number"==typeof t&&t>0},r=t.isInsert=function(t){return"string"==typeof t},i=t.isDelete=function(t){return"number"==typeof t&&t<0};return t.prototype.retain=function(t){if("number"!=typeof t)throw new Error("retain expects an integer");return 0===t?this:(this.baseLength+=t,this.targetLength+=t,o(this.ops[this.ops.length-1])?this.ops[this.ops.length-1]+=t:this.ops.push(t),this)},t.prototype.insert=function(t){if("string"!=typeof t)throw new Error("insert expects a string");if(""===t)return this;this.targetLength+=t.length;var e=this.ops;return r(e[e.length-1])?e[e.length-1]+=t:i(e[e.length-1])?r(e[e.length-2])?e[e.length-2]+=t:(e[e.length]=e[e.length-1],e[e.length-2]=t):e.push(t),this},t.prototype["delete"]=function(t){if("string"==typeof t&&(t=t.length),"number"!=typeof t)throw new Error("delete expects an integer or a string");return 0===t?this:(t>0&&(t=-t),this.baseLength-=t,i(this.ops[this.ops.length-1])?this.ops[this.ops.length-1]+=t:this.ops.push(t),this)},t.prototype.isNoop=function(){return 0===this.ops.length||1===this.ops.length&&o(this.ops[0])},t.prototype.toString=function(){var t=Array.prototype.map||function(t){for(var e=this,n=[],o=0,r=e.length;o<r;o++)n[o]=t(e[o]);return n};return t.call(this.ops,function(t){return o(t)?"retain "+t:r(t)?"insert '"+t+"'":"delete "+-t}).join(", ")},t.prototype.toJSON=function(){return this.ops},t.fromJSON=function(e){for(var n=new t,s=0,a=e.length;s<a;s++){var h=e[s];if(o(h))n.retain(h);else if(r(h))n.insert(h);else{if(!i(h))throw new Error("unknown operation: "+JSON.stringify(h));n["delete"](h)}}return n},t.prototype.apply=function(t){var e=this;if(t.length!==e.baseLength)throw new Error("The operation's base length must be equal to the string's length.");for(var n=[],i=0,s=0,a=this.ops,h=0,p=a.length;h<p;h++){var c=a[h];if(o(c)){if(s+c>t.length)throw new Error("Operation can't retain more characters than are left in the string.");n[i++]=t.slice(s,s+c),s+=c}else r(c)?n[i++]=c:s-=c}if(s!==t.length)throw new Error("The operation didn't operate on the whole string.");return n.join("")},t.prototype.invert=function(e){for(var n=0,i=new t,s=this.ops,a=0,h=s.length;a<h;a++){var p=s[a];o(p)?(i.retain(p),n+=p):r(p)?i["delete"](p.length):(i.insert(e.slice(n,n-p)),n-=p)}return i},t.prototype.compose=function(e){var n=this;if(n.targetLength!==e.baseLength)throw new Error("The base length of the second operation has to be the target length of the first operation");for(var s=new t,a=n.ops,h=e.ops,p=0,c=0,l=a[p++],u=h[c++];;){if("undefined"==typeof l&&"undefined"==typeof u)break;if(i(l))s["delete"](l),l=a[p++];else if(r(u))s.insert(u),u=h[c++];else{if("undefined"==typeof l)throw new Error("Cannot compose operations: first operation is too short.");if("undefined"==typeof u)throw new Error("Cannot compose operations: first operation is too long.");if(o(l)&&o(u))l>u?(s.retain(u),l-=u,u=h[c++]):l===u?(s.retain(l),l=a[p++],u=h[c++]):(s.retain(l),u-=l,l=a[p++]);else if(r(l)&&i(u))l.length>-u?(l=l.slice(-u),u=h[c++]):l.length===-u?(l=a[p++],u=h[c++]):(u+=l.length,l=a[p++]);else if(r(l)&&o(u))l.length>u?(s.insert(l.slice(0,u)),l=l.slice(u),u=h[c++]):l.length===u?(s.insert(l),l=a[p++],u=h[c++]):(s.insert(l),u-=l.length,l=a[p++]);else{if(!o(l)||!i(u))throw new Error("This shouldn't happen: op1: "+JSON.stringify(l)+", op2: "+JSON.stringify(u));l>-u?(s["delete"](u),l+=u,u=h[c++]):l===-u?(s["delete"](u),l=a[p++],u=h[c++]):(s["delete"](l),u+=l,l=a[p++])}}}return s},t.prototype.shouldBeComposedWith=function(t){if(this.isNoop()||t.isNoop())return!0;var o=n(this),s=n(t),a=e(this),h=e(t);return!(!a||!h)&&(r(a)&&r(h)?o+a.length===s:!(!i(a)||!i(h))&&(s-h===o||o===s))},t.prototype.shouldBeComposedWithInverted=function(t){if(this.isNoop()||t.isNoop())return!0;var o=n(this),s=n(t),a=e(this),h=e(t);return!(!a||!h)&&(r(a)&&r(h)?o+a.length===s||o===s:!(!i(a)||!i(h))&&s-h===o)},t.transform=function(e,n){if(e.baseLength!==n.baseLength)throw new Error("Both operations have to have the same base length");for(var s=new t,a=new t,h=e.ops,p=n.ops,c=0,l=0,u=h[c++],f=p[l++];;){if("undefined"==typeof u&&"undefined"==typeof f)break;if(r(u))s.insert(u),a.retain(u.length),u=h[c++];else if(r(f))s.retain(f.length),a.insert(f),f=p[l++];else{if("undefined"==typeof u)throw new Error("Cannot compose operations: first operation is too short.");if("undefined"==typeof f)throw new Error("Cannot compose operations: first operation is too long.");var d;if(o(u)&&o(f))u>f?(d=f,u-=f,f=p[l++]):u===f?(d=f,u=h[c++],f=p[l++]):(d=u,f-=u,u=h[c++]),s.retain(d),a.retain(d);else if(i(u)&&i(f))-u>-f?(u-=f,f=p[l++]):u===f?(u=h[c++],f=p[l++]):(f-=u,u=h[c++]);else if(i(u)&&o(f))-u>f?(d=f,u+=f,f=p[l++]):-u===f?(d=f,u=h[c++],f=p[l++]):(d=-u,f+=u,u=h[c++]),s["delete"](d);else{if(!o(u)||!i(f))throw new Error("The two operations aren't compatible");u>-f?(d=-f,u+=f,f=p[l++]):u===-f?(d=u,u=h[c++],f=p[l++]):(d=u,f+=u,u=h[c++]),a["delete"](d)}}}return[s,a]},t}(),"object"==typeof module&&(module.exports=ot.TextOperation),"undefined"==typeof ot)var ot={};if(ot.Selection=function(t){"use strict";function e(t,e){this.anchor=t,this.head=e}function n(t){this.ranges=t||[]}var o=t.ot?t.ot.TextOperation:require("./text-operation");return e.fromJSON=function(t){return new e(t.anchor,t.head)},e.prototype.equals=function(t){return this.anchor===t.anchor&&this.head===t.head},e.prototype.isEmpty=function(){return this.anchor===this.head},e.prototype.transform=function(t){function n(e){for(var n=e,r=t.ops,i=0,s=t.ops.length;i<s&&(o.isRetain(r[i])?e-=r[i]:o.isInsert(r[i])?n+=r[i].length:(n-=Math.min(e,-r[i]),e+=r[i]),!(e<0));i++);return n}var r=n(this.anchor);return this.anchor===this.head?new e(r,r):new e(r,n(this.head))},n.Range=e,n.createCursor=function(t){return new n([new e(t,t)])},n.fromJSON=function(t){for(var o=t.ranges||t,r=0,i=[];r<o.length;r++)i[r]=e.fromJSON(o[r]);return new n(i)},n.prototype.equals=function(t){if(this.position!==t.position)return!1;if(this.ranges.length!==t.ranges.length)return!1;for(var e=0;e<this.ranges.length;e++)if(!this.ranges[e].equals(t.ranges[e]))return!1;return!0},n.prototype.somethingSelected=function(){for(var t=0;t<this.ranges.length;t++)if(!this.ranges[t].isEmpty())return!0;return!1},n.prototype.compose=function(t){return t},n.prototype.transform=function(t){for(var e=0,o=[];e<this.ranges.length;e++)o[e]=this.ranges[e].transform(t);return new n(o)},n}(this),"object"==typeof module&&(module.exports=ot.Selection),"undefined"==typeof ot)var ot={};if(ot.WrappedOperation=function(t){"use strict";function e(t,e){this.wrapped=t,this.meta=e}function n(t,e){for(var n in t)t.hasOwnProperty(n)&&(e[n]=t[n])}function o(t,e){if(t&&"object"==typeof t){if("function"==typeof t.compose)return t.compose(e);var o={};return n(t,o),n(e,o),o}return e}function r(t,e){return t&&"object"==typeof t&&"function"==typeof t.transform?t.transform(e):t}return e.prototype.apply=function(){return this.wrapped.apply.apply(this.wrapped,arguments)},e.prototype.invert=function(){var t=this.meta;return new e(this.wrapped.invert.apply(this.wrapped,arguments),t&&"object"==typeof t&&"function"==typeof t.invert?t.invert.apply(t,arguments):t)},e.prototype.compose=function(t){return new e(this.wrapped.compose(t.wrapped),o(this.meta,t.meta))},e.transform=function(t,n){var o=t.wrapped.constructor.transform,i=o(t.wrapped,n.wrapped);return[new e(i[0],r(t.meta,n.wrapped)),new e(i[1],r(n.meta,t.wrapped))]},e}(this),"object"==typeof module&&(module.exports=ot.WrappedOperation),"undefined"==typeof ot)var ot={};if(ot.UndoManager=function(){"use strict";function t(t){this.maxItems=t||50,this.state=n,this.dontCompose=!1,this.undoStack=[],this.redoStack=[]}function e(t,e){for(var n=[],o=e.constructor,r=t.length-1;r>=0;r--){var i=o.transform(t[r],e);"function"==typeof i[0].isNoop&&i[0].isNoop()||n.push(i[0]),e=i[1]}return n.reverse()}var n="normal",o="undoing",r="redoing";return t.prototype.add=function(t,e){if(this.state===o)this.redoStack.push(t),this.dontCompose=!0;else if(this.state===r)this.undoStack.push(t),this.dontCompose=!0;else{var n=this.undoStack;!this.dontCompose&&e&&n.length>0?n.push(t.compose(n.pop())):(n.push(t),n.length>this.maxItems&&n.shift()),this.dontCompose=!1,this.redoStack=[]}},t.prototype.transform=function(t){this.undoStack=e(this.undoStack,t),this.redoStack=e(this.redoStack,t)},t.prototype.performUndo=function(t){if(this.state=o,0===this.undoStack.length)throw new Error("undo not possible");t(this.undoStack.pop()),this.state=n},t.prototype.performRedo=function(t){if(this.state=r,0===this.redoStack.length)throw new Error("redo not possible");t(this.redoStack.pop()),this.state=n},t.prototype.canUndo=function(){return 0!==this.undoStack.length},t.prototype.canRedo=function(){return 0!==this.redoStack.length},t.prototype.isUndoing=function(){return this.state===o},t.prototype.isRedoing=function(){return this.state===r},t}(),"object"==typeof module&&(module.exports=ot.UndoManager),"undefined"==typeof ot)var ot={};ot.Client=function(t){"use strict";function e(t){this.revision=t,this.setState(a)}function n(){}function o(t){this.outstanding=t}function r(t,e){this.outstanding=t,this.buffer=e}function i(t,e,n){this.acknowlaged=t,this.client=e,this.revision=n}function s(t,e,n,o){this.acknowlaged=t,this.buffer=e,this.client=n,this.revision=o}e.prototype.setState=function(t){this.state=t},e.prototype.applyClient=function(t){this.setState(this.state.applyClient(this,t))},e.prototype.applyServer=function(t,e){this.setState(this.state.applyServer(this,t,e))},e.prototype.applyOperations=function(t,e){this.setState(this.state.applyOperations(this,t,e))},e.prototype.serverAck=function(t){this.setState(this.state.serverAck(this,t))},e.prototype.serverReconnect=function(){"function"==typeof this.state.resend&&this.state.resend(this)},e.prototype.transformSelection=function(t){return this.state.transformSelection(t)},e.prototype.sendOperation=function(t,e){throw new Error("sendOperation must be defined in child class")},e.prototype.applyOperation=function(t){throw new Error("applyOperation must be defined in child class")},e.Synchronized=n,n.prototype.applyClient=function(t,e){return t.sendOperation(t.revision,e),new o(e)},n.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");return t.revision=e,t.applyOperation(n),this},n.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},n.prototype.transformSelection=function(t){return t};var a=new n;return e.AwaitingConfirm=o,o.prototype.applyClient=function(t,e){return new r(this.outstanding,e)},o.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");t.revision=e;var r=n.constructor.transform(this.outstanding,n);return t.applyOperation(r[1]),new o(r[0])},o.prototype.serverAck=function(t,e){return e-t.revision>1?new i(this.outstanding,t,e).getOperations():(t.revision=e,a)},o.prototype.transformSelection=function(t){return t.transform(this.outstanding)},o.prototype.resend=function(t){t.sendOperation(t.revision,this.outstanding)},e.AwaitingWithBuffer=r,r.prototype.applyClient=function(t,e){var n=this.buffer.compose(e);return new r(this.outstanding,n)},r.prototype.applyServer=function(t,e,n){if(e-t.revision>1)throw new Error("Invalid revision.");t.revision=e;var o=n.constructor.transform,i=o(this.outstanding,n),s=o(this.buffer,i[1]);return t.applyOperation(s[1]),new r(i[0],s[0])},r.prototype.serverAck=function(t,e){return e-t.revision>1?new s(this.outstanding,this.buffer,t,e).getOperations():(t.revision=e,t.sendOperation(t.revision,this.buffer),new o(this.buffer))},r.prototype.transformSelection=function(t){return t.transform(this.outstanding).transform(this.buffer)},r.prototype.resend=function(t){t.sendOperation(t.revision,this.outstanding)},e.Stale=i,i.prototype.applyClient=function(t,e){return new s(this.acknowlaged,e,t,this.revision)},i.prototype.applyServer=function(t,e,n){throw new Error("Ignored server-side change.")},i.prototype.applyOperations=function(t,e,n){for(var o=this.acknowlaged.constructor.transform,r=0;r<n.length;r++){var i=ot.TextOperation.fromJSON(n[r]),s=o(this.acknowlaged,i);t.applyOperation(s[1]),this.acknowlaged=s[0]}return t.revision=this.revision,a},i.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},i.prototype.transformSelection=function(t){return t},i.prototype.getOperations=function(){return this.client.getOperations(this.client.revision,this.revision-1),this},e.StaleWithBuffer=s,s.prototype.applyClient=function(t,e){var n=this.buffer.compose(e);return new s(this.acknowlaged,n,t,this.revision)},s.prototype.applyServer=function(t,e,n){throw new Error("Ignored server-side change.")},s.prototype.applyOperations=function(t,e,n){for(var r=this.acknowlaged.constructor.transform,i=0;i<n.length;i++){var s=ot.TextOperation.fromJSON(n[i]),a=r(this.acknowlaged,s),h=r(this.buffer,a[1]);t.applyOperation(h[1]),this.acknowlaged=a[0],this.buffer=h[0]}return t.revision=this.revision,t.sendOperation(t.revision,this.buffer),new o(this.buffer)},s.prototype.serverAck=function(t,e){throw new Error("There is no pending operation.")},s.prototype.transformSelection=function(t){return t},s.prototype.getOperations=function(){return this.client.getOperations(this.client.revision,this.revision-1),this},e}(this),"object"==typeof module&&(module.exports=ot.Client),ot.CodeMirrorAdapter=function(t){"use strict";function e(t){this.cm=t,this.ignoreNextChange=!1,this.changeInProgress=!1,this.selectionChanged=!1,a(this,"onChanges"),a(this,"onChange"),a(this,"onCursorActivity"),a(this,"onFocus"),a(this,"onBlur"),t.on("changes",this.onChanges),t.on("change",this.onChange),t.on("cursorActivity",this.onCursorActivity),t.on("focus",this.onFocus),t.on("blur",this.onBlur)}function n(t,e){return t.line<e.line?-1:t.line>e.line?1:t.ch<e.ch?-1:t.ch>e.ch?1:0}function o(t,e){return n(t,e)<=0}function r(t,e){return o(t,e)?t:e}function i(t,e){return o(t,e)?e:t}function s(t){return t.indexFromPos({line:t.lastLine(),ch:0})+t.getLine(t.lastLine()).length}function a(t,e){var n=t[e];t[e]=function(){n.apply(t,arguments)}}var h=ot.TextOperation,p=ot.Selection;e.prototype.detach=function(){this.cm.off("changes",this.onChanges),this.cm.off("change",this.onChange),this.cm.off("cursorActivity",this.onCursorActivity),this.cm.off("focus",this.onFocus),this.cm.off("blur",this.onBlur)},e.operationFromCodeMirrorChanges=function(t,e){function n(t){return t[t.length-1]}function r(t){if(0===t.length)return 0;for(var e=0,n=0;n<t.length;n++)e+=t[n].length;return e+t.length-1}function i(t,e){return function(i){return o(i,e.from)?t(i):o(e.to,i)?t({line:i.line+e.text.length-1-(e.to.line-e.from.line),ch:e.to.line<i.line?i.ch:e.text.length<=1?i.ch-(e.to.ch-e.from.ch)+r(e.text):i.ch-e.to.ch+n(e.text).length})+r(e.removed)-r(e.text):e.from.line===i.line?t(e.from)+i.ch-e.from.ch:t(e.from)+r(e.removed.slice(0,i.line-e.from.line))+1+i.ch}}for(var a=s(e),p=(new h).retain(a),c=(new h).retain(a),l=function(t){return e.indexFromPos(t)},u=t.length-1;u>=0;u--){var f=t[u];l=i(l,f);var d=l(f.from),g=a-d-r(f.text);p=(new h).retain(d)["delete"](r(f.removed)).insert(f.text.join("\n")).retain(g).compose(p),c=c.compose((new h).retain(d)["delete"](r(f.text)).insert(f.removed.join("\n")).retain(g)),a+=r(f.removed)-r(f.text)}return[p,c]},e.operationFromCodeMirrorChange=e.operationFromCodeMirrorChanges,e.applyOperationToCodeMirror=function(t,e){e.operation(function(){for(var n=t.ops,o=0,r=0,i=n.length;r<i;r++){var s=n[r];if(h.isRetain(s))o+=s;else if(h.isInsert(s))e.replaceRange(s,e.posFromIndex(o),null,"ignoreHistory"),o+=s.length;else if(h.isDelete(s)){var a=e.posFromIndex(o),p=e.posFromIndex(o-s);e.replaceRange("",a,p,"ignoreHistory")}}})},e.prototype.registerCallbacks=function(t){this.callbacks=t},e.prototype.onChange=function(){this.changeInProgress=!0},e.prototype.onChanges=function(t,n){if(!this.ignoreNextChange){var o=e.operationFromCodeMirrorChanges(n,this.cm);this.trigger("change",o[0],o[1])}this.selectionChanged&&this.trigger("selectionChange"),this.changeInProgress=!1,this.ignoreNextChange=!1},e.prototype.onCursorActivity=e.prototype.onFocus=function(){this.changeInProgress?this.selectionChanged=!0:this.trigger("selectionChange")},e.prototype.onBlur=function(){this.cm.somethingSelected()||this.trigger("blur")},e.prototype.getValue=function(){return this.cm.getValue()},e.prototype.getSelection=function(){for(var t=this.cm,e=t.listSelections(),n=[],o=0;o<e.length;o++)n[o]=new p.Range(t.indexFromPos(e[o].anchor),t.indexFromPos(e[o].head));return new p(n)},e.prototype.setSelection=function(t){for(var e=[],n=0;t&&n<t.ranges.length;n++){var o=t.ranges[n];e[n]={anchor:this.cm.posFromIndex(o.anchor),head:this.cm.posFromIndex(o.head)}}this.cm.setSelections(e)};var c=function(){var t={},e=document.createElement("style");document.documentElement.getElementsByTagName("head")[0].appendChild(e);var n=e.sheet;return function(e){t[e]||(t[e]=!0,n.insertRule(e,(n.cssRules||n.rules).length))}}();return e.prototype.setOtherCursor=function(t,e,n){var o=this.cm.posFromIndex(t),r=(this.cm.cursorCoords(o),document.createElement("span"));return r.className="other-client",r.style.display="none",r.setAttribute("data-clientid",n),this.cm.setBookmark(o,{widget:r,insertLeft:!0})},e.prototype.setOtherSelectionRange=function(t,e,n){var o=/^#([0-9a-fA-F]{6})$/.exec(e);if(!o)throw new Error("only six-digit hex colors are allowed.");var s="selection-"+o[1],a=hex2rgb(e),h="."+s+" { background: rgba("+a.red+","+a.green+","+a.blue+",0.2); }";c(h);var p=this.cm.posFromIndex(t.anchor),l=this.cm.posFromIndex(t.head);return this.cm.markText(r(p,l),i(p,l),{className:s})},e.prototype.setOtherSelection=function(t,e,n){for(var o=[],r=0;r<t.ranges.length;r++){var i=t.ranges[r];i.isEmpty()||(o[r]=this.setOtherSelectionRange(i,e,n))}return{clear:function(){for(var t=0;t<o.length;t++)o[t].clear()}}},e.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},e.prototype.applyOperation=function(t){this.ignoreNextChange=!0,e.applyOperationToCodeMirror(t,this.cm)},e.prototype.registerUndo=function(t){this.cm.undo=t},e.prototype.registerRedo=function(t){this.cm.redo=t},e}(this),ot.SocketIOAdapter=function(){"use strict";function t(t){this.socket=t;var e=this;t.on("client_left",function(t){e.trigger("client_left",t)}),t.on("set_name",function(t,n){e.trigger("set_name",t,n)}),t.on("set_color",function(t,n){e.trigger("set_color",t,n)}),t.on("ack",function(t){e.trigger("ack",t)}),t.on("operation",function(t,n,o,r){e.trigger("operation",n,o),e.trigger("selection",t,r)}),t.on("operations",function(t,n){e.trigger("operations",t,n)}),t.on("selection",function(t,n){e.trigger("selection",t,n)}),t.on("reconnect",function(){e.trigger("reconnect")})}return t.prototype.sendOperation=function(t,e,n){this.socket.emit("operation",t,e,n)},t.prototype.sendSelection=function(t){this.socket.emit("selection",t)},t.prototype.getOperations=function(t,e){this.socket.emit("get_operations",t,e)},t.prototype.registerCallbacks=function(t){this.callbacks=t},t.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},t}(),ot.AjaxAdapter=function(){"use strict";function t(t,e,n){"/"!==t[t.length-1]&&(t+="/"),this.path=t,this.ownUserName=e,this.majorRevision=n.major||0,this.minorRevision=n.minor||0,this.poll()}return t.prototype.renderRevisionPath=function(){return"revision/"+this.majorRevision+"-"+this.minorRevision},t.prototype.handleResponse=function(t){var e,n=t.operations;for(e=0;e<n.length;e++)n[e].user===this.ownUserName?this.trigger("ack"):this.trigger("operation",n[e].operation);n.length>0&&(this.majorRevision+=n.length,this.minorRevision=0);var o=t.events;if(o){for(e=0;e<o.length;e++){var r=o[e].user;if(r!==this.ownUserName)switch(o[e].event){case"joined":this.trigger("set_name",r,r);break;case"left":this.trigger("client_left",r);break;case"selection":this.trigger("selection",r,o[e].selection)}}this.minorRevision+=o.length}var i=t.users;i&&(delete i[this.ownUserName],this.trigger("clients",i)),t.revision&&(this.majorRevision=t.revision.major,this.minorRevision=t.revision.minor)},t.prototype.poll=function(){var t=this;$.ajax({url:this.path+this.renderRevisionPath(),type:"GET",dataType:"json",timeout:5e3,success:function(e){t.handleResponse(e),t.poll()},error:function(){setTimeout(function(){t.poll()},500)}})},t.prototype.sendOperation=function(t,e,n){if(t!==this.majorRevision)throw new Error("Revision numbers out of sync");var o=this;$.ajax({url:this.path+this.renderRevisionPath(),type:"POST",data:JSON.stringify({operation:e,selection:n}),contentType:"application/json",processData:!1,success:function(t){},error:function(){setTimeout(function(){o.sendOperation(t,e,n)},500)}})},t.prototype.sendSelection=function(t){$.ajax({url:this.path+this.renderRevisionPath()+"/selection",type:"POST",data:JSON.stringify(t),contentType:"application/json",processData:!1,timeout:1e3})},t.prototype.registerCallbacks=function(t){this.callbacks=t},t.prototype.trigger=function(t){var e=Array.prototype.slice.call(arguments,1),n=this.callbacks&&this.callbacks[t];n&&n.apply(this,e)},t}(),ot.EditorClient=function(){"use strict";function t(t,e){this.selectionBefore=t,this.selectionAfter=e}function e(t,e){this.clientId=t,this.selection=e}function n(t,e,n,o,r,i){this.id=t,this.listEl=e,this.editorAdapter=n,this.name=o,this.color=r,this.li=document.createElement("li"),o&&(this.li.textContent=o,this.listEl.appendChild(this.li)),r?this.setForceColor(r):this.setColor(o?s(o):Math.random()),i&&this.updateSelection(i)}function o(t,e,n,o){c.call(this,t),this.serverAdapter=n,this.editorAdapter=o,this.undoManager=new u,this.initializeClientList(),this.initializeClients(e);var r=this;this.editorAdapter.registerCallbacks({change:function(t,e){r.onChange(t,e)},selectionChange:function(){r.onSelectionChange()},blur:function(){r.onBlur()}}),this.editorAdapter.registerUndo(function(){r.undo()}),this.editorAdapter.registerRedo(function(){r.redo()}),this.serverAdapter.registerCallbacks({client_left:function(t){r.onClientLeft(t)},set_name:function(t,e){r.getClientObject(t).setName(e)},set_color:function(t,e){r.getClientObject(t).setForceColor(e)},ack:function(t){r.serverAck(t)},operation:function(t,e){r.applyServer(t,f.fromJSON(e))},operations:function(t,e){r.applyOperations(t,e)},selection:function(t,e){e?r.getClientObject(t).updateSelection(r.transformSelection(l.fromJSON(e))):r.getClientObject(t).removeSelection()},clients:function(t){var e;for(e in r.clients)r.clients.hasOwnProperty(e)&&!t.hasOwnProperty(e)&&r.onClientLeft(e);for(e in t)if(t.hasOwnProperty(e)){var n=r.getClientObject(e);t[e].name&&n.setName(t[e].name);var o=t[e].selection;o?r.clients[e].updateSelection(r.transformSelection(l.fromJSON(o))):r.clients[e].removeSelection()}},reconnect:function(){r.serverReconnect()}})}function r(t,e,n){function o(t){var e=Math.round(255*t).toString(16);return 1===e.length?"0"+e:e}return"#"+o(t)+o(e)+o(n)}function i(t,e,n){if(0===e)return r(n,n,n);var o=n<.5?n*(1+e):n+e-e*n,i=2*n-o,s=function(t){return t<0&&(t+=1),t>1&&(t-=1),6*t<1?i+6*(o-i)*t:2*t<1?o:3*t<2?i+6*(o-i)*(2/3-t):i};return r(s(t+1/3),s(t),s(t-1/3))}function s(t){for(var e=1,n=0;n<t.length;n++)e=17*(e+t.charCodeAt(n))%360;return e/360}function a(t,e){function n(){}n.prototype=e.prototype,t.prototype=new n,t.prototype.constructor=t}function h(t){return t[t.length-1]}function p(t){t.parentNode&&t.parentNode.removeChild(t)}var c=ot.Client,l=ot.Selection,u=ot.UndoManager,f=ot.TextOperation,d=ot.WrappedOperation;return t.prototype.invert=function(){return new t(this.selectionAfter,this.selectionBefore)},t.prototype.compose=function(e){return new t(this.selectionBefore,e.selectionAfter)},t.prototype.transform=function(e){return new t(this.selectionBefore?this.selectionBefore.transform(e):null,this.selectionAfter?this.selectionAfter.transform(e):null)},e.fromJSON=function(t){return new e(t.clientId,t.selection&&l.fromJSON(t.selection))},e.prototype.transform=function(t){return new e(this.clientId,this.selection&&this.selection.transform(t))},n.prototype.setColor=function(t){this.hue=t,this.color=i(t,.75,.5),this.lightColor=i(t,.5,.9),this.li&&(this.li.style.color=this.color)},n.prototype.setForceColor=function(t){this.hue=null,this.color=t,this.lightColor=t,this.li&&(this.li.style.color=this.color)},n.prototype.setName=function(t){this.name!==t&&(this.name=t,this.li.textContent=t,this.li.parentNode||this.listEl.appendChild(this.li),this.setColor(s(t)))},n.prototype.updateSelection=function(t){this.removeSelection(),this.selection=t,this.mark=this.editorAdapter.setOtherSelection(t,t.position===t.selectionEnd?this.color:this.lightColor,this.id)},n.prototype.remove=function(){this.li&&p(this.li),this.removeSelection()},n.prototype.removeSelection=function(){this.mark&&(this.mark.clear(),this.mark=null)},a(o,c),o.prototype.addClient=function(t,e){this.clients[t]=new n(t,this.clientListEl,this.editorAdapter,e.name||t,e.color||null,e.selection?l.fromJSON(e.selection):null)},o.prototype.initializeClients=function(t){this.clients={};for(var e in t)t.hasOwnProperty(e)&&this.addClient(e,t[e])},o.prototype.getClientObject=function(t){var e=this.clients[t];return e?e:this.clients[t]=new n(t,this.clientListEl,this.editorAdapter)},o.prototype.onClientLeft=function(t){var e=this.clients[t];e&&(e.remove(),delete this.clients[t])},o.prototype.initializeClientList=function(){this.clientListEl=document.createElement("ul")},o.prototype.applyUnredo=function(t){this.undoManager.add(t.invert(this.editorAdapter.getValue())),this.editorAdapter.applyOperation(t.wrapped),this.selection=t.meta.selectionAfter,this.editorAdapter.setSelection(this.selection),this.applyClient(t.wrapped)},o.prototype.undo=function(){var t=this;this.undoManager.canUndo()&&this.undoManager.performUndo(function(e){t.applyUnredo(e)})},o.prototype.redo=function(){var t=this;this.undoManager.canRedo()&&this.undoManager.performRedo(function(e){t.applyUnredo(e)})},o.prototype.onChange=function(e,n){var o=this.selection;this.updateSelection();var r=new t(o,this.selection),i=(new d(e,r),this.undoManager.undoStack.length>0&&n.shouldBeComposedWithInverted(h(this.undoManager.undoStack).wrapped)),s=new t(this.selection,o);this.undoManager.add(new d(n,s),i),this.applyClient(e)},o.prototype.updateSelection=function(){this.selection=this.editorAdapter.getSelection()},o.prototype.onSelectionChange=function(){var t=this.selection;this.updateSelection(),t&&this.selection.equals(t)||this.sendSelection(this.selection)},o.prototype.onBlur=function(){this.selection=null,this.sendSelection(null)},o.prototype.sendSelection=function(t){this.state instanceof c.AwaitingWithBuffer||this.serverAdapter.sendSelection(t)},o.prototype.sendOperation=function(t,e){this.serverAdapter.sendOperation(t,e.toJSON(),this.selection)},o.prototype.getOperations=function(t,e){this.serverAdapter.getOperations(t,e)},o.prototype.applyOperation=function(t){this.editorAdapter.applyOperation(t),this.updateSelection(),this.undoManager.transform(new d(t,null))},o}(); \ No newline at end of file
diff --git a/public/vendor/ot/socketio-adapter.js b/public/vendor/ot/socketio-adapter.js
index 329d4f3e..189a081b 100644..100755
--- a/public/vendor/ot/socketio-adapter.js
+++ b/public/vendor/ot/socketio-adapter.js
@@ -24,8 +24,6 @@ ot.SocketIOAdapter = (function () {
self.trigger('selection', clientId, selection);
});
socket.on('operations', function (head, operations) {
- operations = LZString.decompressFromUTF16(operations);
- operations = JSON.parse(operations);
self.trigger('operations', head, operations);
});
socket.on('selection', function (clientId, selection) {
@@ -37,7 +35,6 @@ ot.SocketIOAdapter = (function () {
}
SocketIOAdapter.prototype.sendOperation = function (revision, operation, selection) {
- operation = LZString.compressToUTF16(JSON.stringify(operation));
this.socket.emit('operation', revision, operation, selection);
};
diff --git a/public/views/index.ejs b/public/views/index.ejs
index 5b84f4fc..fe900673 100644
--- a/public/views/index.ejs
+++ b/public/views/index.ejs
@@ -150,7 +150,7 @@
<iframe src="//ghbtns.com/github-btn.html?user=hackmdio&repo=hackmd&type=star&count=true" frameborder="0" scrolling="0" width="104px" height="20px"></iframe>
</h6>
<p>
- &copy; 2016 <a href="https://www.facebook.com/hackmdio" target="_blank"><i class="fa fa-facebook-square"></i> HackMD</a> | <a href="<%- url %>/s/release-notes" target="_blank"><%= __('Releases') %></a>
+ &copy; 2017 <a href="https://www.facebook.com/hackmdio" target="_blank"><i class="fa fa-facebook-square"></i> HackMD</a> | <a href="<%- url %>/s/release-notes" target="_blank"><%= __('Releases') %></a>
</p>
<select class="ui-locale">
<option value="en">English</option>
@@ -170,6 +170,7 @@
<option value="uk">Українська</option>
<option value="hi">हिन्दी</option>
<option value="sv">svenska</option>
+ <option value="eo">Esperanto</option>
</select>
</div>
</div>
diff --git a/webpackBaseConfig.js b/webpackBaseConfig.js
index 6c569c1e..e618bffd 100644
--- a/webpackBaseConfig.js
+++ b/webpackBaseConfig.js
@@ -172,12 +172,12 @@ module.exports = {
"script!listPagnation",
"expose?select2!select2",
"expose?moment!moment",
- "js-url",
+ "script!js-url",
path.join(__dirname, 'public/js/cover.js')
],
index: [
"script!jquery-ui-resizable",
- "js-url",
+ "script!js-url",
"expose?filterXSS!xss",
"script!Idle.Js",
"expose?LZString!lz-string",
@@ -227,7 +227,7 @@ module.exports = {
"expose?jsyaml!js-yaml",
"script!mermaid",
"expose?moment!moment",
- "js-url",
+ "script!js-url",
"script!handlebars",
"expose?hljs!highlight.js",
"expose?emojify!emojify.js",