mirror of https://github.com/status-im/codimd.git
Merge branch 'master' into frontend-next
This commit is contained in:
commit
65acaea8cf
9
AUTHORS
9
AUTHORS
|
@ -1,13 +1,19 @@
|
|||
List of HackMD contributors.
|
||||
|
||||
bananaapple
|
||||
Bartlomiej Szala
|
||||
Colin Maudry
|
||||
Dmytro Kytsmen
|
||||
Fabien Meghazi
|
||||
Florian Rhiem
|
||||
Ikumi Shimizu
|
||||
ivanorsolic
|
||||
Jason Croft
|
||||
Jannik Lorenz
|
||||
James Stephenson
|
||||
Jordan Matelsky
|
||||
Kenji Doi
|
||||
Lars Kajes
|
||||
Lapinot
|
||||
Laura Kyle
|
||||
Marcelo Alencar
|
||||
|
@ -17,10 +23,13 @@ Max Wu
|
|||
Ömer Erdinç Yağmurlu
|
||||
p0v1n0m
|
||||
Pablo Guerrero
|
||||
paraschadha2052
|
||||
Peter Dave Hello
|
||||
Qubo
|
||||
Sergio Valverde
|
||||
Tom Wyckhuys
|
||||
Yukai Huang
|
||||
Zacharias Traianos
|
||||
Zankio
|
||||
Xavier
|
||||
葉家郡
|
2
LICENSE
2
LICENSE
|
@ -1,6 +1,6 @@
|
|||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2016 Max Wu <jackymaxj@gmail.com> and others
|
||||
Copyright (c) 2017 Max Wu <jackymaxj@gmail.com> and others
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
|
@ -22,6 +22,12 @@ You can quickly setup a sample heroku hackmd application by clicking the button
|
|||
|
||||
[![Deploy](https://www.herokucdn.com/deploy/button.svg)](https://heroku.com/deploy)
|
||||
|
||||
[migration-to-0.5.0](https://github.com/hackmdio/migration-to-0.5.0)
|
||||
---
|
||||
|
||||
We don't use LZString to compress socket.io data and DB data after version 0.5.0.
|
||||
Please run the migration tool if you're upgrading from the old version.
|
||||
|
||||
[migration-to-0.4.0](https://github.com/hackmdio/migration-to-0.4.0)
|
||||
---
|
||||
|
||||
|
|
5
app.js
5
app.js
|
@ -11,6 +11,7 @@ var compression = require('compression')
|
|||
var session = require('express-session');
|
||||
var SequelizeStore = require('connect-session-sequelize')(session.Store);
|
||||
var fs = require('fs');
|
||||
var url = require('url');
|
||||
var path = require('path');
|
||||
var imgur = require('imgur');
|
||||
var formidable = require('formidable');
|
||||
|
@ -102,7 +103,7 @@ app.use(helmet.hsts({
|
|||
}));
|
||||
|
||||
i18n.configure({
|
||||
locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv'],
|
||||
locales: ['en', 'zh', 'fr', 'de', 'ja', 'es', 'el', 'pt', 'it', 'tr', 'ru', 'nl', 'hr', 'pl', 'uk', 'hi', 'sv', 'eo'],
|
||||
cookie: 'locale',
|
||||
directory: __dirname + '/locales'
|
||||
});
|
||||
|
@ -487,7 +488,7 @@ app.post('/uploadimage', function (req, res) {
|
|||
switch (config.imageUploadType) {
|
||||
case 'filesystem':
|
||||
res.send({
|
||||
link: path.join(config.serverurl, files.image.path.match(/^public(.+$)/)[1])
|
||||
link: url.resolve(config.serverurl, files.image.path.match(/^public(.+$)/)[1])
|
||||
});
|
||||
|
||||
break;
|
||||
|
|
7
app.json
7
app.json
|
@ -35,11 +35,16 @@
|
|||
"description": "sub url path, like `www.example.com/<URL_PATH>`",
|
||||
"required": false
|
||||
},
|
||||
"HMD_ALLOW_ORIGIN": {
|
||||
"HMD_PORT": {
|
||||
"description": "web app port",
|
||||
"required": false,
|
||||
"value": "80"
|
||||
},
|
||||
"HMD_ALLOW_ORIGIN": {
|
||||
"description": "domain name whitelist (use comma to separate)",
|
||||
"required": false,
|
||||
"value": "localhost"
|
||||
},
|
||||
"HMD_PROTOCOL_USESSL": {
|
||||
"description": "set to use ssl protocol for resources path (only applied when domain is set)",
|
||||
"required": false
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
{
|
||||
"test": {
|
||||
"dialect": "sqlite",
|
||||
"storage": "./db.hackmd.sqlite"
|
||||
"db": {
|
||||
"dialect": "sqlite",
|
||||
"storage": "./db.hackmd.sqlite"
|
||||
}
|
||||
},
|
||||
"development": {
|
||||
"domain": "localhost",
|
||||
|
|
|
@ -111,8 +111,8 @@ function getserverurl() {
|
|||
return url;
|
||||
}
|
||||
|
||||
var version = '0.4.6';
|
||||
var minimumCompatibleVersion = '0.4.5';
|
||||
var version = '0.5.0';
|
||||
var minimumCompatibleVersion = '0.5.0';
|
||||
var maintenance = true;
|
||||
var cwd = path.join(__dirname, '..');
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -20,6 +20,19 @@ if (config.dburl)
|
|||
else
|
||||
sequelize = new Sequelize(dbconfig.database, dbconfig.username, dbconfig.password, dbconfig);
|
||||
|
||||
// [Postgres] Handling NULL bytes
|
||||
// https://github.com/sequelize/sequelize/issues/6485
|
||||
function stripNullByte(value) {
|
||||
return value ? value.replace(/\u0000/g, "") : value;
|
||||
}
|
||||
sequelize.stripNullByte = stripNullByte;
|
||||
|
||||
function processData(data, _default, process) {
|
||||
if (data === undefined) return data;
|
||||
else return data === null ? _default : (process ? process(data) : data);
|
||||
}
|
||||
sequelize.processData = processData;
|
||||
|
||||
var db = {};
|
||||
|
||||
fs
|
||||
|
|
|
@ -52,13 +52,31 @@ module.exports = function (sequelize, DataTypes) {
|
|||
defaultValue: 0
|
||||
},
|
||||
title: {
|
||||
type: DataTypes.TEXT
|
||||
type: DataTypes.TEXT,
|
||||
get: function () {
|
||||
return sequelize.processData(this.getDataValue('title'), "");
|
||||
},
|
||||
set: function (value) {
|
||||
this.setDataValue('title', sequelize.stripNullByte(value));
|
||||
}
|
||||
},
|
||||
content: {
|
||||
type: DataTypes.TEXT
|
||||
type: DataTypes.TEXT,
|
||||
get: function () {
|
||||
return sequelize.processData(this.getDataValue('content'), "");
|
||||
},
|
||||
set: function (value) {
|
||||
this.setDataValue('content', sequelize.stripNullByte(value));
|
||||
}
|
||||
},
|
||||
authorship: {
|
||||
type: DataTypes.TEXT
|
||||
type: DataTypes.TEXT,
|
||||
get: function () {
|
||||
return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse);
|
||||
},
|
||||
set: function (value) {
|
||||
this.setDataValue('authorship', JSON.stringify(value));
|
||||
}
|
||||
},
|
||||
lastchangeAt: {
|
||||
type: DataTypes.DATE
|
||||
|
@ -124,8 +142,6 @@ module.exports = function (sequelize, DataTypes) {
|
|||
var body = fs.readFileSync(filePath, 'utf8');
|
||||
var contentLength = body.length;
|
||||
var title = Note.parseNoteTitle(body);
|
||||
body = LZString.compressToBase64(body);
|
||||
title = LZString.compressToBase64(title);
|
||||
if (fsModifiedTime.isAfter(dbModifiedTime) && note.content !== body) {
|
||||
note.update({
|
||||
title: title,
|
||||
|
@ -135,14 +151,14 @@ module.exports = function (sequelize, DataTypes) {
|
|||
sequelize.models.Revision.saveNoteRevision(note, function (err, revision) {
|
||||
if (err) return _callback(err, null);
|
||||
// update authorship on after making revision of docs
|
||||
var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
|
||||
var patch = dmp.patch_fromText(revision.patch);
|
||||
var operations = Note.transformPatchToOperations(patch, contentLength);
|
||||
var authorship = note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : [];
|
||||
var authorship = note.authorship;
|
||||
for (var i = 0; i < operations.length; i++) {
|
||||
authorship = Note.updateAuthorshipByOperation(operations[i], null, authorship);
|
||||
}
|
||||
note.update({
|
||||
authorship: LZString.compressToBase64(JSON.stringify(authorship))
|
||||
authorship: JSON.stringify(authorship)
|
||||
}).then(function (note) {
|
||||
return callback(null, note.id);
|
||||
}).catch(function (err) {
|
||||
|
@ -264,10 +280,7 @@ module.exports = function (sequelize, DataTypes) {
|
|||
return markdown.substr(0, 100).replace(/(?:\r\n|\r|\n)/g, ' ');
|
||||
},
|
||||
decodeTitle: function (title) {
|
||||
var decodedTitle = LZString.decompressFromBase64(title);
|
||||
if (decodedTitle) title = decodedTitle;
|
||||
else title = 'Untitled';
|
||||
return title;
|
||||
return title ? title : 'Untitled';
|
||||
},
|
||||
generateWebTitle: function (title) {
|
||||
title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD";
|
||||
|
@ -496,8 +509,8 @@ module.exports = function (sequelize, DataTypes) {
|
|||
if (Note.checkFileExist(filePath)) {
|
||||
var fsCreatedTime = moment(fs.statSync(filePath).ctime);
|
||||
body = fs.readFileSync(filePath, 'utf8');
|
||||
note.title = LZString.compressToBase64(Note.parseNoteTitle(body));
|
||||
note.content = LZString.compressToBase64(body);
|
||||
note.title = Note.parseNoteTitle(body);
|
||||
note.content = body;
|
||||
if (filePath !== config.defaultnotepath) {
|
||||
note.createdAt = fsCreatedTime;
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
// external modules
|
||||
var Sequelize = require("sequelize");
|
||||
var LZString = require('lz-string');
|
||||
var async = require('async');
|
||||
var moment = require('moment');
|
||||
var childProcess = require('child_process');
|
||||
|
@ -60,19 +59,43 @@ module.exports = function (sequelize, DataTypes) {
|
|||
defaultValue: Sequelize.UUIDV4
|
||||
},
|
||||
patch: {
|
||||
type: DataTypes.TEXT
|
||||
type: DataTypes.TEXT,
|
||||
get: function () {
|
||||
return sequelize.processData(this.getDataValue('patch'), "");
|
||||
},
|
||||
set: function (value) {
|
||||
this.setDataValue('patch', sequelize.stripNullByte(value));
|
||||
}
|
||||
},
|
||||
lastContent: {
|
||||
type: DataTypes.TEXT
|
||||
type: DataTypes.TEXT,
|
||||
get: function () {
|
||||
return sequelize.processData(this.getDataValue('lastContent'), "");
|
||||
},
|
||||
set: function (value) {
|
||||
this.setDataValue('lastContent', sequelize.stripNullByte(value));
|
||||
}
|
||||
},
|
||||
content: {
|
||||
type: DataTypes.TEXT
|
||||
type: DataTypes.TEXT,
|
||||
get: function () {
|
||||
return sequelize.processData(this.getDataValue('content'), "");
|
||||
},
|
||||
set: function (value) {
|
||||
this.setDataValue('content', sequelize.stripNullByte(value));
|
||||
}
|
||||
},
|
||||
length: {
|
||||
type: DataTypes.INTEGER
|
||||
},
|
||||
authorship: {
|
||||
type: DataTypes.TEXT
|
||||
type: DataTypes.TEXT,
|
||||
get: function () {
|
||||
return sequelize.processData(this.getDataValue('authorship'), [], JSON.parse);
|
||||
},
|
||||
set: function (value) {
|
||||
this.setDataValue('authorship', value ? JSON.stringify(value) : value);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
classMethods: {
|
||||
|
@ -214,7 +237,7 @@ module.exports = function (sequelize, DataTypes) {
|
|||
Revision.create({
|
||||
noteId: note.id,
|
||||
lastContent: note.content,
|
||||
length: LZString.decompressFromBase64(note.content).length,
|
||||
length: note.content.length,
|
||||
authorship: note.authorship
|
||||
}).then(function (revision) {
|
||||
Revision.finishSaveNoteRevision(note, revision, callback);
|
||||
|
@ -223,8 +246,8 @@ module.exports = function (sequelize, DataTypes) {
|
|||
});
|
||||
} else {
|
||||
var latestRevision = revisions[0];
|
||||
var lastContent = LZString.decompressFromBase64(latestRevision.content || latestRevision.lastContent);
|
||||
var content = LZString.decompressFromBase64(note.content);
|
||||
var lastContent = latestRevision.content || latestRevision.lastContent;
|
||||
var content = note.content;
|
||||
sendDmpWorker({
|
||||
msg: 'create patch',
|
||||
lastDoc: lastContent,
|
||||
|
@ -244,9 +267,9 @@ module.exports = function (sequelize, DataTypes) {
|
|||
} else {
|
||||
Revision.create({
|
||||
noteId: note.id,
|
||||
patch: LZString.compressToBase64(patch),
|
||||
patch: patch,
|
||||
content: note.content,
|
||||
length: LZString.decompressFromBase64(note.content).length,
|
||||
length: note.content.length,
|
||||
authorship: note.authorship
|
||||
}).then(function (revision) {
|
||||
// clear last revision content to reduce db size
|
||||
|
|
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -71,7 +71,6 @@ function emitCheck(note) {
|
|||
authors: note.authors,
|
||||
authorship: note.authorship
|
||||
};
|
||||
out = LZString.compressToUTF16(JSON.stringify(out));
|
||||
realtime.io.to(note.id).emit('check', out);
|
||||
}
|
||||
|
||||
|
@ -153,12 +152,10 @@ function finishUpdateNote(note, _note, callback) {
|
|||
if (!note || !note.server) return callback(null, null);
|
||||
var body = note.server.document;
|
||||
var title = note.title = models.Note.parseNoteTitle(body);
|
||||
title = LZString.compressToBase64(title);
|
||||
body = LZString.compressToBase64(body);
|
||||
var values = {
|
||||
title: title,
|
||||
content: body,
|
||||
authorship: LZString.compressToBase64(JSON.stringify(note.authorship)),
|
||||
authorship: note.authorship,
|
||||
lastchangeuserId: note.lastchangeuser,
|
||||
lastchangeAt: Date.now()
|
||||
};
|
||||
|
@ -301,7 +298,6 @@ function emitOnlineUsers(socket) {
|
|||
var out = {
|
||||
users: users
|
||||
};
|
||||
out = LZString.compressToUTF16(JSON.stringify(out));
|
||||
realtime.io.to(noteId).emit('online users', out);
|
||||
}
|
||||
|
||||
|
@ -330,7 +326,6 @@ function emitRefresh(socket) {
|
|||
createtime: note.createtime,
|
||||
updatetime: note.updatetime
|
||||
};
|
||||
out = LZString.compressToUTF16(JSON.stringify(out));
|
||||
socket.emit('refresh', out);
|
||||
}
|
||||
|
||||
|
@ -462,7 +457,7 @@ function startConnection(socket) {
|
|||
var lastchangeuser = note.lastchangeuserId;
|
||||
var lastchangeuserprofile = note.lastchangeuser ? models.User.getProfile(note.lastchangeuser) : null;
|
||||
|
||||
var body = LZString.decompressFromBase64(note.content);
|
||||
var body = note.content;
|
||||
var createtime = note.createdAt;
|
||||
var updatetime = note.lastchangeAt;
|
||||
var server = new ot.EditorSocketIOServer(body, [], noteId, ifMayEdit, operationCallback);
|
||||
|
@ -482,7 +477,7 @@ function startConnection(socket) {
|
|||
notes[noteId] = {
|
||||
id: noteId,
|
||||
alias: note.alias,
|
||||
title: LZString.decompressFromBase64(note.title),
|
||||
title: note.title,
|
||||
owner: owner,
|
||||
ownerprofile: ownerprofile,
|
||||
permission: note.permission,
|
||||
|
@ -494,7 +489,7 @@ function startConnection(socket) {
|
|||
updatetime: moment(updatetime).valueOf(),
|
||||
server: server,
|
||||
authors: authors,
|
||||
authorship: note.authorship ? JSON.parse(LZString.decompressFromBase64(note.authorship)) : []
|
||||
authorship: note.authorship
|
||||
};
|
||||
|
||||
return finishConnection(socket, notes[noteId], users[socket.id]);
|
||||
|
@ -863,7 +858,6 @@ function connection(socket) {
|
|||
var out = {
|
||||
users: users
|
||||
};
|
||||
out = LZString.compressToUTF16(JSON.stringify(out));
|
||||
socket.emit('online users', out);
|
||||
});
|
||||
|
||||
|
|
|
@ -75,7 +75,7 @@ function showIndex(req, res, next) {
|
|||
}
|
||||
|
||||
function responseHackMD(res, note) {
|
||||
var body = LZString.decompressFromBase64(note.content);
|
||||
var body = note.content;
|
||||
var meta = null;
|
||||
try {
|
||||
meta = models.Note.parseMeta(metaMarked(body).meta);
|
||||
|
@ -191,7 +191,7 @@ function showPublishNote(req, res, next) {
|
|||
if (!note) {
|
||||
return response.errorNotFound(res);
|
||||
}
|
||||
var body = LZString.decompressFromBase64(note.content);
|
||||
var body = note.content;
|
||||
var meta = null;
|
||||
var markdown = null;
|
||||
try {
|
||||
|
@ -209,7 +209,7 @@ function showPublishNote(req, res, next) {
|
|||
var origin = config.serverurl;
|
||||
var data = {
|
||||
title: title,
|
||||
description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
|
||||
description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
|
||||
viewcount: note.viewcount,
|
||||
createtime: createtime,
|
||||
updatetime: updatetime,
|
||||
|
@ -248,7 +248,7 @@ function actionSlide(req, res, note) {
|
|||
}
|
||||
|
||||
function actionDownload(req, res, note) {
|
||||
var body = LZString.decompressFromBase64(note.content);
|
||||
var body = note.content;
|
||||
var title = models.Note.decodeTitle(note.title);
|
||||
var filename = title;
|
||||
filename = encodeURIComponent(filename);
|
||||
|
@ -265,7 +265,7 @@ function actionDownload(req, res, note) {
|
|||
}
|
||||
|
||||
function actionInfo(req, res, note) {
|
||||
var body = LZString.decompressFromBase64(note.content);
|
||||
var body = note.content;
|
||||
var meta = null;
|
||||
var markdown = null;
|
||||
try {
|
||||
|
@ -281,7 +281,7 @@ function actionInfo(req, res, note) {
|
|||
var title = models.Note.decodeTitle(note.title);
|
||||
var data = {
|
||||
title: meta.title || title,
|
||||
description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
|
||||
description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
|
||||
viewcount: note.viewcount,
|
||||
createtime: createtime,
|
||||
updatetime: updatetime
|
||||
|
@ -297,7 +297,7 @@ function actionInfo(req, res, note) {
|
|||
}
|
||||
|
||||
function actionPDF(req, res, note) {
|
||||
var body = LZString.decompressFromBase64(note.content);
|
||||
var body = note.content;
|
||||
try {
|
||||
body = metaMarked(body).markdown;
|
||||
} catch(err) {
|
||||
|
@ -479,7 +479,7 @@ function githubActionGist(req, res, note) {
|
|||
if (!error && httpResponse.statusCode == 200) {
|
||||
var access_token = body.access_token;
|
||||
if (access_token) {
|
||||
var content = LZString.decompressFromBase64(note.content);
|
||||
var content = note.content;
|
||||
var title = models.Note.decodeTitle(note.title);
|
||||
var filename = title.replace('/', ' ') + '.md';
|
||||
var gist = {
|
||||
|
@ -579,7 +579,7 @@ function showPublishSlide(req, res, next) {
|
|||
if (!note) {
|
||||
return response.errorNotFound(res);
|
||||
}
|
||||
var body = LZString.decompressFromBase64(note.content);
|
||||
var body = note.content;
|
||||
var meta = null;
|
||||
var markdown = null;
|
||||
try {
|
||||
|
@ -597,7 +597,7 @@ function showPublishSlide(req, res, next) {
|
|||
var origin = config.serverurl;
|
||||
var data = {
|
||||
title: title,
|
||||
description: meta.description || markdown ? models.Note.generateDescription(markdown) : null,
|
||||
description: meta.description || (markdown ? models.Note.generateDescription(markdown) : null),
|
||||
viewcount: note.viewcount,
|
||||
createtime: createtime,
|
||||
updatetime: updatetime,
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// external modules
|
||||
var LZString = require('lz-string');
|
||||
var DiffMatchPatch = require('diff-match-patch');
|
||||
var dmp = new DiffMatchPatch();
|
||||
|
||||
|
@ -58,7 +57,6 @@ process.on('message', function(data) {
|
|||
function createPatch(lastDoc, currDoc) {
|
||||
var ms_start = (new Date()).getTime();
|
||||
var diff = dmp.diff_main(lastDoc, currDoc);
|
||||
dmp.diff_cleanupSemantic(diff);
|
||||
var patch = dmp.patch_make(lastDoc, diff);
|
||||
patch = dmp.patch_toText(patch);
|
||||
var ms_end = (new Date()).getTime();
|
||||
|
@ -80,10 +78,10 @@ function getRevision(revisions, count) {
|
|||
for (var i = 0; i < count; i++) {
|
||||
var revision = revisions[i];
|
||||
if (i == 0) {
|
||||
startContent = LZString.decompressFromBase64(revision.content || revision.lastContent);
|
||||
startContent = revision.content || revision.lastContent;
|
||||
}
|
||||
if (i != count - 1) {
|
||||
var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
|
||||
var patch = dmp.patch_fromText(revision.patch);
|
||||
applyPatches = applyPatches.concat(patch);
|
||||
}
|
||||
lastPatch = revision.patch;
|
||||
|
@ -105,11 +103,11 @@ function getRevision(revisions, count) {
|
|||
for (var i = l; i >= count - 1; i--) {
|
||||
var revision = revisions[i];
|
||||
if (i == l) {
|
||||
startContent = LZString.decompressFromBase64(revision.lastContent);
|
||||
startContent = revision.lastContent;
|
||||
authorship = revision.authorship;
|
||||
}
|
||||
if (revision.patch) {
|
||||
var patch = dmp.patch_fromText(LZString.decompressFromBase64(revision.patch));
|
||||
var patch = dmp.patch_fromText(revision.patch);
|
||||
applyPatches = applyPatches.concat(patch);
|
||||
}
|
||||
lastPatch = revision.patch;
|
||||
|
@ -123,8 +121,8 @@ function getRevision(revisions, count) {
|
|||
}
|
||||
var data = {
|
||||
content: finalContent,
|
||||
patch: dmp.patch_fromText(LZString.decompressFromBase64(lastPatch)),
|
||||
authorship: authorship ? JSON.parse(LZString.decompressFromBase64(authorship)) : null
|
||||
patch: dmp.patch_fromText(lastPatch),
|
||||
authorship: authorship
|
||||
};
|
||||
var ms_end = (new Date()).getTime();
|
||||
if (config.debug) {
|
||||
|
|
|
@ -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 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",
|
||||
|
|
14
package.json
14
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "hackmd",
|
||||
"version": "0.4.6",
|
||||
"version": "0.5.0",
|
||||
"description": "Realtime collaborative markdown notes on all platforms.",
|
||||
"main": "app.js",
|
||||
"license": "MIT",
|
||||
|
@ -13,7 +13,7 @@
|
|||
"dependencies": {
|
||||
"Idle.Js": "github:shawnmclean/Idle.js",
|
||||
"async": "^2.1.4",
|
||||
"aws-sdk": "^2.7.15",
|
||||
"aws-sdk": "^2.7.20",
|
||||
"blueimp-md5": "^2.6.0",
|
||||
"body-parser": "^1.15.2",
|
||||
"bootstrap": "^3.3.7",
|
||||
|
@ -38,7 +38,7 @@
|
|||
"formidable": "^1.0.17",
|
||||
"gist-embed": "~2.6.0",
|
||||
"handlebars": "^4.0.6",
|
||||
"helmet": "^3.1.0",
|
||||
"helmet": "^3.3.0",
|
||||
"highlight.js": "~9.9.0",
|
||||
"i18n": "^0.8.3",
|
||||
"imgur": "git+https://github.com/hackmdio/node-imgur.git",
|
||||
|
@ -54,7 +54,7 @@
|
|||
"keymaster": "^1.6.2",
|
||||
"list.js": "^1.3.0",
|
||||
"list.pagination.js": "^0.1.1",
|
||||
"lodash": "^4.17.2",
|
||||
"lodash": "^4.17.4",
|
||||
"lz-string": "1.4.4",
|
||||
"markdown-it": "^8.2.2",
|
||||
"markdown-it-abbr": "^1.0.4",
|
||||
|
@ -98,7 +98,7 @@
|
|||
"reveal.js": "^3.3.0",
|
||||
"scrypt": "^6.0.3",
|
||||
"select2": "^3.5.2-browserify",
|
||||
"sequelize": "^3.27.0",
|
||||
"sequelize": "^3.28.0",
|
||||
"sequelize-cli": "^2.5.1",
|
||||
"shortid": "2.2.6",
|
||||
"socket.io": "~1.7.2",
|
||||
|
@ -118,7 +118,7 @@
|
|||
"vue": "^2.1.6",
|
||||
"vue-loader": "^10.0.2",
|
||||
"winston": "^2.3.0",
|
||||
"xss": "^0.3.2"
|
||||
"xss": "^0.3.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.x"
|
||||
|
@ -154,7 +154,7 @@
|
|||
"expose-loader": "^0.7.1",
|
||||
"extract-text-webpack-plugin": "^1.0.1",
|
||||
"file-loader": "^0.9.0",
|
||||
"html-webpack-plugin": "^2.24.1",
|
||||
"html-webpack-plugin": "^2.25.0",
|
||||
"imports-loader": "^0.7.0",
|
||||
"json-loader": "^0.5.4",
|
||||
"less": "^2.7.1",
|
||||
|
|
|
@ -34,7 +34,7 @@ This will automatically upload the image to **[imgur](http://imgur.com)**, nothi
|
|||
|
||||
## Share Notes:
|
||||
If you want to share an **editable** note, just copy the URL.
|
||||
If you want to share a **read-only** note, simply press share button <i class="fa fa-share-alt"></i> and copy the URL.
|
||||
If you want to share a **read-only** note, simply press publish button <i class="fa fa-share-square-o"></i> and copy the URL.
|
||||
|
||||
## Save a Note:
|
||||
Currently, you can save to **Dropbox** <i class="fa fa-dropbox"></i> or save an `.md` file <i class="fa fa-file-text"></i> locally.
|
||||
|
|
|
@ -1,6 +1,35 @@
|
|||
Release Notes
|
||||
===
|
||||
|
||||
<i class="fa fa-tag"></i> 0.5.0 `Ristretto` <i class="fa fa-clock-o"></i> 2017-01-02 02:35
|
||||
---
|
||||
### Enhancements
|
||||
* Update year to 2017 (Happy New Year!)
|
||||
* Update to improve editor performance by debounce checkEditorScrollbar event
|
||||
* Refactor data processing to model definition
|
||||
* Update to remove null byte on editor changes
|
||||
* Update to remove null byte before saving to DB
|
||||
* Update to support Esperanto locale
|
||||
* Little improvements (typos, uppercase + accents, better case) for French locale
|
||||
* Update features.md publish button name and icon
|
||||
|
||||
### Fixes
|
||||
* Fix authorship might losing update event because of throttling
|
||||
* Fix migration script of revision lacks of definition of primary key
|
||||
* Fix to not use diff_cleanupSemantic
|
||||
* Fix URL concatenation when uploading images to local filesystem
|
||||
* Fix js-url not import correctly
|
||||
* Fixed typo: anonmyous
|
||||
* Fix codemirror spell checker not considering abbreviation which contain apostrophe in word
|
||||
* Fix possible user is undefined in realtime events
|
||||
* Fix wrong package name reference in webpack config for bootstrap-validator
|
||||
* Fix email option in config not parse correctly
|
||||
* Fix mathjax not able to render issue
|
||||
|
||||
### Removes
|
||||
- Remove LZString compression for data storage
|
||||
- Remove LZString compression for some socket.io event data
|
||||
|
||||
<i class="fa fa-tag"></i> 0.4.6 `Melya` <i class="fa fa-clock-o"></i> 2016-12-19 17:20
|
||||
---
|
||||
### Features
|
||||
|
|
|
@ -12,7 +12,7 @@ window.serverurl = window.location.protocol + '//' + (domain ? domain : window.l
|
|||
var noteid = urlpath ? window.location.pathname.slice(urlpath.length + 1, window.location.pathname.length).split('/')[1] : window.location.pathname.split('/')[1];
|
||||
var noteurl = serverurl + '/' + noteid;
|
||||
|
||||
var version = '0.4.6';
|
||||
var version = '0.5.0';
|
||||
|
||||
var checkAuth = false;
|
||||
var profile = null;
|
||||
|
|
|
@ -11,7 +11,6 @@ require('highlight.js/styles/github-gist.css');
|
|||
var toMarkdown = require('to-markdown');
|
||||
|
||||
var saveAs = require('file-saver').saveAs;
|
||||
var url = require('js-url');
|
||||
var randomColor = require('randomcolor');
|
||||
|
||||
var _ = require("lodash");
|
||||
|
@ -1225,7 +1224,11 @@ function checkSyncToggle() {
|
|||
}
|
||||
}
|
||||
|
||||
function checkEditorScrollbar() {
|
||||
var checkEditorScrollbar = _.debounce(function () {
|
||||
editor.operation(checkEditorScrollbarInner);
|
||||
}, 50);
|
||||
|
||||
function checkEditorScrollbarInner() {
|
||||
// workaround simple scroll bar knob
|
||||
// will get wrong position when editor height changed
|
||||
var scrollInfo = editor.getScrollInfo();
|
||||
|
@ -2445,7 +2448,7 @@ function updateInfo(data) {
|
|||
updateAuthorship();
|
||||
}
|
||||
}
|
||||
var updateAuthorship = _.throttle(function () {
|
||||
var updateAuthorship = _.debounce(function () {
|
||||
editor.operation(updateAuthorshipInner);
|
||||
}, 50);
|
||||
function initMark() {
|
||||
|
@ -2647,8 +2650,6 @@ editor.on('update', function () {
|
|||
});
|
||||
});
|
||||
socket.on('check', function (data) {
|
||||
data = LZString.decompressFromUTF16(data);
|
||||
data = JSON.parse(data);
|
||||
//console.log(data);
|
||||
updateInfo(data);
|
||||
});
|
||||
|
@ -2658,8 +2659,6 @@ socket.on('permission', function (data) {
|
|||
var docmaxlength = null;
|
||||
var permission = null;
|
||||
socket.on('refresh', function (data) {
|
||||
data = LZString.decompressFromUTF16(data);
|
||||
data = JSON.parse(data);
|
||||
//console.log(data);
|
||||
docmaxlength = data.docmaxlength;
|
||||
editor.setOption("maxLength", docmaxlength);
|
||||
|
@ -2706,8 +2705,6 @@ var CodeMirrorAdapter = ot.CodeMirrorAdapter;
|
|||
var cmClient = null;
|
||||
|
||||
socket.on('doc', function (obj) {
|
||||
obj = LZString.decompressFromUTF16(obj);
|
||||
obj = JSON.parse(obj);
|
||||
var body = obj.str;
|
||||
var bodyMismatch = editor.getValue() !== body;
|
||||
var havePendingOperation = cmClient && Object.keys(cmClient.state).length > 0;
|
||||
|
@ -2768,8 +2765,6 @@ socket.on('operation', function () {
|
|||
});
|
||||
|
||||
socket.on('online users', function (data) {
|
||||
data = LZString.decompressFromUTF16(data);
|
||||
data = JSON.parse(data);
|
||||
if (debug)
|
||||
console.debug(data);
|
||||
onlineUsers = data.users;
|
||||
|
@ -3217,6 +3212,12 @@ function buildCursor(user) {
|
|||
}
|
||||
|
||||
//editor actions
|
||||
function removeNullByte(cm, change) {
|
||||
var str = change.text.join("\n");
|
||||
if (/\u0000/g.test(str) && change.update) {
|
||||
change.update(change.from, change.to, str.replace(/\u0000/g, "").split("\n"));
|
||||
}
|
||||
}
|
||||
function enforceMaxLength(cm, change) {
|
||||
var maxLength = cm.getOption("maxLength");
|
||||
if (maxLength && change.update) {
|
||||
|
@ -3238,6 +3239,7 @@ var ignoreEmitEvents = ['setValue', 'ignoreHistory'];
|
|||
editor.on('beforeChange', function (cm, change) {
|
||||
if (debug)
|
||||
console.debug(change);
|
||||
removeNullByte(cm, change);
|
||||
if (enforceMaxLength(cm, change)) {
|
||||
$('.limit-modal').modal('show');
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -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);
|
||||
};
|
||||
|
||||
|
|
|
@ -150,7 +150,7 @@
|
|||
<iframe src="//ghbtns.com/github-btn.html?user=hackmdio&repo=hackmd&type=star&count=true" frameborder="0" scrolling="0" width="104px" height="20px"></iframe>
|
||||
</h6>
|
||||
<p>
|
||||
© 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>
|
||||
<select class="ui-locale">
|
||||
<option value="en">English</option>
|
||||
|
@ -170,6 +170,7 @@
|
|||
<option value="uk">Українська</option>
|
||||
<option value="hi">हिन्दी</option>
|
||||
<option value="sv">svenska</option>
|
||||
<option value="eo">Esperanto</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue