Merge branch 'master' into frontend-next
This commit is contained in:
commit
65acaea8cf
26 changed files with 307 additions and 111 deletions
9
AUTHORS
9
AUTHORS
|
@ -1,13 +1,19 @@
|
||||||
List of HackMD contributors.
|
List of HackMD contributors.
|
||||||
|
|
||||||
|
bananaapple
|
||||||
Bartlomiej Szala
|
Bartlomiej Szala
|
||||||
|
Colin Maudry
|
||||||
Dmytro Kytsmen
|
Dmytro Kytsmen
|
||||||
Fabien Meghazi
|
Fabien Meghazi
|
||||||
|
Florian Rhiem
|
||||||
Ikumi Shimizu
|
Ikumi Shimizu
|
||||||
ivanorsolic
|
ivanorsolic
|
||||||
Jason Croft
|
Jason Croft
|
||||||
Jannik Lorenz
|
Jannik Lorenz
|
||||||
|
James Stephenson
|
||||||
Jordan Matelsky
|
Jordan Matelsky
|
||||||
|
Kenji Doi
|
||||||
|
Lars Kajes
|
||||||
Lapinot
|
Lapinot
|
||||||
Laura Kyle
|
Laura Kyle
|
||||||
Marcelo Alencar
|
Marcelo Alencar
|
||||||
|
@ -17,10 +23,13 @@ Max Wu
|
||||||
Ömer Erdinç Yağmurlu
|
Ömer Erdinç Yağmurlu
|
||||||
p0v1n0m
|
p0v1n0m
|
||||||
Pablo Guerrero
|
Pablo Guerrero
|
||||||
|
paraschadha2052
|
||||||
Peter Dave Hello
|
Peter Dave Hello
|
||||||
Qubo
|
Qubo
|
||||||
Sergio Valverde
|
Sergio Valverde
|
||||||
|
Tom Wyckhuys
|
||||||
Yukai Huang
|
Yukai Huang
|
||||||
Zacharias Traianos
|
Zacharias Traianos
|
||||||
Zankio
|
Zankio
|
||||||
|
Xavier
|
||||||
葉家郡
|
葉家郡
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
||||||
The MIT License (MIT)
|
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
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|
|
@ -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)
|
[![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)
|
[migration-to-0.4.0](https://github.com/hackmdio/migration-to-0.4.0)
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
5
app.js
5
app.js
|
@ -11,6 +11,7 @@ var compression = require('compression')
|
||||||
var session = require('express-session');
|
var session = require('express-session');
|
||||||
var SequelizeStore = require('connect-session-sequelize')(session.Store);
|
var SequelizeStore = require('connect-session-sequelize')(session.Store);
|
||||||
var fs = require('fs');
|
var fs = require('fs');
|
||||||
|
var url = require('url');
|
||||||
var path = require('path');
|
var path = require('path');
|
||||||
var imgur = require('imgur');
|
var imgur = require('imgur');
|
||||||
var formidable = require('formidable');
|
var formidable = require('formidable');
|
||||||
|
@ -102,7 +103,7 @@ app.use(helmet.hsts({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
i18n.configure({
|
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',
|
cookie: 'locale',
|
||||||
directory: __dirname + '/locales'
|
directory: __dirname + '/locales'
|
||||||
});
|
});
|
||||||
|
@ -487,7 +488,7 @@ app.post('/uploadimage', function (req, res) {
|
||||||
switch (config.imageUploadType) {
|
switch (config.imageUploadType) {
|
||||||
case 'filesystem':
|
case 'filesystem':
|
||||||
res.send({
|
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;
|
break;
|
||||||
|
|
7
app.json
7
app.json
|
@ -35,11 +35,16 @@
|
||||||
"description": "sub url path, like `www.example.com/<URL_PATH>`",
|
"description": "sub url path, like `www.example.com/<URL_PATH>`",
|
||||||
"required": false
|
"required": false
|
||||||
},
|
},
|
||||||
"HMD_ALLOW_ORIGIN": {
|
"HMD_PORT": {
|
||||||
"description": "web app port",
|
"description": "web app port",
|
||||||
"required": false,
|
"required": false,
|
||||||
"value": "80"
|
"value": "80"
|
||||||
},
|
},
|
||||||
|
"HMD_ALLOW_ORIGIN": {
|
||||||
|
"description": "domain name whitelist (use comma to separate)",
|
||||||
|
"required": false,
|
||||||
|
"value": "localhost"
|
||||||
|
},
|
||||||
"HMD_PROTOCOL_USESSL": {
|
"HMD_PROTOCOL_USESSL": {
|
||||||
"description": "set to use ssl protocol for resources path (only applied when domain is set)",
|
"description": "set to use ssl protocol for resources path (only applied when domain is set)",
|
||||||
"required": false
|
"required": false
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
{
|
{
|
||||||
"test": {
|
"test": {
|
||||||
|
"db": {
|
||||||
"dialect": "sqlite",
|
"dialect": "sqlite",
|
||||||
"storage": "./db.hackmd.sqlite"
|
"storage": "./db.hackmd.sqlite"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"development": {
|
"development": {
|
||||||
"domain": "localhost",
|
"domain": "localhost",
|
||||||
|
|
|
@ -111,8 +111,8 @@ function getserverurl() {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
var version = '0.4.6';
|
var version = '0.5.0';
|
||||||
var minimumCompatibleVersion = '0.4.5';
|
var minimumCompatibleVersion = '0.5.0';
|
||||||
var maintenance = true;
|
var maintenance = true;
|
||||||
var cwd = path.join(__dirname, '..');
|
var cwd = path.join(__dirname, '..');
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,10 @@ module.exports = {
|
||||||
up: function (queryInterface, Sequelize) {
|
up: function (queryInterface, Sequelize) {
|
||||||
queryInterface.addColumn('Notes', 'savedAt', Sequelize.DATE);
|
queryInterface.addColumn('Notes', 'savedAt', Sequelize.DATE);
|
||||||
queryInterface.createTable('Revisions', {
|
queryInterface.createTable('Revisions', {
|
||||||
id: Sequelize.UUID,
|
id: {
|
||||||
|
type: Sequelize.UUID,
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
noteId: Sequelize.UUID,
|
noteId: Sequelize.UUID,
|
||||||
patch: Sequelize.TEXT,
|
patch: Sequelize.TEXT,
|
||||||
lastContent: Sequelize.TEXT,
|
lastContent: Sequelize.TEXT,
|
||||||
|
|
|
@ -20,6 +20,19 @@ if (config.dburl)
|
||||||
else
|
else
|
||||||
sequelize = new Sequelize(dbconfig.database, dbconfig.username, dbconfig.password, dbconfig);
|
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 = {};
|
var db = {};
|
||||||
|
|
||||||
fs
|
fs
|
||||||
|
|
|
@ -52,13 +52,31 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
defaultValue: 0
|
defaultValue: 0
|
||||||
},
|
},
|
||||||
title: {
|
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: {
|
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: {
|
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: {
|
lastchangeAt: {
|
||||||
type: DataTypes.DATE
|
type: DataTypes.DATE
|
||||||
|
@ -124,8 +142,6 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
var body = fs.readFileSync(filePath, 'utf8');
|
var body = fs.readFileSync(filePath, 'utf8');
|
||||||
var contentLength = body.length;
|
var contentLength = body.length;
|
||||||
var title = Note.parseNoteTitle(body);
|
var title = Note.parseNoteTitle(body);
|
||||||
body = LZString.compressToBase64(body);
|
|
||||||
title = LZString.compressToBase64(title);
|
|
||||||
if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) {
|
if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) {
|
||||||
note.update({
|
note.update({
|
||||||
title: title,
|
title: title,
|
||||||
|
@ -135,14 +151,14 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
|
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
|
||||||
if (err) return _callback(err, null);
|
if (err) return _callback(err, null);
|
||||||
// update authorship on after making revision of docs
|
// 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 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++) {
|
for (var i = 0; i < operations.length; i++) {
|
||||||
authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship);
|
authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship);
|
||||||
}
|
}
|
||||||
note.update({
|
note.update({
|
||||||
authorship: LZString.compressToBase64(JSON.stringify(authorship))
|
authorship: JSON.stringify(authorship)
|
||||||
}).then(function (note) {
|
}).then(function (note) {
|
||||||
return callback(null, note.id);
|
return callback(null, note.id);
|
||||||
}).catch(function (err) {
|
}).catch(function (err) {
|
||||||
|
@ -264,10 +280,7 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ');
|
return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ');
|
||||||
},
|
},
|
||||||
decodeTitle: function (title) {
|
decodeTitle: function (title) {
|
||||||
var decodedTitle = LZString.decompressFromBase64(title);
|
return title ? title : 'Untitled';
|
||||||
if (decodedTitle) title = decodedTitle;
|
|
||||||
else title = 'Untitled';
|
|
||||||
return title;
|
|
||||||
},
|
},
|
||||||
generateWebTitle: function (title) {
|
generateWebTitle: function (title) {
|
||||||
title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD";
|
title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD";
|
||||||
|
@ -496,8 +509,8 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
if (Note.checkFileExist(filePath)) {
|
if (Note.checkFileExist(filePath)) {
|
||||||
var fsCreatedTime = moment(fs.statSync(filePath).ctime);
|
var fsCreatedTime = moment(fs.statSync(filePath).ctime);
|
||||||
body = fs.readFileSync(filePath, 'utf8');
|
body = fs.readFileSync(filePath, 'utf8');
|
||||||
note.title = LZString.compressToBase64(Note.parseNoteTitle(body));
|
note.title = Note.parseNoteTitle(body);
|
||||||
note.content = LZString.compressToBase64(body);
|
note.content = body;
|
||||||
if (filePath !== config.defaultnotepath) {
|
if (filePath !== config.defaultnotepath) {
|
||||||
note.createdAt = fsCreatedTime;
|
note.createdAt = fsCreatedTime;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
|
|
||||||
// external modules
|
// external modules
|
||||||
var Sequelize = require("sequelize");
|
var Sequelize = require("sequelize");
|
||||||
var LZString = require('lz-string');
|
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var moment = require('moment');
|
var moment = require('moment');
|
||||||
var childProcess = require('child_process');
|
var childProcess = require('child_process');
|
||||||
|
@ -60,19 +59,43 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
defaultValue: Sequelize.UUIDV4
|
defaultValue: Sequelize.UUIDV4
|
||||||
},
|
},
|
||||||
patch: {
|
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: {
|
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: {
|
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: {
|
length: {
|
||||||
type: DataTypes.INTEGER
|
type: DataTypes.INTEGER
|
||||||
},
|
},
|
||||||
authorship: {
|
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: {
|
classMethods: {
|
||||||
|
@ -214,7 +237,7 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
Revision.create({
|
Revision.create({
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
lastContent: note.content,
|
lastContent: note.content,
|
||||||
length: LZString.decompressFromBase64(note.content).length,
|
length: note.content.length,
|
||||||
authorship: note.authorship
|
authorship: note.authorship
|
||||||
}).then(function (revision) {
|
}).then(function (revision) {
|
||||||
Revision.finishSaveNoteRevision(note, revision, callback);
|
Revision.finishSaveNoteRevision(note, revision, callback);
|
||||||
|
@ -223,8 +246,8 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
var latestRevision = revisions[0];
|
var latestRevision = revisions[0];
|
||||||
var lastContent = LZString.decompressFromBase64(latestRevision.content || latestRevision.lastContent);
|
var lastContent = latestRevision.content || latestRevision.lastContent;
|
||||||
var content = LZString.decompressFromBase64(note.content);
|
var content = note.content;
|
||||||
sendDmpWorker({
|
sendDmpWorker({
|
||||||
msg: 'create patch',
|
msg: 'create patch',
|
||||||
lastDoc: lastContent,
|
lastDoc: lastContent,
|
||||||
|
@ -244,9 +267,9 @@ module.exports = function (sequelize, DataTypes) {
|
||||||
} else {
|
} else {
|
||||||
Revision.create({
|
Revision.create({
|
||||||
noteId: note.id,
|
noteId: note.id,
|
||||||
patch: LZString.compressToBase64(patch),
|
patch: patch,
|
||||||
content: note.content,
|
content: note.content,
|
||||||
length: LZString.decompressFromBase64(note.content).length,
|
length: note.content.length,
|
||||||
authorship: note.authorship
|
authorship: note.authorship
|
||||||
}).then(function (revision) {
|
}).then(function (revision) {
|
||||||
// clear last revision content to reduce db size
|
// clear last revision content to reduce db size
|
||||||
|
|
|
@ -7,7 +7,6 @@ var Server = require('./server');
|
||||||
var Selection = require('./selection');
|
var Selection = require('./selection');
|
||||||
var util = require('util');
|
var util = require('util');
|
||||||
|
|
||||||
var LZString = require('lz-string');
|
|
||||||
var logger = require('../logger');
|
var logger = require('../logger');
|
||||||
|
|
||||||
function EditorSocketIOServer(document, operations, docId, mayWrite, operationCallback) {
|
function EditorSocketIOServer(document, operations, docId, mayWrite, operationCallback) {
|
||||||
|
@ -40,10 +39,8 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
|
||||||
revision: this.operations.length,
|
revision: this.operations.length,
|
||||||
clients: this.users
|
clients: this.users
|
||||||
};
|
};
|
||||||
socket.emit('doc', LZString.compressToUTF16(JSON.stringify(docOut)));
|
socket.emit('doc', docOut);
|
||||||
socket.on('operation', function (revision, operation, selection) {
|
socket.on('operation', function (revision, operation, selection) {
|
||||||
operation = LZString.decompressFromUTF16(operation);
|
|
||||||
operation = JSON.parse(operation);
|
|
||||||
socket.origin = 'operation';
|
socket.origin = 'operation';
|
||||||
self.mayWrite(socket, function (mayWrite) {
|
self.mayWrite(socket, function (mayWrite) {
|
||||||
if (!mayWrite) {
|
if (!mayWrite) {
|
||||||
|
@ -62,7 +59,7 @@ EditorSocketIOServer.prototype.addClient = function (socket) {
|
||||||
clients: self.users,
|
clients: self.users,
|
||||||
force: true
|
force: true
|
||||||
};
|
};
|
||||||
socket.emit('doc', LZString.compressToUTF16(JSON.stringify(docOut)));
|
socket.emit('doc', docOut);
|
||||||
}, 100);
|
}, 100);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -129,7 +126,6 @@ EditorSocketIOServer.prototype.onGetOperations = function (socket, base, head) {
|
||||||
var operations = this.operations.slice(base, head).map(function (op) {
|
var operations = this.operations.slice(base, head).map(function (op) {
|
||||||
return op.wrapped.toJSON();
|
return op.wrapped.toJSON();
|
||||||
});
|
});
|
||||||
operations = LZString.compressToUTF16(JSON.stringify(operations));
|
|
||||||
socket.emit('operations', head, operations);
|
socket.emit('operations', head, operations);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -71,7 +71,6 @@ function emitCheck(note) {
|
||||||
authors: note.authors,
|
authors: note.authors,
|
||||||
authorship: note.authorship
|
authorship: note.authorship
|
||||||
};
|
};
|
||||||
out = LZString.compressToUTF16(JSON.stringify(out));
|
|
||||||
realtime.io.to(note.id).emit('check', 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);
|
if (!note || !note.server) return callback(null, null);
|
||||||
var body = note.server.document;
|
var body = note.server.document;
|
||||||
var title = note.title = models.Note.parseNoteTitle(body);
|
var title = note.title = models.Note.parseNoteTitle(body);
|
||||||
title = LZString.compressToBase64(title);
|
|
||||||
body = LZString.compressToBase64(body);
|
|
||||||
var values = {
|
var values = {
|
||||||
title: title,
|
title: title,
|
||||||
content: body,
|
content: body,
|
||||||
authorship: LZString.compressToBase64(JSON.stringify(note.authorship)),
|
authorship: note.authorship,
|
||||||
lastchangeuserId: note.lastchangeuser,
|
lastchangeuserId: note.lastchangeuser,
|
||||||
lastchangeAt: Date.now()
|
lastchangeAt: Date.now()
|
||||||
};
|
};
|
||||||
|
@ -301,7 +298,6 @@ function emitOnlineUsers(socket) {
|
||||||
var out = {
|
var out = {
|
||||||
users: users
|
users: users
|
||||||
};
|
};
|
||||||
out = LZString.compressToUTF16(JSON.stringify(out));
|
|
||||||
realtime.io.to(noteId).emit('online users', out);
|
realtime.io.to(noteId).emit('online users', out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -330,7 +326,6 @@ function emitRefresh(socket) {
|
||||||
createtime: note.createtime,
|
createtime: note.createtime,
|
||||||
updatetime: note.updatetime
|
updatetime: note.updatetime
|
||||||
};
|
};
|
||||||
out = LZString.compressToUTF16(JSON.stringify(out));
|
|
||||||
socket.emit('refresh', out);
|
socket.emit('refresh', out);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -462,7 +457,7 @@ function startConnection(socket) {
|
||||||
var lastchangeuser = note.lastchangeuserId;
|
var lastchangeuser = note.lastchangeuserId;
|
||||||
var lastchangeuserprofile = note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null;
|
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 createtime = note.createdAt;
|
||||||
var updatetime = note.lastchangeAt;
|
var updatetime = note.lastchangeAt;
|
||||||
var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback);
|
var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback);
|
||||||
|
@ -482,7 +477,7 @@ function startConnection(socket) {
|
||||||
notes[noteId] = {
|
notes[noteId] = {
|
||||||
id: noteId,
|
id: noteId,
|
||||||
alias: note.alias,
|
alias: note.alias,
|
||||||
title: LZString.decompressFromBase64(note.title),
|
title: note.title,
|
||||||
owner: owner,
|
owner: owner,
|
||||||
ownerprofile: ownerprofile,
|
ownerprofile: ownerprofile,
|
||||||
permission: note.permission,
|
permission: note.permission,
|
||||||
|
@ -494,7 +489,7 @@ function startConnection(socket) {
|
||||||
updatetime: moment(updatetime).valueOf(),
|
updatetime: moment(updatetime).valueOf(),
|
||||||
server: server,
|
server: server,
|
||||||
authors: authors,
|
authors: authors,
|
||||||
authorship: note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : []
|
authorship: note.authorship
|
||||||
};
|
};
|
||||||
|
|
||||||
return finishConnection(socket, notes[noteId], users[socket.id]);
|
return finishConnection(socket, notes[noteId], users[socket.id]);
|
||||||
|
@ -863,7 +858,6 @@ function connection(socket) {
|
||||||
var out = {
|
var out = {
|
||||||
users: users
|
users: users
|
||||||
};
|
};
|
||||||
out = LZString.compressToUTF16(JSON.stringify(out));
|
|
||||||
socket.emit('online users', out);
|
socket.emit('online users', out);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -75,7 +75,7 @@ function showIndex(req, res, next) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function responseHackMD(res, note) {
|
function responseHackMD(res, note) {
|
||||||
var body = LZString.decompressFromBase64(note.content);
|
var body = note.content;
|
||||||
var meta = null;
|
var meta = null;
|
||||||
try {
|
try {
|
||||||
meta = models.Note.parseMeta(metaMarked(body).meta);
|
meta = models.Note.parseMeta(metaMarked(body).meta);
|
||||||
|
@ -191,7 +191,7 @@ function showPublishNote(req, res, next) {
|
||||||
if (!note) {
|
if (!note) {
|
||||||
return response.errorNotFound(res);
|
return response.errorNotFound(res);
|
||||||
}
|
}
|
||||||
var body = LZString.decompressFromBase64(note.content);
|
var body = note.content;
|
||||||
var meta = null;
|
var meta = null;
|
||||||
var markdown = null;
|
var markdown = null;
|
||||||
try {
|
try {
|
||||||
|
@ -209,7 +209,7 @@ function showPublishNote(req, res, next) {
|
||||||
var origin = config.serverurl;
|
var origin = config.serverurl;
|
||||||
var data = {
|
var data = {
|
||||||
title: title,
|
title: title,
|
||||||
description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
|
description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
|
||||||
viewcount: note.viewcount,
|
viewcount: note.viewcount,
|
||||||
createtime: createtime,
|
createtime: createtime,
|
||||||
updatetime: updatetime,
|
updatetime: updatetime,
|
||||||
|
@ -248,7 +248,7 @@ function actionSlide(req, res, note) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function actionDownload(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 title = models.Note.decodeTitle(note.title);
|
||||||
var filename = title;
|
var filename = title;
|
||||||
filename = encodeURIComponent(filename);
|
filename = encodeURIComponent(filename);
|
||||||
|
@ -265,7 +265,7 @@ function actionDownload(req, res, note) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function actionInfo(req, res, note) {
|
function actionInfo(req, res, note) {
|
||||||
var body = LZString.decompressFromBase64(note.content);
|
var body = note.content;
|
||||||
var meta = null;
|
var meta = null;
|
||||||
var markdown = null;
|
var markdown = null;
|
||||||
try {
|
try {
|
||||||
|
@ -281,7 +281,7 @@ function actionInfo(req, res, note) {
|
||||||
var title = models.Note.decodeTitle(note.title);
|
var title = models.Note.decodeTitle(note.title);
|
||||||
var data = {
|
var data = {
|
||||||
title: meta.title || title,
|
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,
|
viewcount: note.viewcount,
|
||||||
createtime: createtime,
|
createtime: createtime,
|
||||||
updatetime: updatetime
|
updatetime: updatetime
|
||||||
|
@ -297,7 +297,7 @@ function actionInfo(req, res, note) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function actionPDF(req, res, note) {
|
function actionPDF(req, res, note) {
|
||||||
var body = LZString.decompressFromBase64(note.content);
|
var body = note.content;
|
||||||
try {
|
try {
|
||||||
body = metaMarked(body).markdown;
|
body = metaMarked(body).markdown;
|
||||||
} catch(err) {
|
} catch(err) {
|
||||||
|
@ -479,7 +479,7 @@ function githubActionGist(req, res, note) {
|
||||||
if (!error && httpResponse.statusCode == 200) {
|
if (!error && httpResponse.statusCode == 200) {
|
||||||
var access_token = body.access_token;
|
var access_token = body.access_token;
|
||||||
if (access_token) {
|
if (access_token) {
|
||||||
var content = LZString.decompressFromBase64(note.content);
|
var content = note.content;
|
||||||
var title = models.Note.decodeTitle(note.title);
|
var title = models.Note.decodeTitle(note.title);
|
||||||
var filename = title.replace('/', ' ') + '.md';
|
var filename = title.replace('/', ' ') + '.md';
|
||||||
var gist = {
|
var gist = {
|
||||||
|
@ -579,7 +579,7 @@ function showPublishSlide(req, res, next) {
|
||||||
if (!note) {
|
if (!note) {
|
||||||
return response.errorNotFound(res);
|
return response.errorNotFound(res);
|
||||||
}
|
}
|
||||||
var body = LZString.decompressFromBase64(note.content);
|
var body = note.content;
|
||||||
var meta = null;
|
var meta = null;
|
||||||
var markdown = null;
|
var markdown = null;
|
||||||
try {
|
try {
|
||||||
|
@ -597,7 +597,7 @@ function showPublishSlide(req, res, next) {
|
||||||
var origin = config.serverurl;
|
var origin = config.serverurl;
|
||||||
var data = {
|
var data = {
|
||||||
title: title,
|
title: title,
|
||||||
description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
|
description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
|
||||||
viewcount: note.viewcount,
|
viewcount: note.viewcount,
|
||||||
createtime: createtime,
|
createtime: createtime,
|
||||||
updatetime: updatetime,
|
updatetime: updatetime,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
// external modules
|
// external modules
|
||||||
var LZString = require('lz-string');
|
|
||||||
var DiffMatchPatch = require('diff-match-patch');
|
var DiffMatchPatch = require('diff-match-patch');
|
||||||
var dmp = new DiffMatchPatch();
|
var dmp = new DiffMatchPatch();
|
||||||
|
|
||||||
|
@ -58,7 +57,6 @@ process.on('message', function(data) {
|
||||||
function createPatch(lastDoc, currDoc) {
|
function createPatch(lastDoc, currDoc) {
|
||||||
var ms_start = (new Date()).getTime();
|
var ms_start = (new Date()).getTime();
|
||||||
var diff = dmp.diff_main(lastDoc, currDoc);
|
var diff = dmp.diff_main(lastDoc, currDoc);
|
||||||
dmp.diff_cleanupSemantic(diff);
|
|
||||||
var patch = dmp.patch_make(lastDoc, diff);
|
var patch = dmp.patch_make(lastDoc, diff);
|
||||||
patch = dmp.patch_toText(patch);
|
patch = dmp.patch_toText(patch);
|
||||||
var ms_end = (new Date()).getTime();
|
var ms_end = (new Date()).getTime();
|
||||||
|
@ -80,10 +78,10 @@ function getRevision(revisions, count) {
|
||||||
for (var i = 0; i < count; i++) {
|
for (var i = 0; i < count; i++) {
|
||||||
var revision = revisions[i];
|
var revision = revisions[i];
|
||||||
if (i == 0) {
|
if (i == 0) {
|
||||||
startContent = LZString.decompressFromBase64(revision.content || revision.lastContent);
|
startContent = revision.content || revision.lastContent;
|
||||||
}
|
}
|
||||||
if (i != count - 1) {
|
if (i != count - 1) {
|
||||||
var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
|
var patch = dmp.patch_fromText(revision.patch);
|
||||||
applyPatches = applyPatches.concat(patch);
|
applyPatches = applyPatches.concat(patch);
|
||||||
}
|
}
|
||||||
lastPatch = revision.patch;
|
lastPatch = revision.patch;
|
||||||
|
@ -105,11 +103,11 @@ function getRevision(revisions, count) {
|
||||||
for (var i = l; i >= count - 1; i--) {
|
for (var i = l; i >= count - 1; i--) {
|
||||||
var revision = revisions[i];
|
var revision = revisions[i];
|
||||||
if (i == l) {
|
if (i == l) {
|
||||||
startContent = LZString.decompressFromBase64(revision.lastContent);
|
startContent = revision.lastContent;
|
||||||
authorship = revision.authorship;
|
authorship = revision.authorship;
|
||||||
}
|
}
|
||||||
if (revision.patch) {
|
if (revision.patch) {
|
||||||
var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
|
var patch = dmp.patch_fromText(revision.patch);
|
||||||
applyPatches = applyPatches.concat(patch);
|
applyPatches = applyPatches.concat(patch);
|
||||||
}
|
}
|
||||||
lastPatch = revision.patch;
|
lastPatch = revision.patch;
|
||||||
|
@ -123,8 +121,8 @@ function getRevision(revisions, count) {
|
||||||
}
|
}
|
||||||
var data = {
|
var data = {
|
||||||
content: finalContent,
|
content: finalContent,
|
||||||
patch: dmp.patch_fromText(LZString.decompressFromBase64(lastPatch)),
|
patch: dmp.patch_fromText(lastPatch),
|
||||||
authorship: authorship ? JSON.parse(LZString.decompressFromBase64(authorship)) : null
|
authorship: authorship
|
||||||
};
|
};
|
||||||
var ms_end = (new Date()).getTime();
|
var ms_end = (new Date()).getTime();
|
||||||
if (config.debug) {
|
if (config.debug) {
|
||||||
|
|
104
locales/eo.json
Normal file
104
locales/eo.json
Normal file
|
@ -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"
|
||||||
|
}
|
|
@ -9,12 +9,12 @@
|
||||||
"Support charts and MathJax": "Supporte les graphiques et MathJax",
|
"Support charts and MathJax": "Supporte les graphiques et MathJax",
|
||||||
"Support slide mode": "Supporte le mode présentation",
|
"Support slide mode": "Supporte le mode présentation",
|
||||||
"Sign In": "Se connecter",
|
"Sign In": "Se connecter",
|
||||||
"Below is the history from browser": "En dessous ce situe l'historique du navigateur",
|
"Below is the history from browser": "Ci-dessous, l'historique du navigateur",
|
||||||
"Welcome!": "Bienvenue !",
|
"Welcome!": "Bienvenue !",
|
||||||
"New note": "Nouvelle note",
|
"New note": "Nouvelle note",
|
||||||
"or": "ou",
|
"or": "ou",
|
||||||
"Sign Out": "Se déconnecter",
|
"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...",
|
"Select tags...": "Selectionner les tags...",
|
||||||
"Search keyword...": "Chercher un mot-clef...",
|
"Search keyword...": "Chercher un mot-clef...",
|
||||||
"Sort by title": "Trier par titre",
|
"Sort by title": "Trier par titre",
|
||||||
|
@ -28,7 +28,7 @@
|
||||||
"No history": "Pas d'historique",
|
"No history": "Pas d'historique",
|
||||||
"Import from browser": "Importer depuis le navigateur",
|
"Import from browser": "Importer depuis le navigateur",
|
||||||
"Releases": "Versions",
|
"Releases": "Versions",
|
||||||
"Are you sure?": "Etes-vous sûr?",
|
"Are you sure?": "Ëtes-vous sûr ?",
|
||||||
"Cancel": "Annuler",
|
"Cancel": "Annuler",
|
||||||
"Yes, do it!": "Oui, je suis sûr !",
|
"Yes, do it!": "Oui, je suis sûr !",
|
||||||
"Choose method": "Choisir la méthode",
|
"Choose method": "Choisir la méthode",
|
||||||
|
@ -37,13 +37,13 @@
|
||||||
"Publish": "Publier",
|
"Publish": "Publier",
|
||||||
"Extra": "Extra",
|
"Extra": "Extra",
|
||||||
"Revision": "Historique",
|
"Revision": "Historique",
|
||||||
"Slide Mode": "Mode Présentation",
|
"Slide Mode": "Mode présentation",
|
||||||
"Export": "Exporter",
|
"Export": "Exporter",
|
||||||
"Import": "Importer",
|
"Import": "Importer",
|
||||||
"Clipboard": "Presse-papier",
|
"Clipboard": "Presse-papier",
|
||||||
"Download": "Télécharger",
|
"Download": "Télécharger",
|
||||||
"Raw HTML": "HTML Brut",
|
"Raw HTML": "HTML brut",
|
||||||
"Edit": "Editer",
|
"Edit": "Éditer",
|
||||||
"View": "Voir",
|
"View": "Voir",
|
||||||
"Both": "Les deux",
|
"Both": "Les deux",
|
||||||
"Help": "Aide",
|
"Help": "Aide",
|
||||||
|
@ -54,23 +54,23 @@
|
||||||
"Refresh to update.": "Recharger pour mettre à jour.",
|
"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",
|
"See releases notes here": "Voir les commentaires de version ici",
|
||||||
"Refresh to enjoy new features.": "Recharger pour bénéficier des nouvelles fonctionnalitées.",
|
"Refresh to enjoy new features.": "Recharger pour bénéficier des nouvelles fonctionnalités.",
|
||||||
"Your user state has changed.": "Votre status utilisateur a changé.",
|
"Your user state has changed.": "Votre statut utilisateur a changé.",
|
||||||
"Refresh to load new user state.": "Recharger pour avoir le nouveau statut utilisateur.",
|
"Refresh to load new user state.": "Recharger pour avoir le nouveau statut utilisateur.",
|
||||||
"Refresh": "Recharger",
|
"Refresh": "Recharger",
|
||||||
"Contacts": "Contacts",
|
"Contacts": "Contacts",
|
||||||
"Report an issue": "Signaler un problème",
|
"Report an issue": "Signaler un problème",
|
||||||
"Send us email": "Envoyez-nous un mail",
|
"Send us email": "Envoyez-nous un mail",
|
||||||
"Documents": "Documents",
|
"Documents": "Documents",
|
||||||
"Features": "Fonctionnalitées",
|
"Features": "Fonctionnalités",
|
||||||
"YAML Metadata": "Métadonnées YAML",
|
"YAML Metadata": "Métadonnées YAML",
|
||||||
"Slide Example": "Exemple de présentation",
|
"Slide Example": "Exemple de présentation",
|
||||||
"Cheatsheet": "Pense-bête",
|
"Cheatsheet": "Pense-bête",
|
||||||
"Example": "Exemple",
|
"Example": "Exemple",
|
||||||
"Syntax": "Syntaxe",
|
"Syntax": "Syntaxe",
|
||||||
"Header": "Entête",
|
"Header": "En-tête",
|
||||||
"Unordered List": "Liste non-ordonnée",
|
"Unordered List": "Liste à puce",
|
||||||
"Ordered List": "List ordonnée",
|
"Ordered List": "List numérotée",
|
||||||
"Todo List": "Liste de tâches",
|
"Todo List": "Liste de tâches",
|
||||||
"Blockquote": "Citation",
|
"Blockquote": "Citation",
|
||||||
"Bold font": "Gras",
|
"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.",
|
"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!",
|
"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",
|
"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",
|
"Import from Snippet": "Importer depuis Snippet",
|
||||||
"Select From Available Projects": "Sélectionner depuis les projets disponibles",
|
"Select From Available Projects": "Sélectionner depuis les projets disponibles",
|
||||||
"Select From Available Snippets": "Sélectionner depuis les Snippets disponibles",
|
"Select From Available Snippets": "Sélectionner depuis les Snippets disponibles",
|
||||||
|
|
14
package.json
14
package.json
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "hackmd",
|
"name": "hackmd",
|
||||||
"version": "0.4.6",
|
"version": "0.5.0",
|
||||||
"description": "Realtime collaborative markdown notes on all platforms.",
|
"description": "Realtime collaborative markdown notes on all platforms.",
|
||||||
"main": "app.js",
|
"main": "app.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
@ -13,7 +13,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"Idle.Js": "github:shawnmclean/Idle.js",
|
"Idle.Js": "github:shawnmclean/Idle.js",
|
||||||
"async": "^2.1.4",
|
"async": "^2.1.4",
|
||||||
"aws-sdk": "^2.7.15",
|
"aws-sdk": "^2.7.20",
|
||||||
"blueimp-md5": "^2.6.0",
|
"blueimp-md5": "^2.6.0",
|
||||||
"body-parser": "^1.15.2",
|
"body-parser": "^1.15.2",
|
||||||
"bootstrap": "^3.3.7",
|
"bootstrap": "^3.3.7",
|
||||||
|
@ -38,7 +38,7 @@
|
||||||
"formidable": "^1.0.17",
|
"formidable": "^1.0.17",
|
||||||
"gist-embed": "~2.6.0",
|
"gist-embed": "~2.6.0",
|
||||||
"handlebars": "^4.0.6",
|
"handlebars": "^4.0.6",
|
||||||
"helmet": "^3.1.0",
|
"helmet": "^3.3.0",
|
||||||
"highlight.js": "~9.9.0",
|
"highlight.js": "~9.9.0",
|
||||||
"i18n": "^0.8.3",
|
"i18n": "^0.8.3",
|
||||||
"imgur": "git+https://github.com/hackmdio/node-imgur.git",
|
"imgur": "git+https://github.com/hackmdio/node-imgur.git",
|
||||||
|
@ -54,7 +54,7 @@
|
||||||
"keymaster": "^1.6.2",
|
"keymaster": "^1.6.2",
|
||||||
"list.js": "^1.3.0",
|
"list.js": "^1.3.0",
|
||||||
"list.pagination.js": "^0.1.1",
|
"list.pagination.js": "^0.1.1",
|
||||||
"lodash": "^4.17.2",
|
"lodash": "^4.17.4",
|
||||||
"lz-string": "1.4.4",
|
"lz-string": "1.4.4",
|
||||||
"markdown-it": "^8.2.2",
|
"markdown-it": "^8.2.2",
|
||||||
"markdown-it-abbr": "^1.0.4",
|
"markdown-it-abbr": "^1.0.4",
|
||||||
|
@ -98,7 +98,7 @@
|
||||||
"reveal.js": "^3.3.0",
|
"reveal.js": "^3.3.0",
|
||||||
"scrypt": "^6.0.3",
|
"scrypt": "^6.0.3",
|
||||||
"select2": "^3.5.2-browserify",
|
"select2": "^3.5.2-browserify",
|
||||||
"sequelize": "^3.27.0",
|
"sequelize": "^3.28.0",
|
||||||
"sequelize-cli": "^2.5.1",
|
"sequelize-cli": "^2.5.1",
|
||||||
"shortid": "2.2.6",
|
"shortid": "2.2.6",
|
||||||
"socket.io": "~1.7.2",
|
"socket.io": "~1.7.2",
|
||||||
|
@ -118,7 +118,7 @@
|
||||||
"vue": "^2.1.6",
|
"vue": "^2.1.6",
|
||||||
"vue-loader": "^10.0.2",
|
"vue-loader": "^10.0.2",
|
||||||
"winston": "^2.3.0",
|
"winston": "^2.3.0",
|
||||||
"xss": "^0.3.2"
|
"xss": "^0.3.3"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.x"
|
"node": ">=4.x"
|
||||||
|
@ -154,7 +154,7 @@
|
||||||
"expose-loader": "^0.7.1",
|
"expose-loader": "^0.7.1",
|
||||||
"extract-text-webpack-plugin": "^1.0.1",
|
"extract-text-webpack-plugin": "^1.0.1",
|
||||||
"file-loader": "^0.9.0",
|
"file-loader": "^0.9.0",
|
||||||
"html-webpack-plugin": "^2.24.1",
|
"html-webpack-plugin": "^2.25.0",
|
||||||
"imports-loader": "^0.7.0",
|
"imports-loader": "^0.7.0",
|
||||||
"json-loader": "^0.5.4",
|
"json-loader": "^0.5.4",
|
||||||
"less": "^2.7.1",
|
"less": "^2.7.1",
|
||||||
|
|
|
@ -34,7 +34,7 @@ This will automatically upload the image to **[imgur](http://imgur.com)**, nothi
|
||||||
|
|
||||||
## Share Notes:
|
## Share Notes:
|
||||||
If you want to share an **editable** note, just copy the URL.
|
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:
|
## 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.
|
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.
|
||||||
|
|
|
@ -1,6 +1,35 @@
|
||||||
Release Notes
|
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
|
<i class="fa fa-tag"></i> 0.4.6 `Melya` <i class="fa fa-clock-o"></i> 2016-12-19 17:20
|
||||||
---
|
---
|
||||||
### Features
|
### Features
|
||||||
|
|
|
@ -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 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 noteurl = serverurl + '/' + noteid;
|
||||||
|
|
||||||
var version = '0.4.6';
|
var version = '0.5.0';
|
||||||
|
|
||||||
var checkAuth = false;
|
var checkAuth = false;
|
||||||
var profile = null;
|
var profile = null;
|
||||||
|
|
|
@ -11,7 +11,6 @@ require('highlight.js/styles/github-gist.css');
|
||||||
var toMarkdown = require('to-markdown');
|
var toMarkdown = require('to-markdown');
|
||||||
|
|
||||||
var saveAs = require('file-saver').saveAs;
|
var saveAs = require('file-saver').saveAs;
|
||||||
var url = require('js-url');
|
|
||||||
var randomColor = require('randomcolor');
|
var randomColor = require('randomcolor');
|
||||||
|
|
||||||
var _ = require("lodash");
|
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
|
// workaround simple scroll bar knob
|
||||||
// will get wrong position when editor height changed
|
// will get wrong position when editor height changed
|
||||||
var scrollInfo = editor.getScrollInfo();
|
var scrollInfo = editor.getScrollInfo();
|
||||||
|
@ -2445,7 +2448,7 @@ function updateInfo(data) {
|
||||||
updateAuthorship();
|
updateAuthorship();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var updateAuthorship = _.throttle(function () {
|
var updateAuthorship = _.debounce(function () {
|
||||||
editor.operation(updateAuthorshipInner);
|
editor.operation(updateAuthorshipInner);
|
||||||
}, 50);
|
}, 50);
|
||||||
function initMark() {
|
function initMark() {
|
||||||
|
@ -2647,8 +2650,6 @@ editor.on('update', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
socket.on('check', function (data) {
|
socket.on('check', function (data) {
|
||||||
data = LZString.decompressFromUTF16(data);
|
|
||||||
data = JSON.parse(data);
|
|
||||||
//console.log(data);
|
//console.log(data);
|
||||||
updateInfo(data);
|
updateInfo(data);
|
||||||
});
|
});
|
||||||
|
@ -2658,8 +2659,6 @@ socket.on('permission', function (data) {
|
||||||
var docmaxlength = null;
|
var docmaxlength = null;
|
||||||
var permission = null;
|
var permission = null;
|
||||||
socket.on('refresh', function (data) {
|
socket.on('refresh', function (data) {
|
||||||
data = LZString.decompressFromUTF16(data);
|
|
||||||
data = JSON.parse(data);
|
|
||||||
//console.log(data);
|
//console.log(data);
|
||||||
docmaxlength = data.docmaxlength;
|
docmaxlength = data.docmaxlength;
|
||||||
editor.setOption("maxLength", docmaxlength);
|
editor.setOption("maxLength", docmaxlength);
|
||||||
|
@ -2706,8 +2705,6 @@ var CodeMirrorAdapter = ot.CodeMirrorAdapter;
|
||||||
var cmClient = null;
|
var cmClient = null;
|
||||||
|
|
||||||
socket.on('doc', function (obj) {
|
socket.on('doc', function (obj) {
|
||||||
obj = LZString.decompressFromUTF16(obj);
|
|
||||||
obj = JSON.parse(obj);
|
|
||||||
var body = obj.str;
|
var body = obj.str;
|
||||||
var bodyMismatch = editor.getValue() !== body;
|
var bodyMismatch = editor.getValue() !== body;
|
||||||
var havePendingOperation = cmClient && Object.keys(cmClient.state).length > 0;
|
var havePendingOperation = cmClient && Object.keys(cmClient.state).length > 0;
|
||||||
|
@ -2768,8 +2765,6 @@ socket.on('operation', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('online users', function (data) {
|
socket.on('online users', function (data) {
|
||||||
data = LZString.decompressFromUTF16(data);
|
|
||||||
data = JSON.parse(data);
|
|
||||||
if (debug)
|
if (debug)
|
||||||
console.debug(data);
|
console.debug(data);
|
||||||
onlineUsers = data.users;
|
onlineUsers = data.users;
|
||||||
|
@ -3217,6 +3212,12 @@ function buildCursor(user) {
|
||||||
}
|
}
|
||||||
|
|
||||||
//editor actions
|
//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) {
|
function enforceMaxLength(cm, change) {
|
||||||
var maxLength = cm.getOption("maxLength");
|
var maxLength = cm.getOption("maxLength");
|
||||||
if (maxLength && change.update) {
|
if (maxLength && change.update) {
|
||||||
|
@ -3238,6 +3239,7 @@ var ignoreEmitEvents = ['setValue', 'ignoreHistory'];
|
||||||
editor.on('beforeChange', function (cm, change) {
|
editor.on('beforeChange', function (cm, change) {
|
||||||
if (debug)
|
if (debug)
|
||||||
console.debug(change);
|
console.debug(change);
|
||||||
|
removeNullByte(cm, change);
|
||||||
if (enforceMaxLength(cm, change)) {
|
if (enforceMaxLength(cm, change)) {
|
||||||
$('.limit-modal').modal('show');
|
$('.limit-modal').modal('show');
|
||||||
}
|
}
|
||||||
|
|
2
public/vendor/ot/ot.min.js
vendored
2
public/vendor/ot/ot.min.js
vendored
File diff suppressed because one or more lines are too long
3
public/vendor/ot/socketio-adapter.js
vendored
Normal file → Executable file
3
public/vendor/ot/socketio-adapter.js
vendored
Normal file → Executable file
|
@ -24,8 +24,6 @@ ot.SocketIOAdapter = (function () {
|
||||||
self.trigger('selection', clientId, selection);
|
self.trigger('selection', clientId, selection);
|
||||||
});
|
});
|
||||||
socket.on('operations', function (head, operations) {
|
socket.on('operations', function (head, operations) {
|
||||||
operations = LZString.decompressFromUTF16(operations);
|
|
||||||
operations = JSON.parse(operations);
|
|
||||||
self.trigger('operations', head, operations);
|
self.trigger('operations', head, operations);
|
||||||
});
|
});
|
||||||
socket.on('selection', function (clientId, selection) {
|
socket.on('selection', function (clientId, selection) {
|
||||||
|
@ -37,7 +35,6 @@ ot.SocketIOAdapter = (function () {
|
||||||
}
|
}
|
||||||
|
|
||||||
SocketIOAdapter.prototype.sendOperation = function (revision, operation, selection) {
|
SocketIOAdapter.prototype.sendOperation = function (revision, operation, selection) {
|
||||||
operation = LZString.compressToUTF16(JSON.stringify(operation));
|
|
||||||
this.socket.emit('operation', revision, operation, selection);
|
this.socket.emit('operation', revision, operation, selection);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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>
|
<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>
|
</h6>
|
||||||
<p>
|
<p>
|
||||||
© 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>
|
© 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>
|
</p>
|
||||||
<select class="ui-locale">
|
<select class="ui-locale">
|
||||||
<option value="en">English</option>
|
<option value="en">English</option>
|
||||||
|
@ -170,6 +170,7 @@
|
||||||
<option value="uk">Українська</option>
|
<option value="uk">Українська</option>
|
||||||
<option value="hi">हिन्दी</option>
|
<option value="hi">हिन्दी</option>
|
||||||
<option value="sv">svenska</option>
|
<option value="sv">svenska</option>
|
||||||
|
<option value="eo">Esperanto</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -172,12 +172,12 @@ module.exports = {
|
||||||
"script!listPagnation",
|
"script!listPagnation",
|
||||||
"expose?select2!select2",
|
"expose?select2!select2",
|
||||||
"expose?moment!moment",
|
"expose?moment!moment",
|
||||||
"js-url",
|
"script!js-url",
|
||||||
path.join(__dirname, 'public/js/cover.js')
|
path.join(__dirname, 'public/js/cover.js')
|
||||||
],
|
],
|
||||||
index: [
|
index: [
|
||||||
"script!jquery-ui-resizable",
|
"script!jquery-ui-resizable",
|
||||||
"js-url",
|
"script!js-url",
|
||||||
"expose?filterXSS!xss",
|
"expose?filterXSS!xss",
|
||||||
"script!Idle.Js",
|
"script!Idle.Js",
|
||||||
"expose?LZString!lz-string",
|
"expose?LZString!lz-string",
|
||||||
|
@ -227,7 +227,7 @@ module.exports = {
|
||||||
"expose?jsyaml!js-yaml",
|
"expose?jsyaml!js-yaml",
|
||||||
"script!mermaid",
|
"script!mermaid",
|
||||||
"expose?moment!moment",
|
"expose?moment!moment",
|
||||||
"js-url",
|
"script!js-url",
|
||||||
"script!handlebars",
|
"script!handlebars",
|
||||||
"expose?hljs!highlight.js",
|
"expose?hljs!highlight.js",
|
||||||
"expose?emojify!emojify.js",
|
"expose?emojify!emojify.js",
|
||||||
|
|
Loading…
Reference in a new issue