From aff206ca9592f194cd41a36c63cba0a3416fe638 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Sat, 24 Dec 2016 17:02:03 +0800 Subject: Fix js-url not import correctly --- public/js/index.js | 1 - webpackBaseConfig.js | 6 +++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/public/js/index.js b/public/js/index.js index 96580fe3..e0f6bd6b 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"); diff --git a/webpackBaseConfig.js b/webpackBaseConfig.js index 400db14b..496afce4 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", -- cgit v1.2.3 From 0c42780a397a0691f32ef81d80eee1110b4daf4d Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Sat, 24 Dec 2016 17:23:04 +0800 Subject: Update features.md publish button name and icon --- public/docs/features.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 and copy the URL. +If you want to share a **read-only** note, simply press publish button and copy the URL. ## Save a Note: Currently, you can save to **Dropbox** or save an `.md` file locally. -- cgit v1.2.3 From 70a9b2ce15a7250d61656c4a5251bf058fe88e1d Mon Sep 17 00:00:00 2001 From: bananaappletw Date: Sat, 24 Dec 2016 22:04:18 +0800 Subject: Fix config mistake --- config.json.example | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) 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", -- cgit v1.2.3 From 6db51528084d744eb1be71b03c63e0dfc82ed6d3 Mon Sep 17 00:00:00 2001 From: bananaappletw Date: Sat, 24 Dec 2016 22:17:04 +0800 Subject: Add missing HMD_ALLOW_ORIGIN variable --- app.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) 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/`", "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 -- cgit v1.2.3 From 51f5a70375cbd68815cf57d976c32a9707cf4ce3 Mon Sep 17 00:00:00 2001 From: Colin Maudry Date: Mon, 26 Dec 2016 17:18:53 +0100 Subject: Little improvements (typos, uppercase + accents) --- locales/fr.json | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) 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", -- cgit v1.2.3 From a2fbb3add98060371bdfd37547c5e15de0bdd82c Mon Sep 17 00:00:00 2001 From: knjcode Date: Tue, 27 Dec 2016 12:46:07 +0900 Subject: Fix URL concatenation --- app.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app.js b/app.js index c6ee8ace..3246c879 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'); @@ -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; -- cgit v1.2.3 From ec1ae8c6b5ce04c6f434f9421aa76032e4f9741f Mon Sep 17 00:00:00 2001 From: James Stephenson Date: Fri, 30 Dec 2016 22:02:57 -0500 Subject: Added Esperanto translation Translation by Jonathan Powell and James Stephenson --- app.js | 2 +- locales/eo.json | 104 +++++++++++++++++++++++++++++++++++++++++++++++++ public/views/index.ejs | 1 + 3 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 locales/eo.json diff --git a/app.js b/app.js index 3246c879..f096ab64 100644 --- a/app.js +++ b/app.js @@ -103,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' }); 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/public/views/index.ejs b/public/views/index.ejs index adcdd34d..d8d0a76f 100644 --- a/public/views/index.ejs +++ b/public/views/index.ejs @@ -169,6 +169,7 @@ + -- cgit v1.2.3 From c904083d1f5064d2e786e0bc6ee3804b91805d24 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 10:52:47 +0800 Subject: Remove manual LZString compression for partial socket io event data --- lib/ot/editor-socketio-server.js | 8 ++------ lib/realtime.js | 4 ---- public/js/index.js | 8 -------- public/vendor/ot/ot.min.js | 2 +- public/vendor/ot/socketio-adapter.js | 3 --- 5 files changed, 3 insertions(+), 22 deletions(-) mode change 100644 => 100755 public/vendor/ot/socketio-adapter.js 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..c66fea0a 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); } @@ -301,7 +300,6 @@ function emitOnlineUsers(socket) { var out = { users: users }; - out = LZString.compressToUTF16(JSON.stringify(out)); realtime.io.to(noteId).emit('online users', out); } @@ -330,7 +328,6 @@ function emitRefresh(socket) { createtime: note.createtime, updatetime: note.updatetime }; - out = LZString.compressToUTF16(JSON.stringify(out)); socket.emit('refresh', out); } @@ -863,7 +860,6 @@ function connection(socket) { var out = { users: users }; - out = LZString.compressToUTF16(JSON.stringify(out)); socket.emit('online users', out); }); diff --git a/public/js/index.js b/public/js/index.js index e0f6bd6b..328b67fe 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -2645,8 +2645,6 @@ editor.on('update', function () { }); }); socket.on('check', function (data) { - data = LZString.decompressFromUTF16(data); - data = JSON.parse(data); //console.log(data); updateInfo(data); }); @@ -2656,8 +2654,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); @@ -2704,8 +2700,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; @@ -2766,8 +2760,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; 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;e0},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;ot.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;au?(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=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;re.line?1:t.che.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=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;r0&&(this.majorRevision+=n.length,this.minorRevision=0);var o=t.events;if(o){for(e=0;e1&&(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;n0&&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;e0},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;ot.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;au?(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=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;re.line?1:t.che.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=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;r0&&(this.majorRevision+=n.length,this.minorRevision=0);var o=t.events;if(o){for(e=0;e1&&(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;n0&&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 old mode 100644 new mode 100755 index 329d4f3e..189a081b --- 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); }; -- cgit v1.2.3 From f6d8e3ab00370a78c0c788ad1e37a7ff77a53555 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 10:59:53 +0800 Subject: Remove LZString compression for data storage --- lib/models/note.js | 17 ++++++----------- lib/models/revision.js | 11 +++++------ lib/realtime.js | 10 ++++------ lib/response.js | 14 +++++++------- lib/workers/dmpWorker.js | 13 ++++++------- 5 files changed, 28 insertions(+), 37 deletions(-) diff --git a/lib/models/note.js b/lib/models/note.js index 5727046c..81de991f 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -124,8 +124,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 +133,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 +262,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 +491,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..6f44cf1d 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'); @@ -214,7 +213,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 +222,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 +243,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/realtime.js b/lib/realtime.js index c66fea0a..a662deeb 100644 --- a/lib/realtime.js +++ b/lib/realtime.js @@ -152,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() }; @@ -459,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); @@ -479,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, @@ -491,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]); diff --git a/lib/response.js b/lib/response.js index 2b38cf25..f591d5ed 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 { @@ -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 { @@ -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 { diff --git a/lib/workers/dmpWorker.js b/lib/workers/dmpWorker.js index fae36191..5b4b6aa2 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(); @@ -80,10 +79,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 +104,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 +122,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) { -- cgit v1.2.3 From 99628a5662b0abcb396e78aad0f631071d9d1f61 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 11:00:08 +0800 Subject: Fix to not use diff_cleanupSemantic, bug report refer to https://code.google.com/p/google-diff-match-patch/issues/detail?id=67 --- lib/workers/dmpWorker.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/workers/dmpWorker.js b/lib/workers/dmpWorker.js index 5b4b6aa2..8e69636e 100644 --- a/lib/workers/dmpWorker.js +++ b/lib/workers/dmpWorker.js @@ -57,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(); -- cgit v1.2.3 From c3a96ff112c8f7ea31af97fbc3319e782f4490e2 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 11:00:40 +0800 Subject: Fix migration script of revision lacks of definition of primary key --- lib/migrations/20160607060246-support-revision.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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, -- cgit v1.2.3 From d9e19b602968551fcda4ee806d767a04f6a11490 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 11:05:05 +0800 Subject: Update to remove null byte before saving to DB and remove null byte on changes --- lib/models/index.js | 7 +++++++ lib/models/note.js | 15 ++++++++++++--- lib/models/revision.js | 20 ++++++++++++++++---- public/js/index.js | 7 +++++++ 4 files changed, 42 insertions(+), 7 deletions(-) diff --git a/lib/models/index.js b/lib/models/index.js index de6cd13c..6d0fd3c3 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -20,6 +20,13 @@ 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; + var db = {}; fs diff --git a/lib/models/note.js b/lib/models/note.js index 81de991f..37d26ec0 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -52,13 +52,22 @@ module.exports = function (sequelize, DataTypes) { defaultValue: 0 }, title: { - type: DataTypes.TEXT + type: DataTypes.TEXT, + set: function (value) { + this.setDataValue('title', sequelize.stripNullByte(value)); + } }, content: { - type: DataTypes.TEXT + type: DataTypes.TEXT, + set: function (value) { + this.setDataValue('content', sequelize.stripNullByte(value)); + } }, authorship: { - type: DataTypes.TEXT + type: DataTypes.TEXT, + set: function (value) { + this.setDataValue('authorship', JSON.stringify(value)); + } }, lastchangeAt: { type: DataTypes.DATE diff --git a/lib/models/revision.js b/lib/models/revision.js index 6f44cf1d..adc651f1 100644 --- a/lib/models/revision.js +++ b/lib/models/revision.js @@ -59,19 +59,31 @@ module.exports = function (sequelize, DataTypes) { defaultValue: Sequelize.UUIDV4 }, patch: { - type: DataTypes.TEXT + type: DataTypes.TEXT, + set: function (value) { + this.setDataValue('patch', sequelize.stripNullByte(value)); + } }, lastContent: { - type: DataTypes.TEXT + type: DataTypes.TEXT, + set: function (value) { + this.setDataValue('lastContent', sequelize.stripNullByte(value)); + } }, content: { - type: DataTypes.TEXT + type: DataTypes.TEXT, + set: function (value) { + this.setDataValue('content', sequelize.stripNullByte(value)); + } }, length: { type: DataTypes.INTEGER }, authorship: { - type: DataTypes.TEXT + type: DataTypes.TEXT, + set: function (value) { + this.setDataValue('authorship', value ? JSON.stringify(value) : value); + } } }, { classMethods: { diff --git a/public/js/index.js b/public/js/index.js index 328b67fe..e62d1dcb 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -3207,6 +3207,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) { @@ -3228,6 +3234,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'); } -- cgit v1.2.3 From b1ec3ba748dc7b58015e97448958e78b512f6df0 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 11:05:36 +0800 Subject: Refactor data processing to model definition --- lib/models/index.js | 6 ++++++ lib/models/note.js | 9 +++++++++ lib/models/revision.js | 12 ++++++++++++ 3 files changed, 27 insertions(+) diff --git a/lib/models/index.js b/lib/models/index.js index 6d0fd3c3..e83956e5 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -27,6 +27,12 @@ function stripNullByte(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 37d26ec0..5c63dc1a 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -53,18 +53,27 @@ module.exports = function (sequelize, DataTypes) { }, title: { type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('title'), ""); + }, set: function (value) { this.setDataValue('title', sequelize.stripNullByte(value)); } }, content: { type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('content'), ""); + }, set: function (value) { this.setDataValue('content', sequelize.stripNullByte(value)); } }, authorship: { type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse); + }, set: function (value) { this.setDataValue('authorship', JSON.stringify(value)); } diff --git a/lib/models/revision.js b/lib/models/revision.js index adc651f1..c7360fed 100644 --- a/lib/models/revision.js +++ b/lib/models/revision.js @@ -60,18 +60,27 @@ module.exports = function (sequelize, DataTypes) { }, patch: { type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('patch'), ""); + }, set: function (value) { this.setDataValue('patch', sequelize.stripNullByte(value)); } }, lastContent: { type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('lastContent'), ""); + }, set: function (value) { this.setDataValue('lastContent', sequelize.stripNullByte(value)); } }, content: { type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('content'), ""); + }, set: function (value) { this.setDataValue('content', sequelize.stripNullByte(value)); } @@ -81,6 +90,9 @@ module.exports = function (sequelize, DataTypes) { }, authorship: { type: DataTypes.TEXT, + get: function () { + return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse); + }, set: function (value) { this.setDataValue('authorship', value ? JSON.stringify(value) : value); } -- cgit v1.2.3 From 0db4358adb12b2df1051874829f658496df6910c Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 11:05:49 +0800 Subject: Fix authorship might losing update event because of throttling --- public/js/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/js/index.js b/public/js/index.js index e62d1dcb..2e0513c2 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -2443,7 +2443,7 @@ function updateInfo(data) { updateAuthorship(); } } -var updateAuthorship = _.throttle(function () { +var updateAuthorship = _.debounce(function () { editor.operation(updateAuthorshipInner); }, 50); function initMark() { -- cgit v1.2.3 From db0ea715c61c1b82794403cd045f6ccac5aa66c9 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 11:06:02 +0800 Subject: Update to improve editor performance by debounce checkEditorScrollbar event --- public/js/index.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/public/js/index.js b/public/js/index.js index 2e0513c2..56766657 100644 --- a/public/js/index.js +++ b/public/js/index.js @@ -1223,7 +1223,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(); -- cgit v1.2.3 From 10a8448c6a9cdde0ad8cadf3dc250de159eacbf2 Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 11:13:41 +0800 Subject: Fix yaml metadata description not able to show --- lib/response.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/response.js b/lib/response.js index f591d5ed..54e2a337 100755 --- a/lib/response.js +++ b/lib/response.js @@ -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, @@ -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 @@ -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, -- cgit v1.2.3 From d74fea1d10b600f139552c0353da992c4442998b Mon Sep 17 00:00:00 2001 From: Wu Cheng-Han Date: Mon, 2 Jan 2017 11:14:25 +0800 Subject: Update year to 2017 --- LICENSE | 2 +- public/views/index.ejs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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 and others +Copyright (c) 2017 Max Wu 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/public/views/index.ejs b/public/views/index.ejs index d8d0a76f..84dd2533 100644 --- a/public/views/index.ejs +++ b/public/views/index.ejs @@ -149,7 +149,7 @@

- © 2016 HackMD | <%= __('Releases') %> + © 2017 HackMD

-- cgit v1.2.3