Jump to 0.3.1

This commit is contained in:
Wu Cheng-Han 2015-07-02 00:10:20 +08:00
parent f7f8c901f4
commit 10c9811fc5
49 changed files with 2336 additions and 448 deletions

View file

@ -1,4 +1,4 @@
HackMD 0.2.9 HackMD 0.3.1
=== ===
HackMD is a realtime collaborative markdown notes on all platforms. HackMD is a realtime collaborative markdown notes on all platforms.
@ -7,46 +7,64 @@ Still in early stage, feel free to fork or contribute to this.
Thanks for your using! :smile: Thanks for your using! :smile:
Dependency Database dependency
--- ---
- PostgreSQL 9.3.6 or 9.4.1 - PostgreSQL 9.3.6 or 9.4.1
- MongoDB 3.0.2 - MongoDB 3.0.2
Import db schema Import database schema
--- ---
The notes are store in PostgreSQL, the schema is in the `hackmd_schema.sql` The notes are store in PostgreSQL, the schema is in the `hackmd_schema.sql`
To import the sql file in PostgreSQL, type `psql -i hackmd_schema.sql` To import the sql file in PostgreSQL, type `psql -i hackmd_schema.sql`
The users, temps and sessions are store in MongoDB, which don't need schema, so just make sure you have the correct connection string. The users, temps and sessions are store in MongoDB, which don't need schema, so just make sure you have the correct connection string.
Config Structure
---
```
hackmd/
├── logs/ --- server logs
├── backups/ --- db backups
├── tmp/ --- temporary files
├── lib/ --- server libraries
└── public/ --- client files
├── css/ --- css styles
├── js/ --- js scripts
├── vendor/ --- vendor includes
└── views/ --- view templates
```
Configure
--- ---
There are some config you need to change in below files There are some config you need to change in below files
``` ```
./run.sh ./Procfile --- for heroku start
./config.js ./run.sh --- for forever start
./public/js/common.js ./processes.json --- for pm2 start
./config.js --- for server settings
./public/js/common.js --- for client settings
./hackmd --- for logrotate
``` ```
The script `run.sh`, it's for someone like me to run the server via npm package `forever`, and can passing environment variable to the server, like heroku does. **From 0.3.1, we no longer recommend using `forever` to run your server.**
To install `forever`, just type `npm install forever -g` We using `pm2` to run server.
See [here](https://github.com/Unitech/pm2) for details.
You can use SSL to encrypt your site by passing certificate path in the `config.js` and set `usessl=true` You can use SSL to encrypt your site by passing certificate path in the `config.js` and set `usessl=true`
Run a server Run a server
--- ---
To run the server, type `bash run.sh` - forever: `bash run.sh`
Log will be at `~/.forever/hackmd.log` - pm2: `pm2 start processes.json`
Stop a server Stop a server
--- ---
To stop the server, simply type `forever stop hackmd` - forever: `forever stop hackmd`
- pm2: `pm2 stop hackmd`
Backup db Backup db
--- ---
To backup the db, type `bash backup.sh` To backup the db, type `bash backup.sh`
Backup files will be at `./backups/`
**License under MIT.** **License under MIT.**

51
app.js
View file

@ -49,13 +49,15 @@ if (config.usessl) {
var app = express(); var app = express();
var server = require('http').createServer(app); var server = require('http').createServer(app);
} }
//socket io listen
var io = require('socket.io').listen(server);
//logger //logger
app.use(morgan('combined', { app.use(morgan('combined', {
"stream": logger.stream "stream": logger.stream
})); }));
//socket io
var io = require('socket.io')(server);
// connect to the mongodb // connect to the mongodb
mongoose.connect(process.env.MONGOLAB_URI || config.mongodbstring); mongoose.connect(process.env.MONGOLAB_URI || config.mongodbstring);
@ -80,7 +82,7 @@ var sessionStore = new MongoStore({
touchAfter: config.sessiontouch touchAfter: config.sessiontouch
}, },
function (err) { function (err) {
console.log(err); logger.info(err);
}); });
//compression //compression
@ -115,12 +117,12 @@ app.use(passport.session());
//serialize and deserialize //serialize and deserialize
passport.serializeUser(function (user, done) { passport.serializeUser(function (user, done) {
//console.log('serializeUser: ' + user._id); //logger.info('serializeUser: ' + user._id);
done(null, user._id); done(null, user._id);
}); });
passport.deserializeUser(function (id, done) { passport.deserializeUser(function (id, done) {
User.model.findById(id, function (err, user) { User.model.findById(id, function (err, user) {
//console.log(user) //logger.info(user)
if (!err) done(null, user); if (!err) done(null, user);
else done(err, null); else done(err, null);
}) })
@ -163,7 +165,7 @@ app.get("/temp", function (req, res) {
}); });
temp.remove(function (err) { temp.remove(function (err) {
if (err) if (err)
console.log('remove temp failed: ' + err); logger.error('remove temp failed: ' + err);
}); });
} }
}); });
@ -182,7 +184,7 @@ app.post("/temp", urlencodedParser, function (req, res) {
response.errorForbidden(res); response.errorForbidden(res);
else { else {
if (config.debug) if (config.debug)
console.log('SERVER received temp from [' + host + ']: ' + req.body.data); logger.info('SERVER received temp from [' + host + ']: ' + req.body.data);
Temp.newTemp(id, data, function (err, temp) { Temp.newTemp(id, data, function (err, temp) {
if (!err && temp) { if (!err && temp) {
res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Origin", "*");
@ -247,7 +249,7 @@ app.get('/auth/dropbox/callback',
//logout //logout
app.get('/logout', function (req, res) { app.get('/logout', function (req, res) {
if (config.debug && req.session.passport.user) if (config.debug && req.session.passport.user)
console.log('user logout: ' + req.session.passport.user); logger.info('user logout: ' + req.session.passport.user);
req.logout(); req.logout();
res.redirect('/'); res.redirect('/');
}); });
@ -256,7 +258,7 @@ app.get('/history', function (req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
User.model.findById(req.session.passport.user, function (err, user) { User.model.findById(req.session.passport.user, function (err, user) {
if (err) { if (err) {
console.log('read history failed: ' + err); logger.error('read history failed: ' + err);
} else { } else {
var history = []; var history = [];
if (user.history) if (user.history)
@ -274,18 +276,18 @@ app.get('/history', function (req, res) {
app.post('/history', urlencodedParser, function (req, res) { app.post('/history', urlencodedParser, function (req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
if (config.debug) if (config.debug)
console.log('SERVER received history from [' + req.session.passport.user + ']: ' + req.body.history); logger.info('SERVER received history from [' + req.session.passport.user + ']: ' + req.body.history);
User.model.findById(req.session.passport.user, function (err, user) { User.model.findById(req.session.passport.user, function (err, user) {
if (err) { if (err) {
console.log('write history failed: ' + err); logger.error('write history failed: ' + err);
} else { } else {
user.history = req.body.history; user.history = req.body.history;
user.save(function (err) { user.save(function (err) {
if (err) { if (err) {
console.log('write user history failed: ' + err); logger.error('write user history failed: ' + err);
} else { } else {
if (config.debug) if (config.debug)
console.log("write user history success: " + user._id); logger.info("write user history success: " + user._id);
}; };
}); });
} }
@ -300,7 +302,7 @@ app.get('/me', function (req, res) {
if (req.isAuthenticated()) { if (req.isAuthenticated()) {
User.model.findById(req.session.passport.user, function (err, user) { User.model.findById(req.session.passport.user, function (err, user) {
if (err) { if (err) {
console.log('read me failed: ' + err); logger.error('read me failed: ' + err);
} else { } else {
var profile = JSON.parse(user.profile); var profile = JSON.parse(user.profile);
res.send({ res.send({
@ -324,20 +326,25 @@ app.post('/uploadimage', function (req, res) {
response.errorForbidden(res); response.errorForbidden(res);
} else { } else {
if (config.debug) if (config.debug)
console.log('SERVER received uploadimage: ' + JSON.stringify(files.image)); logger.info('SERVER received uploadimage: ' + JSON.stringify(files.image));
imgur.setClientId(config.imgur.clientID); imgur.setClientId(config.imgur.clientID);
try {
imgur.uploadFile(files.image.path) imgur.uploadFile(files.image.path)
.then(function (json) { .then(function (json) {
if (config.debug) if (config.debug)
console.log('SERVER uploadimage success: ' + JSON.stringify(json)); logger.info('SERVER uploadimage success: ' + JSON.stringify(json));
res.send({ res.send({
link: json.data.link link: json.data.link
}); });
}) })
.catch(function (err) { .catch(function (err) {
console.error(err); logger.error(err);
res.send(err.message); res.send('upload image error');
}); });
} catch (err) {
logger.error(err);
res.send('upload image error');
}
} }
}); });
}); });
@ -345,6 +352,10 @@ app.post('/uploadimage', function (req, res) {
app.get("/new", response.newNote); app.get("/new", response.newNote);
//get features //get features
app.get("/features", response.showFeatures); app.get("/features", response.showFeatures);
//get share note
app.get("/s/:shortid", response.showShareNote);
//share note actions
app.get("/s/:shortid/:action", response.shareNoteActions);
//get note by id //get note by id
app.get("/:noteId", response.showNote); app.get("/:noteId", response.showNote);
//note actions //note actions
@ -370,10 +381,10 @@ io.sockets.on('connection', realtime.connection);
//listen //listen
if (config.usessl) { if (config.usessl) {
server.listen(config.sslport, function () { server.listen(config.sslport, function () {
console.log('HTTPS Server listening at sslport %d', config.sslport); logger.info('HTTPS Server listening at sslport %d', config.sslport);
}); });
} else { } else {
server.listen(config.port, function () { server.listen(config.port, function () {
console.log('HTTP Server listening at port %d', config.port); logger.info('HTTP Server listening at port %d', config.port);
}); });
} }

0
backups/.keep Normal file
View file

View file

@ -11,7 +11,7 @@ var urladdport = true; //add port on getserverurl
var config = { var config = {
debug: true, debug: true,
version: '0.2.8', version: '0.3.1',
domain: domain, domain: domain,
alloworigin: ['add here to allow origin to cross'], alloworigin: ['add here to allow origin to cross'],
testport: testport, testport: testport,

11
hackmd Normal file
View file

@ -0,0 +1,11 @@
/home/hackmd/logs/*.log {
daily
rotate 365
missingok
notifempty
compress
sharedscripts
copytruncate
dateext
dateformat %Y-%m-%d
}

View file

@ -7,17 +7,18 @@ var GithubStrategy = require('passport-github').Strategy;
var DropboxStrategy = require('passport-dropbox-oauth2').Strategy; var DropboxStrategy = require('passport-dropbox-oauth2').Strategy;
//core //core
var User = require('./user.js') var User = require('./user.js');
var config = require('../config.js') var config = require('../config.js');
var logger = require("./logger.js");
function callback(accessToken, refreshToken, profile, done) { function callback(accessToken, refreshToken, profile, done) {
//console.log(profile.displayName || profile.username); //logger.info(profile.displayName || profile.username);
User.findOrNewUser(profile.id, profile, function (err, user) { User.findOrNewUser(profile.id, profile, function (err, user) {
if (err || user == null) { if (err || user == null) {
console.log('auth callback failed: ' + err); logger.error('auth callback failed: ' + err);
} else { } else {
if (config.debug && user) if (config.debug && user)
console.log('user login: ' + user._id); logger.info('user login: ' + user._id);
done(null, user); done(null, user);
} }
}); });

View file

@ -6,6 +6,7 @@ var util = require('util');
//core //core
var config = require("../config.js"); var config = require("../config.js");
var logger = require("./logger.js");
//public //public
var db = { var db = {
@ -48,18 +49,18 @@ function newToDB(id, owner, body, callback) {
if (err) { if (err) {
client.end(); client.end();
callback(err, null); callback(err, null);
return console.error('could not connect to postgres', err); return logger.error('could not connect to postgres', err);
} }
var newnotequery = util.format(insertquery, id, owner, body); var newnotequery = util.format(insertquery, id, owner, body);
//console.log(newnotequery); //logger.info(newnotequery);
client.query(newnotequery, function (err, result) { client.query(newnotequery, function (err, result) {
client.end(); client.end();
if (err) { if (err) {
callback(err, null); callback(err, null);
return console.error("new note to db failed: " + err); return logger.error("new note to db failed: " + err);
} else { } else {
if (config.debug) if (config.debug)
console.log("new note to db success"); logger.info("new note to db success");
callback(null, result); callback(null, result);
} }
}); });
@ -72,22 +73,22 @@ function readFromDB(id, callback) {
if (err) { if (err) {
client.end(); client.end();
callback(err, null); callback(err, null);
return console.error('could not connect to postgres', err); return logger.error('could not connect to postgres', err);
} }
var readquery = util.format(selectquery, id); var readquery = util.format(selectquery, id);
//console.log(readquery); //logger.info(readquery);
client.query(readquery, function (err, result) { client.query(readquery, function (err, result) {
client.end(); client.end();
if (err) { if (err) {
callback(err, null); callback(err, null);
return console.error("read from db failed: " + err); return logger.error("read from db failed: " + err);
} else { } else {
//console.log(result.rows); //logger.info(result.rows);
if (result.rows.length <= 0) { if (result.rows.length <= 0) {
callback("not found note in db: " + id, null); callback("not found note in db: " + id, null);
} else { } else {
if(config.debug) if(config.debug)
console.log("read from db success"); logger.info("read from db success");
callback(null, result); callback(null, result);
} }
} }
@ -101,18 +102,18 @@ function saveToDB(id, title, data, callback) {
if (err) { if (err) {
client.end(); client.end();
callback(err, null); callback(err, null);
return console.error('could not connect to postgres', err); return logger.error('could not connect to postgres', err);
} }
var savequery = util.format(updatequery, title, data, id); var savequery = util.format(updatequery, title, data, id);
//console.log(savequery); //logger.info(savequery);
client.query(savequery, function (err, result) { client.query(savequery, function (err, result) {
client.end(); client.end();
if (err) { if (err) {
callback(err, null); callback(err, null);
return console.error("save to db failed: " + err); return logger.error("save to db failed: " + err);
} else { } else {
if (config.debug) if (config.debug)
console.log("save to db success"); logger.info("save to db success");
callback(null, result); callback(null, result);
} }
}); });
@ -125,20 +126,20 @@ function countFromDB(callback) {
if (err) { if (err) {
client.end(); client.end();
callback(err, null); callback(err, null);
return console.error('could not connect to postgres', err); return logger.error('could not connect to postgres', err);
} }
client.query(countquery, function (err, result) { client.query(countquery, function (err, result) {
client.end(); client.end();
if (err) { if (err) {
callback(err, null); callback(err, null);
return console.error("count from db failed: " + err); return logger.error("count from db failed: " + err);
} else { } else {
//console.log(result.rows); //logger.info(result.rows);
if (result.rows.length <= 0) { if (result.rows.length <= 0) {
callback("not found note in db", null); callback("not found note in db", null);
} else { } else {
if(config.debug) if(config.debug)
console.log("count from db success"); logger.info("count from db success");
callback(null, result); callback(null, result);
} }
} }

View file

@ -7,7 +7,8 @@ var logger = new winston.Logger({
level: 'debug', level: 'debug',
handleExceptions: true, handleExceptions: true,
json: false, json: false,
colorize: true colorize: true,
timestamp: true
}) })
], ],
exitOnError: false exitOnError: false

View file

@ -1,22 +1,56 @@
//note //note
//external modules //external modules
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var LZString = require('lz-string'); var LZString = require('lz-string');
var marked = require('marked'); var marked = require('marked');
var cheerio = require('cheerio'); var cheerio = require('cheerio');
var shortId = require('shortid');
//others //others
var db = require("./db.js"); var db = require("./db.js");
var logger = require("./logger.js");
//permission types
permissionTypes = ["freely", "editable", "locked"];
// create a note model
var model = mongoose.model('note', {
id: String,
shortid: {
type: String,
unique: true,
default: shortId.generate
},
permission: {
type: String,
enum: permissionTypes
},
viewcount: {
type: Number,
default: 0
},
updated: Date,
created: Date
});
//public //public
var note = { var note = {
model: model,
findNote: findNote,
newNote: newNote,
findOrNewNote: findOrNewNote,
checkNoteIdValid: checkNoteIdValid, checkNoteIdValid: checkNoteIdValid,
checkNoteExist: checkNoteExist, checkNoteExist: checkNoteExist,
getNoteTitle: getNoteTitle getNoteTitle: getNoteTitle,
generateWebTitle: generateWebTitle,
increaseViewCount: increaseViewCount,
updatePermission: updatePermission
}; };
function checkNoteIdValid(noteId) { function checkNoteIdValid(noteId) {
try { try {
//console.log(noteId); //logger.info(noteId);
var id = LZString.decompressFromBase64(noteId); var id = LZString.decompressFromBase64(noteId);
if (!id) return false; if (!id) return false;
var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i; var uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
@ -26,21 +60,21 @@ function checkNoteIdValid(noteId) {
else else
return false; return false;
} catch (err) { } catch (err) {
console.error(err); logger.error(err);
return false; return false;
} }
} }
function checkNoteExist(noteId) { function checkNoteExist(noteId) {
try { try {
//console.log(noteId); //logger.info(noteId);
var id = LZString.decompressFromBase64(noteId); var id = LZString.decompressFromBase64(noteId);
db.readFromDB(id, function (err, result) { db.readFromDB(id, function (err, result) {
if (err) return false; if (err) return false;
return true; return true;
}); });
} catch (err) { } catch (err) {
console.error(err); logger.error(err);
return false; return false;
} }
} }
@ -50,11 +84,118 @@ function getNoteTitle(body) {
var $ = cheerio.load(marked(body)); var $ = cheerio.load(marked(body));
var h1s = $("h1"); var h1s = $("h1");
var title = ""; var title = "";
if (h1s.length > 0) if (h1s.length > 0 && h1s.first().text().split('\n').length == 1)
title = h1s.first().text(); title = h1s.first().text();
else else
title = "Untitled"; title = "Untitled";
return title; return title;
} }
//generate note web page title
function generateWebTitle(title) {
title = !title || title == "Untitled" ? "HackMD - Collaborative notes" : title + " - HackMD";
return title;
}
function findNote(id, callback) {
model.findOne({
$or: [
{
id: id
},
{
shortid: id
}
]
}, function (err, note) {
if (err) {
logger.error('find note failed: ' + err);
callback(err, null);
}
if (!err && note) {
callback(null, note);
} else {
logger.error('find note failed: ' + err);
callback(err, null);
};
});
}
function newNote(id, permission, callback) {
var note = new model({
id: id,
permission: permission,
updated: Date.now(),
created: Date.now()
});
note.save(function (err) {
if (err) {
logger.error('new note failed: ' + err);
callback(err, null);
} else {
logger.info("new note success: " + note.id);
callback(null, note);
};
});
}
function findOrNewNote(id, permission, callback) {
findNote(id, function (err, note) {
if (err || !note) {
newNote(id, permission, function (err, note) {
if (err) {
logger.error('find or new note failed: ' + err);
callback(err, null);
} else {
callback(null, note);
}
});
} else {
if (!note.permission) {
note.permission = permission;
note.updated = Date.now();
note.save(function (err) {
if (err) {
logger.error('add note permission failed: ' + err);
callback(err, null);
} else {
logger.info("add note permission success: " + note.id);
callback(null, note);
};
});
} else {
callback(null, note);
}
}
});
}
function increaseViewCount(note, callback) {
note.viewcount++;
note.updated = Date.now();
note.save(function (err) {
if (err) {
logger.error('increase note viewcount failed: ' + err);
callback(err, null);
} else {
logger.info("increase note viewcount success: " + note.id);
callback(null, note);
};
});
}
function updatePermission(note, permission, callback) {
note.permission = permission;
note.updated = Date.now();
note.save(function (err) {
if (err) {
logger.error('update note permission failed: ' + err);
callback(err, null);
} else {
logger.info("update note permission success: " + note.id);
callback(null, note);
};
});
}
module.exports = note; module.exports = note;

View file

@ -9,9 +9,12 @@ var shortId = require('shortid');
var randomcolor = require("randomcolor"); var randomcolor = require("randomcolor");
var Chance = require('chance'), var Chance = require('chance'),
chance = new Chance(); chance = new Chance();
var md5 = require("blueimp-md5").md5;
var moment = require('moment');
//core //core
var config = require("../config.js"); var config = require("../config.js");
var logger = require("./logger.js");
//others //others
var db = require("./db.js"); var db = require("./db.js");
@ -49,7 +52,7 @@ function secure(socket, next) {
next(new Error('AUTH failed: No cookie transmitted.')); next(new Error('AUTH failed: No cookie transmitted.'));
} }
if (config.debug) if (config.debug)
console.log("AUTH success cookie: " + handshakeData.sessionID); logger.info("AUTH success cookie: " + handshakeData.sessionID);
next(); next();
} catch (ex) { } catch (ex) {
@ -65,9 +68,10 @@ var updater = setInterval(function () {
var note = notes[key]; var note = notes[key];
if (note.isDirty) { if (note.isDirty) {
if (config.debug) if (config.debug)
console.log("updater found dirty note: " + key); logger.info("updater found dirty note: " + key);
var body = LZString.decompressFromUTF16(note.body); var body = LZString.decompressFromUTF16(note.body);
var title = Note.getNoteTitle(body); var title = Note.getNoteTitle(body);
title = LZString.compressToBase64(title);
body = LZString.compressToBase64(body); body = LZString.compressToBase64(body);
db.saveToDB(key, title, body, db.saveToDB(key, title, body,
function (err, result) {}); function (err, result) {});
@ -75,36 +79,45 @@ var updater = setInterval(function () {
} }
callback(); callback();
}, function (err) { }, function (err) {
if (err) return console.error('updater error', err); if (err) return logger.error('updater error', err);
}); });
}, 5000); }, 5000);
function getStatus(callback) { function getStatus(callback) {
db.countFromDB(function (err, data) { db.countFromDB(function (err, data) {
if (err) return console.log(err); if (err) return logger.info(err);
var regusers = 0;
var distinctregusers = 0;
var distinctaddresses = []; var distinctaddresses = [];
var regaddresses = [];
var distinctregaddresses = [];
Object.keys(users).forEach(function (key) { Object.keys(users).forEach(function (key) {
var value = users[key]; var user = users[key];
if (value.login)
regusers++;
var found = false; var found = false;
for (var i = 0; i < distinctaddresses.length; i++) { for (var i = 0; i < distinctaddresses.length; i++) {
if (value.address == distinctaddresses[i]) { if (user.address == distinctaddresses[i]) {
found = true; found = true;
break; break;
} }
} }
if (!found) { if (!found) {
distinctaddresses.push(value.address); distinctaddresses.push(user.address);
if (value.login) }
distinctregusers++; if (user.login) {
regaddresses.push(user.address);
var found = false;
for (var i = 0; i < distinctregaddresses.length; i++) {
if (user.address == distinctregaddresses[i]) {
found = true;
break;
}
}
if (!found) {
distinctregaddresses.push(user.address);
}
} }
}); });
User.getUserCount(function (err, regcount) { User.getUserCount(function (err, regcount) {
if (err) { if (err) {
console.log('get status failed: ' + err); logger.error('get status failed: ' + err);
return; return;
} }
if (callback) if (callback)
@ -114,8 +127,8 @@ function getStatus(callback) {
distinctOnlineUsers: distinctaddresses.length, distinctOnlineUsers: distinctaddresses.length,
notesCount: data.rows[0].count, notesCount: data.rows[0].count,
registeredUsers: regcount, registeredUsers: regcount,
onlineRegisteredUsers: regusers, onlineRegisteredUsers: regaddresses.length,
distinctOnlineRegisteredUsers: distinctregusers distinctOnlineRegisteredUsers: distinctregaddresses.length
}); });
}); });
}); });
@ -146,23 +159,40 @@ function emitOnlineUsers(socket) {
if (user) if (user)
users.push(buildUserOutData(user)); users.push(buildUserOutData(user));
}); });
notes[notename].socks.forEach(function (sock) {
var out = { var out = {
users: users users: users
}; };
out = LZString.compressToUTF16(JSON.stringify(out)); out = LZString.compressToUTF16(JSON.stringify(out));
for (var i = 0, l = notes[notename].socks.length; i < l; i++) {
var sock = notes[notename].socks[i];
if (sock && out)
sock.emit('online users', out); sock.emit('online users', out);
}); };
} }
function emitUserStatus(socket) { function emitUserStatus(socket) {
var notename = getNotenameFromSocket(socket); var notename = getNotenameFromSocket(socket);
if (!notename || !notes[notename]) return; if (!notename || !notes[notename]) return;
notes[notename].socks.forEach(function (sock) {
if (sock != socket) {
var out = buildUserOutData(users[socket.id]); var out = buildUserOutData(users[socket.id]);
for (var i = 0, l = notes[notename].socks.length; i < l; i++) {
var sock = notes[notename].socks[i];
if (sock != socket) {
sock.emit('user status', out); sock.emit('user status', out);
} }
};
}
function emitRefresh(socket) {
var notename = getNotenameFromSocket(socket);
if (!notename || !notes[notename]) return;
var note = notes[notename];
socket.emit('refresh', {
owner: note.owner,
permission: note.permission,
body: note.body,
otk: note.otk,
hash: note.hash,
updatetime: note.updatetime
}); });
} }
@ -175,9 +205,7 @@ function finishConnection(socket, notename) {
notes[notename].users[socket.id] = users[socket.id]; notes[notename].users[socket.id] = users[socket.id];
notes[notename].socks.push(socket); notes[notename].socks.push(socket);
emitOnlineUsers(socket); emitOnlineUsers(socket);
socket.emit('refresh', { emitRefresh(socket);
body: notes[notename].body
});
//clear finished socket in queue //clear finished socket in queue
for (var i = 0; i < connectionSocketQueue.length; i++) { for (var i = 0; i < connectionSocketQueue.length; i++) {
@ -190,11 +218,11 @@ function finishConnection(socket, notename) {
startConnection(connectionSocketQueue[0]); startConnection(connectionSocketQueue[0]);
if (config.debug) { if (config.debug) {
console.log('SERVER connected a client to [' + notename + ']:'); logger.info('SERVER connected a client to [' + notename + ']:');
console.log(JSON.stringify(users[socket.id])); logger.info(JSON.stringify(users[socket.id]));
//console.log(notes); //logger.info(notes);
getStatus(function (data) { getStatus(function (data) {
console.log(JSON.stringify(data)); logger.info(JSON.stringify(data));
}); });
} }
} }
@ -219,18 +247,35 @@ function startConnection(socket) {
connectionSocketQueue.splice(i, 1); connectionSocketQueue.splice(i, 1);
} }
isConnectionBusy = false; isConnectionBusy = false;
return console.error(err); return logger.error(err);
}
var owner = data.rows[0].owner;
var permission = "freely";
if (owner && owner != "null") {
permission = "editable";
}
Note.findOrNewNote(notename, permission, function (err, note) {
if (err) {
responseError(res, "404", "Not Found", "oops.");
return;
} }
var body = LZString.decompressFromBase64(data.rows[0].content); var body = LZString.decompressFromBase64(data.rows[0].content);
body = LZString.compressToUTF16(body); body = LZString.compressToUTF16(body);
var updatetime = data.rows[0].update_time;
notes[notename] = { notes[notename] = {
owner: owner,
permission: note.permission,
socks: [], socks: [],
body: body, body: body,
isDirty: false, isDirty: false,
users: {} users: {},
otk: shortId.generate(),
hash: md5(body),
updatetime: moment(updatetime).valueOf()
}; };
finishConnection(socket, notename); finishConnection(socket, notename);
}); });
});
} else { } else {
finishConnection(socket, notename); finishConnection(socket, notename);
} }
@ -241,8 +286,8 @@ function disconnect(socket) {
isDisconnectBusy = true; isDisconnectBusy = true;
if (config.debug) { if (config.debug) {
console.log("SERVER disconnected a client"); logger.info("SERVER disconnected a client");
console.log(JSON.stringify(users[socket.id])); logger.info(JSON.stringify(users[socket.id]));
} }
var notename = getNotenameFromSocket(socket); var notename = getNotenameFromSocket(socket);
if (!notename) return; if (!notename) return;
@ -251,24 +296,31 @@ function disconnect(socket) {
} }
if (notes[notename]) { if (notes[notename]) {
delete notes[notename].users[socket.id]; delete notes[notename].users[socket.id];
do {
var index = notes[notename].socks.indexOf(socket); var index = notes[notename].socks.indexOf(socket);
if (index > -1) { if (index != -1) {
notes[notename].socks.splice(index, 1); notes[notename].socks.splice(index, 1);
} }
} while (index != -1);
if (Object.keys(notes[notename].users).length <= 0) { if (Object.keys(notes[notename].users).length <= 0) {
if (notes[notename].isDirty) {
var body = LZString.decompressFromUTF16(notes[notename].body); var body = LZString.decompressFromUTF16(notes[notename].body);
var title = Note.getNoteTitle(body); var title = Note.getNoteTitle(body);
title = LZString.compressToBase64(title);
body = LZString.compressToBase64(body); body = LZString.compressToBase64(body);
db.saveToDB(notename, title, body, db.saveToDB(notename, title, body,
function (err, result) { function (err, result) {
delete notes[notename]; delete notes[notename];
if (config.debug) { if (config.debug) {
//console.log(notes); //logger.info(notes);
getStatus(function (data) { getStatus(function (data) {
console.log(JSON.stringify(data)); logger.info(JSON.stringify(data));
}); });
} }
}); });
} else {
delete notes[notename];
}
} }
} }
emitOnlineUsers(socket); emitOnlineUsers(socket);
@ -284,9 +336,9 @@ function disconnect(socket) {
disconnect(disconnectSocketQueue[0]); disconnect(disconnectSocketQueue[0]);
if (config.debug) { if (config.debug) {
//console.log(notes); //logger.info(notes);
getStatus(function (data) { getStatus(function (data) {
console.log(JSON.stringify(data)); logger.info(JSON.stringify(data));
}); });
} }
} }
@ -309,6 +361,24 @@ function updateUserData(socket, user) {
//retrieve user data from passport //retrieve user data from passport
if (socket.request.user && socket.request.user.logged_in) { if (socket.request.user && socket.request.user.logged_in) {
var profile = JSON.parse(socket.request.user.profile); var profile = JSON.parse(socket.request.user.profile);
/*
var photo = null;
switch(profile.provider) {
case "facebook":
console.log(profile);
break;
case "twitter":
photo = profile.photos[0];
break;
case "github":
photo = profile.avatar_url;
break;
case "dropbox":
//not image api provided
break;
}
user.photo = photo;
*/
user.name = profile.displayName || profile.username; user.name = profile.displayName || profile.username;
user.userid = socket.request.user._id; user.userid = socket.request.user._id;
user.login = true; user.login = true;
@ -353,7 +423,6 @@ function connection(socket) {
id: socket.id, id: socket.id,
address: socket.handshake.address, address: socket.handshake.address,
'user-agent': socket.handshake.headers['user-agent'], 'user-agent': socket.handshake.headers['user-agent'],
otk: shortId.generate(),
color: color, color: color,
cursor: null, cursor: null,
login: false, login: false,
@ -368,24 +437,41 @@ function connection(socket) {
connectionSocketQueue.push(socket); connectionSocketQueue.push(socket);
startConnection(socket); startConnection(socket);
//when a new client coming or received a client refresh request //received client refresh request
socket.on('refresh', function (body_) { socket.on('refresh', function () {
emitRefresh(socket);
});
//received client data updated
socket.on('update', function (body_) {
var notename = getNotenameFromSocket(socket); var notename = getNotenameFromSocket(socket);
if (!notename) return; if (!notename || !notes[notename]) return;
if (config.debug) if (config.debug)
console.log('SERVER received [' + notename + '] data updated: ' + socket.id); logger.info('SERVER received [' + notename + '] data updated: ' + socket.id);
if (notes[notename].body != body_) { var note = notes[notename];
notes[notename].body = body_; if (note.body != body_) {
notes[notename].isDirty = true; note.body = body_;
note.hash = md5(body_);
note.updatetime = Date.now();
note.isDirty = true;
} }
var out = {
id: socket.id,
hash: note.hash,
updatetime: note.updatetime
};
for (var i = 0, l = note.socks.length; i < l; i++) {
var sock = note.socks[i];
sock.emit('check', out);
};
}); });
//received user status //received user status
socket.on('user status', function (data) { socket.on('user status', function (data) {
var notename = getNotenameFromSocket(socket); var notename = getNotenameFromSocket(socket);
if (!notename) return; if (!notename || !notes[notename]) return;
if (config.debug) if (config.debug)
console.log('SERVER received [' + notename + '] user status from [' + socket.id + ']: ' + JSON.stringify(data)); logger.info('SERVER received [' + notename + '] user status from [' + socket.id + ']: ' + JSON.stringify(data));
if (data) { if (data) {
var user = users[socket.id]; var user = users[socket.id];
user.idle = data.idle; user.idle = data.idle;
@ -394,9 +480,40 @@ function connection(socket) {
emitUserStatus(socket); emitUserStatus(socket);
}); });
//received note permission change request
socket.on('permission', function (permission) {
//need login to do more actions
if (socket.request.user && socket.request.user.logged_in) {
var notename = getNotenameFromSocket(socket);
if (!notename || !notes[notename]) return;
var note = notes[notename];
//Only owner can change permission
if (note.owner == socket.request.user._id) {
note.permission = permission;
Note.findNote(notename, function (err, _note) {
if (err || !_note) {
return;
}
Note.updatePermission(_note, permission, function (err, _note) {
if (err || !_note) {
return;
}
var out = {
permission: permission
};
for (var i = 0, l = note.socks.length; i < l; i++) {
var sock = note.socks[i];
sock.emit('permission', out);
};
});
});
}
}
});
//reveiced when user logout or changed //reveiced when user logout or changed
socket.on('user changed', function () { socket.on('user changed', function () {
console.log('user changed'); logger.info('user changed');
var notename = getNotenameFromSocket(socket); var notename = getNotenameFromSocket(socket);
if (!notename || !notes[notename]) return; if (!notename || !notes[notename]) return;
updateUserData(socket, notes[notename].users[socket.id]); updateUserData(socket, notes[notename].users[socket.id]);
@ -431,11 +548,12 @@ function connection(socket) {
if (!notename || !notes[notename]) return; if (!notename || !notes[notename]) return;
users[socket.id].cursor = data; users[socket.id].cursor = data;
var out = buildUserOutData(users[socket.id]); var out = buildUserOutData(users[socket.id]);
notes[notename].socks.forEach(function (sock) { for (var i = 0, l = notes[notename].socks.length; i < l; i++) {
var sock = notes[notename].socks[i];
if (sock != socket) { if (sock != socket) {
sock.emit('cursor focus', out); sock.emit('cursor focus', out);
} }
}); };
}); });
//received cursor activity //received cursor activity
@ -444,11 +562,12 @@ function connection(socket) {
if (!notename || !notes[notename]) return; if (!notename || !notes[notename]) return;
users[socket.id].cursor = data; users[socket.id].cursor = data;
var out = buildUserOutData(users[socket.id]); var out = buildUserOutData(users[socket.id]);
notes[notename].socks.forEach(function (sock) { for (var i = 0, l = notes[notename].socks.length; i < l; i++) {
var sock = notes[notename].socks[i];
if (sock != socket) { if (sock != socket) {
sock.emit('cursor activity', out); sock.emit('cursor activity', out);
} }
}); };
}); });
//received cursor blur //received cursor blur
@ -459,13 +578,12 @@ function connection(socket) {
var out = { var out = {
id: socket.id id: socket.id
}; };
notes[notename].socks.forEach(function (sock) { for (var i = 0, l = notes[notename].socks.length; i < l; i++) {
if (sock != socket) { var sock = notes[notename].socks[i];
if (sock != socket) { if (sock != socket) {
sock.emit('cursor blur', out); sock.emit('cursor blur', out);
} }
} };
});
}); });
//when a new client disconnect //when a new client disconnect
@ -477,12 +595,30 @@ function connection(socket) {
//when received client change data request //when received client change data request
socket.on('change', function (op) { socket.on('change', function (op) {
var notename = getNotenameFromSocket(socket); var notename = getNotenameFromSocket(socket);
if (!notename) return; if (!notename || !notes[notename]) return;
var note = notes[notename];
switch (note.permission) {
case "freely":
//not blocking anyone
break;
case "editable":
//only login user can change
if (!socket.request.user || !socket.request.user.logged_in)
return;
break;
case "locked":
//only owner can change
if (note.owner != socket.request.user._id)
return;
break;
}
op = LZString.decompressFromUTF16(op); op = LZString.decompressFromUTF16(op);
if (op) if (op)
op = JSON.parse(op); op = JSON.parse(op);
else
return;
if (config.debug) if (config.debug)
console.log('SERVER received [' + notename + '] data changed: ' + socket.id + ', op:' + JSON.stringify(op)); logger.info('SERVER received [' + notename + '] data changed: ' + socket.id + ', op:' + JSON.stringify(op));
switch (op.origin) { switch (op.origin) {
case '+input': case '+input':
case '+delete': case '+delete':
@ -499,16 +635,20 @@ function connection(socket) {
case '+joinLines': case '+joinLines':
case '+duplicateLine': case '+duplicateLine':
case '+sortLines': case '+sortLines':
notes[notename].socks.forEach(function (sock) { op.id = socket.id;
if (sock != socket) { op.otk = note.otk;
op.nextotk = note.otk = shortId.generate();
var stringop = JSON.stringify(op);
var compressstringop = LZString.compressToUTF16(stringop);
for (var i = 0, l = note.socks.length; i < l; i++) {
var sock = note.socks[i];
if (config.debug) if (config.debug)
console.log('SERVER emit sync data out [' + notename + ']: ' + sock.id + ', op:' + JSON.stringify(op)); logger.info('SERVER emit sync data out [' + notename + ']: ' + sock.id + ', op:' + stringop);
sock.emit('change', LZString.compressToUTF16(JSON.stringify(op))); sock.emit('change', compressstringop);
} };
});
break; break;
default: default:
console.log('SERVER received uncaught [' + notename + '] data changed: ' + socket.id + ', op:' + JSON.stringify(op)); logger.info('SERVER received uncaught [' + notename + '] data changed: ' + socket.id + ', op:' + JSON.stringify(op));
} }
}); });
} }

View file

@ -6,6 +6,8 @@ var path = require('path');
var uuid = require('node-uuid'); var uuid = require('node-uuid');
var markdownpdf = require("markdown-pdf"); var markdownpdf = require("markdown-pdf");
var LZString = require('lz-string'); var LZString = require('lz-string');
var S = require('string');
var shortId = require('shortid');
//core //core
var config = require("../config.js"); var config = require("../config.js");
@ -31,16 +33,20 @@ var response = {
newNote: newNote, newNote: newNote,
showFeatures: showFeatures, showFeatures: showFeatures,
showNote: showNote, showNote: showNote,
noteActions: noteActions showShareNote: showShareNote,
noteActions: noteActions,
shareNoteActions: shareNoteActions
}; };
function responseError(res, code, detail, msg) { function responseError(res, code, detail, msg) {
res.writeHead(code, { res.writeHead(code, {
'Content-Type': 'text/html' 'Content-Type': 'text/html'
}); });
var content = ejs.render(fs.readFileSync(config.errorpath, 'utf8'), { var template = config.errorpath;
var content = ejs.render(fs.readFileSync(template, 'utf8'), {
title: code + ' ' + detail + ' ' + msg,
cache: !config.debug, cache: !config.debug,
filename: config.errorpath, filename: template,
code: code, code: code,
detail: detail, detail: detail,
msg: msg msg: msg
@ -49,16 +55,44 @@ function responseError(res, code, detail, msg) {
res.end(); res.end();
} }
function responseHackMD(res) { function responseHackMD(res, noteId) {
res.writeHead(200, { if (noteId != config.featuresnotename) {
'Content-Type': 'text/html' if (!Note.checkNoteIdValid(noteId)) {
}); responseError(res, "404", "Not Found", "oops.");
var content = ejs.render(fs.readFileSync(config.hackmdpath, 'utf8'), { return;
}
noteId = LZString.decompressFromBase64(noteId);
if (!noteId) {
responseError(res, "404", "Not Found", "oops.");
return;
}
}
db.readFromDB(noteId, function (err, data) {
if (err) {
responseError(res, "404", "Not Found", "oops.");
return;
}
var title = data.rows[0].title;
var decodedTitle = LZString.decompressFromBase64(title);
if (decodedTitle) title = decodedTitle;
title = Note.generateWebTitle(title);
var template = config.hackmdpath;
var options = {
cache: !config.debug, cache: !config.debug,
filename: config.hackmdpath filename: template
};
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options);
var html = compiled({
title: title
});
var buf = html;
res.writeHead(200, {
'Content-Type': 'text/html; charset=UTF-8',
'Cache-Control': 'private',
'Content-Length': buf.length
});
res.end(buf);
}); });
res.write(content);
res.end();
} }
function newNote(req, res, next) { function newNote(req, res, next) {
@ -88,10 +122,10 @@ function showFeatures(req, res, next) {
responseError(res, "500", "Internal Error", "wtf."); responseError(res, "500", "Internal Error", "wtf.");
return; return;
} }
responseHackMD(res); responseHackMD(res, config.featuresnotename);
}); });
} else { } else {
responseHackMD(res); responseHackMD(res, config.featuresnotename);
} }
}); });
} }
@ -102,22 +136,105 @@ function showNote(req, res, next) {
responseError(res, "404", "Not Found", "oops."); responseError(res, "404", "Not Found", "oops.");
return; return;
} }
responseHackMD(res); responseHackMD(res, noteId);
} }
function showShareNote(req, res, next) {
var shortid = req.params.shortid;
if (shortId.isValid(shortid)) {
Note.findNote(shortid, function (err, note) {
if (err || !note) {
responseError(res, "404", "Not Found", "oops.");
return;
}
//increase note viewcount
Note.increaseViewCount(note, function (err, note) {
if (err || !note) {
responseError(res, "404", "Not Found", "oops.");
return;
}
db.readFromDB(note.id, function (err, data) {
if (err) {
responseError(res, "404", "Not Found", "oops.");
return;
}
var body = LZString.decompressFromBase64(data.rows[0].content);
var updatetime = data.rows[0].update_time;
var text = S(body).escapeHTML().s;
var title = data.rows[0].title;
var decodedTitle = LZString.decompressFromBase64(title);
if (decodedTitle) title = decodedTitle;
title = Note.generateWebTitle(title);
var template = config.prettypath;
var options = {
cache: !config.debug,
filename: template
};
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options);
var origin = "//" + req.headers.host;
var html = compiled({
title: title,
viewcount: note.viewcount,
updatetime: updatetime,
url: origin,
body: text
});
var buf = html;
res.writeHead(200, {
'Content-Type': 'text/html; charset=UTF-8',
'Cache-Control': 'private',
'Content-Length': buf.length
});
res.end(buf);
});
});
});
} else {
responseError(res, "404", "Not Found", "oops.");
}
}
function actionShare(req, res, noteId) {
db.readFromDB(noteId, function (err, data) {
if (err) {
responseError(res, "404", "Not Found", "oops.");
return;
}
var owner = data.rows[0].owner;
var permission = "freely";
if (owner && owner != "null") {
permission = "editable";
}
Note.findOrNewNote(noteId, permission, function (err, note) {
if (err) {
responseError(res, "404", "Not Found", "oops.");
return;
}
res.redirect("/s/" + note.shortid);
});
});
}
//pretty api is deprecated
function actionPretty(req, res, noteId) { function actionPretty(req, res, noteId) {
db.readFromDB(noteId, function (err, data) { db.readFromDB(noteId, function (err, data) {
if (err) { if (err) {
responseError(res, "404", "Not Found", "oops."); responseError(res, "404", "Not Found", "oops.");
return; return;
} }
var body = data.rows[0].content; var body = LZString.decompressFromBase64(data.rows[0].content);
var text = S(body).escapeHTML().s;
var title = data.rows[0].title;
var decodedTitle = LZString.decompressFromBase64(title);
if (decodedTitle) title = decodedTitle;
title = Note.generateWebTitle(title);
var template = config.prettypath; var template = config.prettypath;
var compiled = ejs.compile(fs.readFileSync(template, 'utf8')); var compiled = ejs.compile(fs.readFileSync(template, 'utf8'));
var origin = "//" + req.headers.host; var origin = "//" + req.headers.host;
var html = compiled({ var html = compiled({
title: title,
url: origin, url: origin,
body: body body: text
}); });
var buf = html; var buf = html;
res.writeHead(200, { res.writeHead(200, {
@ -190,8 +307,9 @@ function noteActions(req, res, next) {
} }
var action = req.params.action; var action = req.params.action;
switch (action) { switch (action) {
case "pretty": case "share":
actionPretty(req, res, noteId); case "pretty": //pretty deprecated
actionShare(req, res, noteId);
break; break;
case "download": case "download":
actionDownload(req, res, noteId); actionDownload(req, res, noteId);
@ -208,4 +326,25 @@ function noteActions(req, res, next) {
} }
} }
function shareNoteActions(req, res, next) {
var action = req.params.action;
switch (action) {
case "edit":
var shortid = req.params.shortid;
if (shortId.isValid(shortid)) {
Note.findNote(shortid, function (err, note) {
if (err || !note) {
responseError(res, "404", "Not Found", "oops.");
return;
}
if (note.id != config.featuresnotename)
res.redirect('/' + LZString.compressToBase64(note.id));
else
res.redirect('/' + note.id);
});
}
break;
}
}
module.exports = response; module.exports = response;

View file

@ -4,6 +4,7 @@ var mongoose = require('mongoose');
//core //core
var config = require("../config.js"); var config = require("../config.js");
var logger = require("./logger.js");
// create a temp model // create a temp model
var model = mongoose.model('temp', { var model = mongoose.model('temp', {
@ -33,13 +34,13 @@ function findTemp(id, callback) {
id: id id: id
}, function (err, temp) { }, function (err, temp) {
if (err) { if (err) {
console.log('find temp failed: ' + err); logger.error('find temp failed: ' + err);
callback(err, null); callback(err, null);
} }
if (!err && temp) { if (!err && temp) {
callback(null, temp); callback(null, temp);
} else { } else {
console.log('find temp failed: ' + err); logger.error('find temp failed: ' + err);
callback(err, null); callback(err, null);
}; };
}); });
@ -53,10 +54,10 @@ function newTemp(id, data, callback) {
}); });
temp.save(function (err) { temp.save(function (err) {
if (err) { if (err) {
console.log('new temp failed: ' + err); logger.error('new temp failed: ' + err);
callback(err, null); callback(err, null);
} else { } else {
console.log("new temp success: " + temp.id); logger.info("new temp success: " + temp.id);
callback(null, temp); callback(null, temp);
}; };
}); });
@ -67,14 +68,14 @@ function removeTemp(id, callback) {
if(!err && temp) { if(!err && temp) {
temp.remove(function(err) { temp.remove(function(err) {
if(err) { if(err) {
console.log('remove temp failed: ' + err); logger.error('remove temp failed: ' + err);
callback(err, null); callback(err, null);
} else { } else {
callback(null, null); callback(null, null);
} }
}); });
} else { } else {
console.log('remove temp failed: ' + err); logger.error('remove temp failed: ' + err);
callback(err, null); callback(err, null);
} }
}); });

View file

@ -4,6 +4,7 @@ var mongoose = require('mongoose');
//core //core
var config = require("../config.js"); var config = require("../config.js");
var logger = require("./logger.js");
// create a user model // create a user model
var model = mongoose.model('user', { var model = mongoose.model('user', {
@ -34,13 +35,13 @@ function findUser(id, callback) {
id: id id: id
}, function (err, user) { }, function (err, user) {
if (err) { if (err) {
console.log('find user failed: ' + err); logger.error('find user failed: ' + err);
callback(err, null); callback(err, null);
} }
if (!err && user) { if (!err && user) {
callback(null, user); callback(null, user);
} else { } else {
console.log('find user failed: ' + err); logger.error('find user failed: ' + err);
callback(err, null); callback(err, null);
}; };
}); });
@ -54,10 +55,10 @@ function newUser(id, profile, callback) {
}); });
user.save(function (err) { user.save(function (err) {
if (err) { if (err) {
console.log('new user failed: ' + err); logger.error('new user failed: ' + err);
callback(err, null); callback(err, null);
} else { } else {
console.log("new user success: " + user.id); logger.info("new user success: " + user.id);
callback(null, user); callback(null, user);
}; };
}); });
@ -68,7 +69,7 @@ function findOrNewUser(id, profile, callback) {
if(err || !user) { if(err || !user) {
newUser(id, profile, function(err, user) { newUser(id, profile, function(err, user) {
if(err) { if(err) {
console.log('find or new user failed: ' + err); logger.error('find or new user failed: ' + err);
callback(err, null); callback(err, null);
} else { } else {
callback(null, user); callback(null, user);

0
logs/.keep Normal file
View file

View file

@ -1,6 +1,6 @@
{ {
"name": "hackmd", "name": "hackmd",
"version": "0.2.9", "version": "0.3.1",
"description": "Realtime collaborative markdown notes on all platforms.", "description": "Realtime collaborative markdown notes on all platforms.",
"main": "app.js", "main": "app.js",
"author": "jackycute", "author": "jackycute",
@ -8,6 +8,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"async": "^0.9.0", "async": "^0.9.0",
"blueimp-md5": "^1.1.0",
"body-parser": "^1.12.3", "body-parser": "^1.12.3",
"chance": "^0.7.5", "chance": "^0.7.5",
"cheerio": "^0.19.0", "cheerio": "^0.19.0",
@ -27,6 +28,7 @@
"markdown-pdf": "^5.2.0", "markdown-pdf": "^5.2.0",
"marked": "^0.3.3", "marked": "^0.3.3",
"method-override": "^2.3.2", "method-override": "^2.3.2",
"moment": "^2.10.3",
"mongoose": "^4.0.2", "mongoose": "^4.0.2",
"morgan": "^1.5.3", "morgan": "^1.5.3",
"node-uuid": "^1.4.3", "node-uuid": "^1.4.3",
@ -38,8 +40,9 @@
"passport.socketio": "^3.5.1", "passport.socketio": "^3.5.1",
"pg": "4.x", "pg": "4.x",
"randomcolor": "^0.2.0", "randomcolor": "^0.2.0",
"shortid": "2.1.3", "shortid": "2.2.2",
"socket.io": "1.3.5", "socket.io": "1.3.5",
"string": "^3.2.0",
"toobusy-js": "^0.4.1", "toobusy-js": "^0.4.1",
"winston": "^1.0.0" "winston": "^1.0.0"
}, },

19
processes.json Normal file
View file

@ -0,0 +1,19 @@
{
"apps": [{
"name": "hackmd",
"script": "app.js",
"exec_mode": "fork",
"instances": 1,
"error_file": "./logs/hackmd-err.log",
"out_file": "./logs/hackmd-out.log",
"pid_file": "./hackmd.pid",
"env": {
"NODE_ENV": "production",
"DATABASE_URL": "change this",
"MONGOLAB_URI": "change this",
"PORT": "80",
"SSLPORT": "443",
"DOMAIN": "change this"
}
}]
}

View file

@ -223,10 +223,21 @@ input {
border-radius: 5px; border-radius: 5px;
color: black; color: black;
text-shadow: none; text-shadow: none;
min-height: 134px;
display: table;
min-width: 100%;
} }
.list li .item .tags { .list li .item .content {
display: table-cell;
vertical-align: middle;
}
.list li .item .content .tags {
line-height: 25px; line-height: 25px;
} }
.list li .item .content .tags span {
display: inline-block;
line-height: 15px;
}
.form-inline { .form-inline {
padding: 0 10px; padding: 0 10px;
} }
@ -246,6 +257,10 @@ input {
.ui-history-close:hover { .ui-history-close:hover {
opacity: 1; opacity: 1;
} }
.ui-or {
margin-top: 5px;
margin-bottom: 5px;
}
.modal-title { .modal-title {
text-align: left; text-align: left;

View file

@ -13,15 +13,21 @@
} }
.vimeo .icon, .vimeo .icon,
.youtube .icon { .youtube .icon {
opacity: 0.5; opacity: 0.3;
display: table-cell; display: table-cell;
vertical-align: middle; vertical-align: middle;
height: inherit; height: inherit;
margin: 0 auto; margin: 0 auto;
color: white;
-webkit-transition: opacity 0.2s; /* Safari */
transition: opacity 0.2s;
} }
.vimeo:hover .icon, .vimeo:hover .icon,
.youtube:hover .icon { .youtube:hover .icon {
opacity: 0.6; opacity: 0.6;
-webkit-transition: opacity 0.2s; /* Safari */
transition: opacity 0.2s;
} }
h1:hover .header-link, h1:hover .header-link,
h2:hover .header-link, h2:hover .header-link,
@ -45,3 +51,167 @@ h6:hover .header-link {
-o-transition: opacity 0.2s ease-in-out 0.1s; -o-transition: opacity 0.2s ease-in-out 0.1s;
transition: opacity 0.2s ease-in-out 0.1s; transition: opacity 0.2s ease-in-out 0.1s;
} }
.ui-infobar {
max-width: 758px;
margin-top: 25px;
margin-bottom: -25px;
color: #777;
}
.ui-toc {
position: fixed;
bottom: 20px;
z-index: 10000;
}
.ui-toc-label {
opacity: 0.3;
background-color: #ccc;
border: none;
-webkit-transition: opacity 0.2s; /* Safari */
transition: opacity 0.2s;
}
.ui-toc .open .ui-toc-label {
opacity: 1;
color: white;
-webkit-transition: opacity 0.2s; /* Safari */
transition: opacity 0.2s;
}
.ui-toc-label:focus {
opacity: 0.3;
background-color: #ccc;
color: black;
}
.ui-toc-label:hover {
opacity: 1;
background-color: #ccc;
-webkit-transition: opacity 0.2s; /* Safari */
transition: opacity 0.2s;
}
.ui-toc-dropdown {
margin-top: 20px;
margin-bottom: 20px;
padding-left: 10px;
padding-right: 10px;
max-width: 45vw;
width: 25vw;
max-height: 65vh;
overflow: auto;
}
.ui-toc-dropdown a {
overflow: hidden;
text-overflow: ellipsis;
white-space: pre;
}
.ui-toc-dropdown .nav>li>a {
display: block;
padding: 4px 20px;
font-size: 13px;
font-weight: 500;
color: #767676;
}
.ui-toc-dropdown .nav>li>a:focus,.ui-toc-dropdown .nav>li>a:hover {
padding-left: 19px;
color: black;
text-decoration: none;
background-color: transparent;
border-left: 1px solid black;
}
.ui-toc-dropdown .nav>.active:focus>a,.ui-toc-dropdown .nav>.active:hover>a,.ui-toc-dropdown .nav>.active>a {
padding-left: 18px;
font-weight: 700;
color: black;
background-color: transparent;
border-left: 2px solid black;
}
.ui-toc-dropdown .nav .nav {
display: none;
padding-bottom: 10px;
}
.ui-toc-dropdown .nav>.active>ul {
display: block;
}
.ui-toc-dropdown .nav .nav>li>a {
padding-top: 1px;
padding-bottom: 1px;
padding-left: 30px;
font-size: 12px;
font-weight: 400;
}
.ui-toc-dropdown .nav .nav>li>ul>li>a {
padding-top: 1px;
padding-bottom: 1px;
padding-left: 40px;
font-size: 12px;
font-weight: 400;
}
.ui-toc-dropdown .nav .nav>li>a:focus,.ui-toc-dropdown .nav .nav>li>a:hover {
padding-left: 29px;
}
.ui-toc-dropdown .nav .nav>li>ul>li>a:focus,.ui-toc-dropdown .nav .nav>li>ul>li>a:hover {
padding-left: 39px;
}
.ui-toc-dropdown .nav .nav>.active:focus>a,.ui-toc-dropdown .nav .nav>.active:hover>a,.ui-toc-dropdown .nav .nav>.active>a {
padding-left: 28px;
font-weight: 500;
}
.ui-toc-dropdown .nav .nav>.active>.nav>.active:focus>a,.ui-toc-dropdown .nav .nav>.active>.nav>.active:hover>a,.ui-toc-dropdown .nav .nav>.active>.nav>.active>a {
padding-left: 38px;
font-weight: 500;
}
.ui-affix-toc {
position: fixed;
top: 0;
max-width: 15vw;
max-height: 70vh;
overflow: auto;
}
.back-to-top, .go-to-bottom {
display: block;
padding: 4px 10px;
margin-top: 10px;
margin-left: 10px;
font-size: 12px;
font-weight: 500;
color: #999;
}
.back-to-top:hover, .back-to-top:focus, .go-to-bottom:hover, .go-to-bottom:focus {
color: #563d7c;
text-decoration: none;
}
.go-to-bottom {
margin-top: 0;
}
small span {
line-height: 22px;
}
small .dropdown {
display: inline-block;
}
small .dropdown a:focus, small .dropdown a:hover {
text-decoration: none;
}

View file

@ -177,11 +177,11 @@ div[contenteditable]:empty:not(:focus):before{
content:attr(data-ph); content:attr(data-ph);
color: gray; color: gray;
} }
.dropdown-menu { .dropdown-menu.list {
max-height: 80vh; max-height: 80vh;
overflow: auto; overflow: auto;
} }
.dropdown-menu::-webkit-scrollbar { .dropdown-menu.list::-webkit-scrollbar {
display: none; display: none;
} }
.dropdown-menu .emoji { .dropdown-menu .emoji {
@ -201,6 +201,26 @@ div[contenteditable]:empty:not(:focus):before{
user-select: none; user-select: none;
} }
.btn-file {
position: relative;
overflow: hidden;
}
.btn-file input[type=file] {
position: absolute;
top: 0;
right: 0;
min-width: 100%;
min-height: 100%;
font-size: 100px;
text-align: right;
filter: alpha(opacity=0);
opacity: 0;
outline: none;
background: white;
cursor: inherit;
display: block;
}
.cm-trailing-space-a:before, .cm-trailing-space-a:before,
.cm-trailing-space-b:before, .cm-trailing-space-b:before,
.cm-trailing-space-new-line:before { .cm-trailing-space-new-line:before {

View file

@ -14,3 +14,9 @@ body {
::-moz-focus-inner { ::-moz-focus-inner {
border: 0 !important; border: 0 !important;
} }
/* manual fix for bootstrap issue 14040, there is an unnecessary padding-right on modal open */
body.modal-open {
overflow-y: auto;
padding-right: 0 !important;
}

View file

@ -52,7 +52,7 @@
<p class="lead"> <p class="lead">
Realtime collaborative markdown notes on all platforms. Realtime collaborative markdown notes on all platforms.
</p> </p>
<a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".bs-example-modal-sm" style="display:none;">Sign In</a> <a type="button" class="btn btn-lg btn-success ui-signin" data-toggle="modal" data-target=".signin-modal" style="display:none;">Sign In</a>
<div class="ui-or" style="display:none;">Or</div> <div class="ui-or" style="display:none;">Or</div>
<p class="lead"> <p class="lead">
<a href="/new" class="btn btn-lg btn-default">Start new note</a> <a href="/new" class="btn btn-lg btn-default">Start new note</a>
@ -66,7 +66,7 @@
<div id="history" class="section" style="display:none;"> <div id="history" class="section" style="display:none;">
<div class="ui-signin"> <div class="ui-signin">
<h4> <h4>
<a type="button" class="btn btn-success" data-toggle="modal" data-target=".bs-example-modal-sm">Sign In</a> to get own history! <a type="button" class="btn btn-success" data-toggle="modal" data-target=".signin-modal">Sign In</a> to get own history!
</h4> </h4>
<p>Below are history from browser</p> <p>Below are history from browser</p>
</div> </div>
@ -98,7 +98,7 @@
<span class="btn btn-default btn-file ui-open-history" title="Import history"> <span class="btn btn-default btn-file ui-open-history" title="Import history">
<i class="fa fa-folder-open-o"></i><input type="file" /> <i class="fa fa-folder-open-o"></i><input type="file" />
</span> </span>
<a href="#" class="btn btn-default ui-clear-history" title="Clear history"><i class="fa fa-trash-o"></i></a> <a href="#" class="btn btn-default ui-clear-history" title="Clear history" data-toggle="modal" data-target=".delete-modal"><i class="fa fa-trash-o"></i></a>
</span> </span>
<a href="#" class="btn btn-default ui-refresh-history" title="Refresh history"><i class="fa fa-refresh"></i></a> <a href="#" class="btn btn-default ui-refresh-history" title="Refresh history"><i class="fa fa-refresh"></i></a>
</form> </form>
@ -143,9 +143,7 @@
<div class="mastfoot"> <div class="mastfoot">
<div class="inner"> <div class="inner">
<h6> <h6>
<div class="fb-like" data-href="https://www.facebook.com/TakeHackMD" data-width="80" data-layout="button_count" data-action="like" data-show-faces="true" data-share="false" style="vertical-align:middle;"></div> <iframe src="//ghbtns.com/github-btn.html?user=jackycute&repo=hackmd&type=star&count=true" frameborder="0" scrolling="0" width="85px" height="20px" style="vertical-align:middle;"></iframe>
&nbsp;
<iframe src="//ghbtns.com/github-btn.html?user=jackycute&repo=hackmd&type=star&count=true" frameborder="0" scrolling="0" width="80px" height="20px" style="vertical-align:middle;"></iframe>
</h6> </h6>
<p>&copy; 2015 <a href="https://www.facebook.com/TakeHackMD" target="_blank"><i class="fa fa-facebook-square"></i> HackMD</a> by <a href="https://github.com/jackycute" target="_blank"><i class="fa fa-github-square"></i> jackycute</a> <p>&copy; 2015 <a href="https://www.facebook.com/TakeHackMD" target="_blank"><i class="fa fa-facebook-square"></i> HackMD</a> by <a href="https://github.com/jackycute" target="_blank"><i class="fa fa-github-square"></i> jackycute</a>
</p> </p>
@ -156,7 +154,7 @@
</div> </div>
<!-- signin modal --> <!-- signin modal -->
<div class="modal fade bs-example-modal-sm" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true"> <div class="modal fade signin-modal" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm"> <div class="modal-dialog modal-sm">
<div class="modal-content"> <div class="modal-content">
<div class="modal-header"> <div class="modal-header">
@ -181,12 +179,30 @@
</div> </div>
</div> </div>
</div> </div>
<div id="fb-root"></div> <!-- delete modal -->
<div class="modal fade delete-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel">Are you sure?</h4>
</div>
<div class="modal-body" style="color:black;">
<h5 class="ui-delete-modal-msg"></h5>
<strong class="ui-delete-modal-item"></strong>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">Cancel</button>
<button type="button" class="btn btn-danger ui-delete-modal-confirm">Yes, do it!</button>
</div>
</div>
</div>
</div>
<!-- Bootstrap core JavaScript <!-- Bootstrap core JavaScript
================================================== --> ================================================== -->
<!-- Placed at the end of the document so the pages load faster --> <!-- Placed at the end of the document so the pages load faster -->
<script src="/js/fb.js" async defer></script>
<script src="//code.jquery.com/jquery-1.11.3.min.js" defer></script> <script src="//code.jquery.com/jquery-1.11.3.min.js" defer></script>
<script src="/vendor/greensock-js/TweenMax.min.js" defer></script> <script src="/vendor/greensock-js/TweenMax.min.js" defer></script>
<script src="/vendor/greensock-js/jquery.gsap.min.js" defer></script> <script src="/vendor/greensock-js/jquery.gsap.min.js" defer></script>

View file

@ -4,13 +4,18 @@ var options = {
<span class="id" style="display:none;"></span>\ <span class="id" style="display:none;"></span>\
<a href="#">\ <a href="#">\
<div class="item">\ <div class="item">\
<div class="ui-history-close fa fa-close fa-fw"></div>\ <div class="ui-history-close fa fa-close fa-fw" data-toggle="modal" data-target=".delete-modal"></div>\
<div class="content">\
<h4 class="text"></h4>\ <h4 class="text"></h4>\
<p><i class="fromNow"><i class="fa fa-clock-o"></i></i>\ <p>\
<i><i class="fa fa-clock-o"></i> visit </i><i class="fromNow"></i>\
<br>\ <br>\
<i class="timestamp" style="display:none;"></i><i class="time"></i></p>\ <i class="timestamp" style="display:none;"></i>\
<i class="time"></i>\
</p>\
<p class="tags"></p>\ <p class="tags"></p>\
</div>\ </div>\
</div>\
</a>\ </a>\
</li>' </li>'
}; };
@ -114,16 +119,53 @@ function parseHistoryCallback(list, notehistory) {
$(".ui-history-close").click(function (e) { $(".ui-history-close").click(function (e) {
e.preventDefault(); e.preventDefault();
var id = $(this).closest("a").siblings("span").html(); var id = $(this).closest("a").siblings("span").html();
getHistory(function (notehistory) { var value = list.get('id', id)[0].values();
var newnotehistory = removeHistory(id, notehistory); $('.ui-delete-modal-msg').text('Do you really want to delete below history?');
saveHistory(newnotehistory); $('.ui-delete-modal-item').html('<i class="fa fa-file-text"></i> ' + value.text + '<br><i class="fa fa-clock-o"></i> ' + value.time);
}); clearHistory = false;
list.remove('id', id); deleteId = id;
checkHistoryList();
}); });
buildTagsFilter(filtertags); buildTagsFilter(filtertags);
} }
//auto update item fromNow every minutes
setInterval(updateItemFromNow, 60000);
function updateItemFromNow() {
var items = $('.item').toArray();
for (var i = 0; i < items.length; i++) {
var item = $(items[i]);
var timestamp = parseInt(item.find('.timestamp').text());
item.find('.fromNow').text(moment(timestamp).fromNow());
}
}
var clearHistory = false;
var deleteId = null;
function deleteHistory() {
if (clearHistory) {
saveHistory([]);
historyList.clear();
checkHistoryList();
} else {
if (!deleteId) return;
getHistory(function (notehistory) {
var newnotehistory = removeHistory(deleteId, notehistory);
saveHistory(newnotehistory);
});
historyList.remove('id', deleteId);
checkHistoryList();
}
$('.delete-modal').modal('hide');
clearHistory = false;
deleteId = null;
}
$(".ui-delete-modal-confirm").click(function () {
deleteHistory();
});
$(".ui-import-from-browser").click(function () { $(".ui-import-from-browser").click(function () {
saveStorageHistoryToServer(function () { saveStorageHistoryToServer(function () {
parseStorageToHistory(historyList, parseHistoryCallback); parseStorageToHistory(historyList, parseHistoryCallback);
@ -160,9 +202,10 @@ $(".ui-open-history").bind("change", function (e) {
}); });
$(".ui-clear-history").click(function () { $(".ui-clear-history").click(function () {
saveHistory([]); $('.ui-delete-modal-msg').text('Do you really want to clear all history?');
historyList.clear(); $('.ui-delete-modal-item').html('There is no turning back.');
checkHistoryList(); clearHistory = true;
deleteId = null;
}); });
$(".ui-refresh-history").click(function () { $(".ui-refresh-history").click(function () {
@ -229,6 +272,67 @@ var source = $("#template").html();
var template = Handlebars.compile(source); var template = Handlebars.compile(source);
var context = { var context = {
release: [ release: [
{
version: "0.3.1",
tag: "clearsky",
date: moment("201506301600", 'YYYYMMDDhhmm').fromNow(),
detail: [
{
title: "Features",
item: [
"+ Added auto table of content",
"+ Added basic permission control",
"+ Added view count in share note"
]
},
{
title: "Enhancements",
item: [
"* Toolbar now will hide in single view",
"* History time now will auto update",
"* Smooth scroll on anchor changed",
"* Updated video style"
]
},
{
title: "Fixes",
item: [
"* Note might not clear when all users disconnect",
"* Blockquote tag not parsed properly",
"* History style not correct"
]
}
]
},
{
version: "0.3.0",
tag: "sunrise",
date: moment("201506152400", 'YYYYMMDDhhmm').fromNow(),
detail: [
{
title: "Enhancements",
item: [
"* Used short url in share notes",
"* Added upload image button on toolbar",
"* Share notes are now SEO and mobile friendly",
"* Updated code block style",
"* Newline now will cause line breaks",
"* Image now will link out",
"* Used otk to avoid race condition",
"* Used hash to avoid data inconsistency",
"* Optimized server realtime script"
]
},
{
title: "Fixes",
item: [
"* Composition input might lost or duplicated when other input involved",
"* Note title might not save properly",
"* Todo list not render properly"
]
}
]
},
{ {
version: "0.2.9", version: "0.2.9",
tag: "wildfire", tag: "wildfire",

View file

@ -1,3 +1,15 @@
//auto update last change
var lastchangetime = null;
var lastchangeui = null;
function updateLastChange() {
if (lastchangetime && lastchangeui) {
lastchangeui.html('&nbsp;<i class="fa fa-clock-o"></i> change ' + moment(lastchangetime).fromNow());
lastchangeui.attr('title', moment(lastchangetime).format('llll'));
}
}
setInterval(updateLastChange, 60000);
//get title //get title
function getTitle(view) { function getTitle(view) {
var h1s = view.find("h1"); var h1s = view.find("h1");
@ -9,6 +21,7 @@ function getTitle(view) {
} }
return title; return title;
} }
//render title //render title
function renderTitle(view) { function renderTitle(view) {
var title = getTitle(view); var title = getTitle(view);
@ -19,6 +32,7 @@ function renderTitle(view) {
} }
return title; return title;
} }
//render filename //render filename
function renderFilename(view) { function renderFilename(view) {
var filename = getTitle(view); var filename = getTitle(view);
@ -28,16 +42,34 @@ function renderFilename(view) {
return filename; return filename;
} }
function slugifyWithUTF8(text) {
var newText = S(text.toLowerCase()).trim().stripTags().dasherize().s;
newText = newText.replace(/([\!\"\#\$\%\&\'\(\)\*\+\,\.\/\:\;\<\=\>\?\@\[\\\]\^\`\{\|\}\~])/g, '');
return newText;
}
var viewAjaxCallback = null; var viewAjaxCallback = null;
//regex for blockquote
var spaceregex = /\s*/;
var notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/;
var coloregex = /\[color=([#|\(|\)|\s|\,|\w]*?)\]/;
coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, "g");
var nameregex = /\[name=(.*?)\]/;
var timeregex = /\[time=([:|,|+|-|\(|\)|\s|\w]*?)\]/;
var nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, "g");
nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, "g");
timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, "g");
//dynamic event or object binding here //dynamic event or object binding here
function finishView(view) { function finishView(view) {
//youtube //youtube
view.find(".youtube").click(function () { view.find(".youtube.raw").removeClass("raw")
.click(function () {
imgPlayiframe(this, '//www.youtube.com/embed/'); imgPlayiframe(this, '//www.youtube.com/embed/');
}); });
//vimeo //vimeo
view.find(".vimeo") view.find(".vimeo.raw").removeClass("raw")
.click(function () { .click(function () {
imgPlayiframe(this, '//player.vimeo.com/video/'); imgPlayiframe(this, '//player.vimeo.com/video/');
}) })
@ -61,28 +93,25 @@ function finishView(view) {
//emojify //emojify
emojify.run(view[0]); emojify.run(view[0]);
//mathjax //mathjax
var mathjaxdivs = view.find('.mathjax').toArray(); var mathjaxdivs = view.find('.mathjax.raw').removeClass("raw").toArray();
try { try {
for (var i = 0; i < mathjaxdivs.length; i++) { for (var i = 0; i < mathjaxdivs.length; i++) {
MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs[i].innerHTML]); MathJax.Hub.Queue(["Typeset", MathJax.Hub, mathjaxdivs[i].innerHTML]);
MathJax.Hub.Queue(viewAjaxCallback); MathJax.Hub.Queue(viewAjaxCallback);
$(mathjaxdivs[i]).removeClass("mathjax");
}
} catch(err) {
} }
} catch (err) {}
//sequence diagram //sequence diagram
var sequence = view.find(".sequence-diagram"); var sequence = view.find(".sequence-diagram.raw").removeClass("raw");
try { try {
sequence.sequenceDiagram({ sequence.sequenceDiagram({
theme: 'simple' theme: 'simple'
}); });
sequence.parent().parent().replaceWith(sequence); sequence.parent().parent().replaceWith(sequence);
sequence.removeClass("sequence-diagram");
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
//flowchart //flowchart
var flow = view.find(".flow-chart"); var flow = view.find(".flow-chart.raw").removeClass("raw");
flow.each(function (key, value) { flow.each(function (key, value) {
try { try {
var chart = flowchart.parse($(value).text()); var chart = flowchart.parse($(value).text());
@ -94,26 +123,41 @@ function finishView(view) {
'font-family': "'Andale Mono', monospace" 'font-family': "'Andale Mono', monospace"
}); });
$(value).parent().parent().replaceWith(value); $(value).parent().parent().replaceWith(value);
$(value).removeClass("flow-chart");
} catch (err) { } catch (err) {
console.error(err); console.error(err);
} }
}); });
//image href new window(emoji not included)
var images = view.find("p > img[src]:not([class])");
images.each(function (key, value) {
var src = $(value).attr('src');
var a = $('<a>');
if (src) {
a.attr('href', src);
a.attr('target', "_blank");
}
a.html($(value).clone());
$(value).replaceWith(a);
});
//blockquote
var blockquote = view.find("blockquote.raw").removeClass("raw");
var blockquote_p = blockquote.find("p");
blockquote_p.each(function (key, value) {
var html = $(value).html();
html = html.replace(coloregex, '<span class="color" data-color="$1"></span>');
html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>');
html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>');
html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>');
$(value).html(html);
});
var blockquote_color = blockquote.find(".color");
blockquote_color.each(function (key, value) {
$(value).closest("blockquote").css('border-left-color', $(value).attr('data-color'));
});
//render title //render title
document.title = renderTitle(view); document.title = renderTitle(view);
} }
//regex for blockquote
var spaceregex = /\s*/;
var notinhtmltagregex = /(?![^<]*>|[^<>]*<\/)/;
var coloregex = /\[color=([#|\(|\)|\s|\,|\w]*)\]/;
coloregex = new RegExp(coloregex.source + notinhtmltagregex.source, "g");
var nameregex = /\[name=([-|_|\s|\w]*)\]/;
var timeregex = /\[time=([:|,|+|-|\(|\)|\s|\w]*)\]/;
var nameandtimeregex = new RegExp(nameregex.source + spaceregex.source + timeregex.source + notinhtmltagregex.source, "g");
nameregex = new RegExp(nameregex.source + notinhtmltagregex.source, "g");
timeregex = new RegExp(timeregex.source + notinhtmltagregex.source, "g");
//only static transform should be here //only static transform should be here
function postProcess(code) { function postProcess(code) {
var result = $('<div>' + code + '</div>'); var result = $('<div>' + code + '</div>');
@ -125,32 +169,107 @@ function postProcess(code) {
return "<noiframe>" + $(this).html() + "</noiframe>" return "<noiframe>" + $(this).html() + "</noiframe>"
}); });
//todo list //todo list
var lis = result[0].getElementsByTagName('li'); var lis = result.find('li.raw').removeClass("raw").sortByDepth().toArray();
for (var i = 0; i < lis.length; i++) { for (var i = 0; i < lis.length; i++) {
var html = lis[i].innerHTML; var li = lis[i];
if (/^\s*\[[x ]\]\s+/.test(html)) { var html = $(li).clone()[0].innerHTML;
lis[i].innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled>') var p = $(li).children('p');
.replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked disabled>'); if (p.length == 1) {
html = p.html();
li = p[0];
}
if (/^\s*\[[x ]\]\s*/.test(html)) {
li.innerHTML = html.replace(/^\s*\[ \]\s*/, '<input type="checkbox" class="task-list-item-checkbox" disabled><label></label>')
.replace(/^\s*\[x\]\s*/, '<input type="checkbox" class="task-list-item-checkbox" checked disabled><label></label>');
lis[i].setAttribute('class', 'task-list-item'); lis[i].setAttribute('class', 'task-list-item');
} }
} }
//blockquote
var blockquote = result.find("blockquote");
blockquote.each(function (key, value) {
var html = $(value).html();
html = html.replace(coloregex, '<span class="color" data-color="$1"></span>');
html = html.replace(nameandtimeregex, '<small><i class="fa fa-user"></i> $1 <i class="fa fa-clock-o"></i> $2</small>');
html = html.replace(nameregex, '<small><i class="fa fa-user"></i> $1</small>');
html = html.replace(timeregex, '<small><i class="fa fa-clock-o"></i> $1</small>');
$(value).html(html);
});
var blockquotecolor = result.find("blockquote .color");
blockquotecolor.each(function (key, value) {
$(value).closest("blockquote").css('border-left-color', $(value).attr('data-color'));
});
return result; return result;
} }
//jQuery sortByDepth
$.fn.sortByDepth = function () {
var ar = this.map(function () {
return {
length: $(this).parents().length,
elt: this
}
}).get(),
result = [],
i = ar.length;
ar.sort(function (a, b) {
return a.length - b.length;
});
while (i--) {
result.push(ar[i].elt);
}
return $(result);
};
//remove hash
function removeHash() {
history.pushState("", document.title, window.location.pathname + window.location.search);
}
//toc
function generateToc(id) {
var target = $('#' + id);
target.html('');
new Toc('doc', {
'level': 3,
'top': -1,
'class': 'toc',
'targetId': id
});
if(target.text() == 'undefined')
target.html('');
var backtotop = $('<a class="back-to-top" href="#">Back to top</a>');
var gotobottom = $('<a class="go-to-bottom" href="#">Go to bottom</a>');
backtotop.click(function (e) {
e.preventDefault();
e.stopPropagation();
if (scrollToTop)
scrollToTop();
removeHash();
});
gotobottom.click(function (e) {
e.preventDefault();
e.stopPropagation();
if (scrollToBottom)
scrollToBottom();
removeHash();
});
target.append(backtotop).append(gotobottom);
}
//smooth all hash trigger scrolling
function smoothHashScroll() {
var hashElements = $("a[href^='#']:not([smoothhashscroll])").toArray();
for (var i = 0; i < hashElements.length; i++) {
var element = hashElements[i];
var $element = $(element);
var hash = element.hash;
if (hash) {
$element.on('click', function (e) {
// store hash
var hash = this.hash;
if ($(hash).length <= 0) return;
// prevent default anchor click behavior
e.preventDefault();
// animate
$('html, body').animate({
scrollTop: $(hash).offset().top
}, 100, "linear", function () {
// when done, add hash to url
// (default click behaviour)
window.location.hash = hash;
});
});
$element.attr('smoothhashscroll', '');
}
}
}
function setSizebyAttr(element, target) { function setSizebyAttr(element, target) {
var width = $(element).attr("width") ? $(element).attr("width") : '100%'; var width = $(element).attr("width") ? $(element).attr("width") : '100%';
var height = $(element).attr("height") ? $(element).attr("height") : '360px'; var height = $(element).attr("height") ? $(element).attr("height") : '360px';
@ -168,10 +287,10 @@ function imgPlayiframe(element, src) {
var anchorForId = function (id) { var anchorForId = function (id) {
var anchor = document.createElement("a"); var anchor = document.createElement("a");
anchor.className = "header-link"; anchor.className = "header-link hidden-xs";
anchor.href = "#" + id; anchor.href = "#" + id;
anchor.innerHTML = "<span class=\"sr-only\">Permalink</span><i class=\"fa fa-link\"></i>"; anchor.innerHTML = "<span class=\"sr-only\"></span><i class=\"fa fa-link\"></i>";
anchor.title = "Permalink"; anchor.title = id;
return anchor; return anchor;
}; };
@ -179,13 +298,15 @@ var linkifyAnchors = function (level, containingElement) {
var headers = containingElement.getElementsByTagName("h" + level); var headers = containingElement.getElementsByTagName("h" + level);
for (var h = 0; h < headers.length; h++) { for (var h = 0; h < headers.length; h++) {
var header = headers[h]; var header = headers[h];
if (header.getElementsByClassName("header-link").length == 0) {
if (typeof header.id == "undefined" || header.id == "") { if (typeof header.id == "undefined" || header.id == "") {
var id = S(header.innerHTML.toLowerCase()).trim().stripTags().dasherize().s; //to escape characters not allow in css and humanize
header.id = encodeURIComponent(id); var id = slugifyWithUTF8(header.innerHTML);
header.id = id;
} }
header.appendChild(anchorForId(header.id)); header.appendChild(anchorForId(header.id));
} }
}
}; };
function autoLinkify(view) { function autoLinkify(view) {
@ -208,9 +329,9 @@ function highlightRender(code, lang) {
if (!lang || /no(-?)highlight|plain|text/.test(lang)) if (!lang || /no(-?)highlight|plain|text/.test(lang))
return; return;
if (lang == 'sequence') { if (lang == 'sequence') {
return '<div class="sequence-diagram">' + code + '</div>'; return '<div class="sequence-diagram raw">' + code + '</div>';
} else if (lang == 'flow') { } else if (lang == 'flow') {
return '<div class="flow-chart">' + code + '</div>'; return '<div class="flow-chart raw">' + code + '</div>';
} }
var reallang = lang.replace('=', ''); var reallang = lang.replace('=', '');
var languages = hljs.listLanguages(); var languages = hljs.listLanguages();
@ -238,10 +359,56 @@ emojify.setConfig({
var md = new Remarkable('full', { var md = new Remarkable('full', {
html: true, html: true,
breaks: true,
langPrefix: "",
linkify: true, linkify: true,
typographer: true, typographer: true,
highlight: highlightRender highlight: highlightRender
}); });
md.renderer.rules.list_item_open = function (/* tokens, idx, options, env */) {
return '<li class="raw">';
};
md.renderer.rules.blockquote_open = function (tokens, idx /*, options, env */ ) {
return '<blockquote class="raw">\n';
};
md.renderer.rules.hardbreak = function (tokens, idx, options /*, env */ ) {
return md.options.xhtmlOut ? '<br /><br />' : '<br><br>';
};
md.renderer.rules.fence = function (tokens, idx, options, env, self) {
var token = tokens[idx];
var langClass = '';
var langPrefix = options.langPrefix;
var langName = '',
fenceName;
var highlighted;
if (token.params) {
//
// ```foo bar
//
// Try custom renderer "foo" first. That will simplify overwrite
// for diagrams, latex, and any other fenced block with custom look
//
fenceName = token.params.split(/\s+/g)[0];
if (Remarkable.utils.has(self.rules.fence_custom, fenceName)) {
return self.rules.fence_custom[fenceName](tokens, idx, options, env, self);
}
langName = Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(Remarkable.utils.unescapeMd(fenceName)));
langClass = ' class="' + langPrefix + langName.replace('=', '') + ' hljs"';
}
if (options.highlight) {
highlighted = options.highlight(token.content, langName) || Remarkable.utils.escapeHtml(token.content);
} else {
highlighted = Remarkable.utils.escapeHtml(token.content);
}
return '<pre><code' + langClass + '>' + highlighted + '</code></pre>' + md.renderer.getBreak(tokens, idx);
};
//youtube //youtube
var youtubePlugin = new Plugin( var youtubePlugin = new Plugin(
// regexp to match // regexp to match
@ -251,7 +418,7 @@ var youtubePlugin = new Plugin(
function (match, utils) { function (match, utils) {
var videoid = match[1]; var videoid = match[1];
if (!videoid) return; if (!videoid) return;
var div = $('<div class="youtube"></div>'); var div = $('<div class="youtube raw"></div>');
setSizebyAttr(div, div); setSizebyAttr(div, div);
div.attr('videoid', videoid); div.attr('videoid', videoid);
var icon = '<i class="icon fa fa-youtube-play fa-5x"></i>'; var icon = '<i class="icon fa fa-youtube-play fa-5x"></i>';
@ -270,7 +437,7 @@ var vimeoPlugin = new Plugin(
function (match, utils) { function (match, utils) {
var videoid = match[1]; var videoid = match[1];
if (!videoid) return; if (!videoid) return;
var div = $('<div class="vimeo"></div>'); var div = $('<div class="vimeo raw"></div>');
setSizebyAttr(div, div); setSizebyAttr(div, div);
div.attr('videoid', videoid); div.attr('videoid', videoid);
var icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>'; var icon = '<i class="icon fa fa-vimeo-square fa-5x"></i>';
@ -298,7 +465,7 @@ var mathjaxPlugin = new Plugin(
// this function will be called when something matches // this function will be called when something matches
function (match, utils) { function (match, utils) {
//var code = $(match).text(); //var code = $(match).text();
return '<span class="mathjax">' + match[0] + '</span>'; return '<span class="mathjax raw">' + match[0] + '</span>';
} }
); );
md.use(youtubePlugin); md.use(youtubePlugin);

View file

@ -1,8 +0,0 @@
(function (d, s, id) {
var js, fjs = d.getElementsByTagName(s)[0];
if (d.getElementById(id)) return;
js = d.createElement(s);
js.id = id;
js.src = "//connect.facebook.net/en_US/sdk.js#xfbml=1&version=v2.3&appId=1436904003272070";
fjs.parentNode.insertBefore(js, fjs);
}(document, 'script', 'facebook-jssdk'));

View file

@ -319,8 +319,9 @@ function parseToHistory(list, notehistory, callback) {
else if (notehistory && notehistory.length > 0) { else if (notehistory && notehistory.length > 0) {
for (var i = 0; i < notehistory.length; i++) { for (var i = 0; i < notehistory.length; i++) {
//parse time to timestamp and fromNow //parse time to timestamp and fromNow
notehistory[i].timestamp = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').unix(); notehistory[i].timestamp = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').valueOf();
notehistory[i].fromNow = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').fromNow(); notehistory[i].fromNow = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').fromNow();
notehistory[i].time = moment(notehistory[i].time, 'MMMM Do YYYY, h:mm:ss a').format('llll');
if (list.get('id', notehistory[i].id).length == 0) if (list.get('id', notehistory[i].id).length == 0)
list.add(notehistory[i]); list.add(notehistory[i]);
} }

View file

@ -1,16 +1,21 @@
//constant vars //constant vars
//settings //settings
var debug = false; var debug = false;
var version = '0.2.9'; var version = '0.3.1';
var defaultTextHeight = 18; var defaultTextHeight = 18;
var viewportMargin = 20; var viewportMargin = 20;
var defaultExtraKeys = { var defaultExtraKeys = {
"Cmd-S": function () {
return CodeMirror.PASS
},
"Ctrl-S": function () {
return CodeMirror.PASS
},
"Enter": "newlineAndIndentContinueMarkdownList" "Enter": "newlineAndIndentContinueMarkdownList"
}; };
var idleTime = 300000; //5 mins var idleTime = 300000; //5 mins
var doneTypingDelay = 400;
var finishChangeDelay = 400; var finishChangeDelay = 400;
var cursorActivityDelay = 50; var cursorActivityDelay = 50;
var cursorAnimatePeriod = 100; var cursorAnimatePeriod = 100;
@ -97,12 +102,27 @@ var supportExternals = [
search: 'gist' search: 'gist'
} }
]; ];
var supportGenerals = [ var supportBlockquoteTags = [
{ {
text: '[name tag]',
search: '[]',
command: function () { command: function () {
return moment().format('llll'); return '[name=' + personalInfo.name + ']';
}, },
search: 'time' },
{
text: '[time tag]',
search: '[]',
command: function () {
return '[time=' + moment().format('llll') + ']';
},
},
{
text: '[color tag]',
search: '[]',
command: function () {
return '[color=' + personalInfo.color + ']';
}
} }
]; ];
var modeType = { var modeType = {
@ -131,6 +151,7 @@ var defaultMode = modeType.both;
//global vars //global vars
var loaded = false; var loaded = false;
var needRefresh = false;
var isDirty = false; var isDirty = false;
var editShown = false; var editShown = false;
var visibleXS = false; var visibleXS = false;
@ -192,7 +213,7 @@ var editor = CodeMirror.fromTextArea(textit, {
extraKeys: defaultExtraKeys, extraKeys: defaultExtraKeys,
readOnly: true readOnly: true
}); });
inlineAttachment.editors.codemirror4.attach(editor); var inlineAttach = inlineAttachment.editors.codemirror4.attach(editor);
defaultTextHeight = parseInt($(".CodeMirror").css('line-height')); defaultTextHeight = parseInt($(".CodeMirror").css('line-height'));
//ui vars //ui vars
@ -203,7 +224,7 @@ var ui = {
shortStatus: $(".ui-short-status"), shortStatus: $(".ui-short-status"),
status: $(".ui-status"), status: $(".ui-status"),
new: $(".ui-new"), new: $(".ui-new"),
pretty: $(".ui-pretty"), share: $(".ui-share"),
download: { download: {
markdown: $(".ui-download-markdown") markdown: $(".ui-download-markdown")
}, },
@ -217,7 +238,24 @@ var ui = {
mode: $(".ui-mode"), mode: $(".ui-mode"),
edit: $(".ui-edit"), edit: $(".ui-edit"),
view: $(".ui-view"), view: $(".ui-view"),
both: $(".ui-both") both: $(".ui-both"),
uploadImage: $(".ui-upload-image")
},
infobar: {
lastchange: $(".ui-lastchange"),
permission: {
permission: $(".ui-permission"),
label: $(".ui-permission-label"),
freely: $(".ui-permission-freely"),
editable: $(".ui-permission-editable"),
locked: $(".ui-permission-locked")
}
},
toc: {
toc: $('.ui-toc'),
affix: $('.ui-affix-toc'),
label: $('.ui-toc-label'),
dropdown: $('.ui-toc-dropdown')
}, },
area: { area: {
edit: $(".ui-edit-area"), edit: $(".ui-edit-area"),
@ -263,10 +301,16 @@ function idleStateChange() {
updateOnlineStatus(); updateOnlineStatus();
} }
loginStateChangeEvent = function () { function setNeedRefresh() {
location.reload(true); $('#refreshModal').modal('show');
needRefresh = true;
editor.setOption('readOnly', true);
socket.disconnect();
showStatus(statusType.offline);
} }
loginStateChangeEvent = setNeedRefresh;
//visibility //visibility
var wasFocus = false; var wasFocus = false;
Visibility.change(function (e, state) { Visibility.change(function (e, state) {
@ -315,6 +359,11 @@ $(document).ready(function () {
$body.removeClass('fixfixed'); $body.removeClass('fixfixed');
}); });
} }
//showup
$().showUp('.navbar', {
upClass: 'navbar-hide',
downClass: 'navbar-show'
});
}); });
//when page resize //when page resize
var windowResizeDelay = 200; var windowResizeDelay = 200;
@ -327,12 +376,38 @@ $(window).resize(function () {
}); });
//when page unload //when page unload
$(window).unload(function () { $(window).unload(function () {
emitRefresh(); emitUpdate();
}); });
//when page hash change
window.onhashchange = locationHashChanged;
function locationHashChanged(e) {
e.stopPropagation();
e.preventDefault();
if (currentMode != modeType.both) {
return;
}
var hashtarget = $("[id$='" + location.hash.substr(1) + "']");
if (hashtarget.length > 0) {
var linenumber = hashtarget.attr('data-startline');
if (linenumber) {
editor.setOption('viewportMargin', Infinity);
editor.setOption('viewportMargin', viewportMargin);
var t = editor.charCoords({
line: linenumber,
ch: 0
}, "local").top;
editor.scrollTo(null, t - defaultTextHeight * 1.2);
}
}
}
function windowResize() { function windowResize() {
checkResponsive(); checkResponsive();
checkEditorStyle(); checkEditorStyle();
checkTocStyle();
//refresh editor
if (loaded) { if (loaded) {
editor.setOption('viewportMargin', Infinity); editor.setOption('viewportMargin', Infinity);
setTimeout(function () { setTimeout(function () {
@ -373,6 +448,39 @@ function checkEditorStyle() {
} }
} }
function checkTocStyle() {
//toc right
var paddingRight = parseFloat(ui.area.markdown.css('padding-right'));
var right = ($(window).width() - (ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - paddingRight));
ui.toc.toc.css('right', right + 'px');
//affix toc left
var newbool;
var rightMargin = (ui.area.markdown.parent().outerWidth() - ui.area.markdown.outerWidth()) / 2;
//for ipad or wider device
if (rightMargin >= 133) {
newbool = true;
var affixLeftMargin = (ui.toc.affix.outerWidth() - ui.toc.affix.width()) / 2;
var left = ui.area.markdown.offset().left + ui.area.markdown.outerWidth() - affixLeftMargin;
ui.toc.affix.css('left', left + 'px');
} else {
newbool = false;
}
//toc scrollspy
ui.toc.toc.removeClass('scrollspy-body, scrollspy-view');
ui.toc.affix.removeClass('scrollspy-body, scrollspy-view');
if (currentMode != modeType.both && !newbool) {
ui.toc.toc.addClass('scrollspy-body');
ui.toc.affix.addClass('scrollspy-body');
} else {
ui.toc.toc.addClass('scrollspy-view');
ui.toc.affix.addClass('scrollspy-body');
}
if (newbool != enoughForAffixToc) {
enoughForAffixToc = newbool;
generateScrollspy();
}
}
function showStatus(type, num) { function showStatus(type, num) {
currentStatus = type; currentStatus = type;
var shortStatus = ui.toolbar.shortStatus; var shortStatus = ui.toolbar.shortStatus;
@ -461,12 +569,21 @@ function changeMode(type) {
} }
if (currentMode != modeType.view && visibleLG) { if (currentMode != modeType.view && visibleLG) {
//editor.focus(); //editor.focus();
editor.refresh(); //editor.refresh();
} else { } else {
editor.getInputField().blur(); editor.getInputField().blur();
} }
if (changeMode != modeType.edit) if (currentMode == modeType.edit || currentMode == modeType.both) {
ui.toolbar.uploadImage.fadeIn();
} else {
ui.toolbar.uploadImage.fadeOut();
}
if (currentMode != modeType.edit) {
$(document.body).css('background-color', 'white');
updateView(); updateView();
} else {
$(document.body).css('background-color', ui.area.codemirror.css('background-color'));
}
restoreInfo(); restoreInfo();
windowResize(); windowResize();
@ -489,10 +606,9 @@ function changeMode(type) {
} }
//button actions //button actions
var noteId = window.location.pathname.split('/')[1]; var url = window.location.pathname;
var url = window.location.origin + '/' + noteId; //share
//pretty ui.toolbar.share.attr("href", url + "/share");
ui.toolbar.pretty.attr("href", url + "/pretty");
//download //download
//markdown //markdown
ui.toolbar.download.markdown.click(function () { ui.toolbar.download.markdown.click(function () {
@ -534,6 +650,73 @@ ui.toolbar.import.dropbox.click(function () {
ui.toolbar.import.clipboard.click(function () { ui.toolbar.import.clipboard.click(function () {
//na //na
}); });
//upload image
ui.toolbar.uploadImage.bind('change', function (e) {
var files = e.target.files || e.dataTransfer.files;
e.dataTransfer = {};
e.dataTransfer.files = files;
inlineAttach.onDrop(e);
});
//toc
ui.toc.dropdown.click(function (e) {
e.stopPropagation();
});
function scrollToTop() {
if (currentMode == modeType.both) {
if (editor.getScrollInfo().top != 0)
editor.scrollTo(0, 0);
else
ui.area.view.animate({
scrollTop: 0
}, 100, "linear");
} else {
$(document.body).animate({
scrollTop: 0
}, 100, "linear");
}
}
function scrollToBottom() {
if (currentMode == modeType.both) {
var scrollInfo = editor.getScrollInfo();
var scrollHeight = scrollInfo.height;
if (scrollInfo.top != scrollHeight)
editor.scrollTo(0, scrollHeight * 2);
else
ui.area.view.animate({
scrollTop: ui.area.view[0].scrollHeight
}, 100, "linear");
} else {
$(document.body).animate({
scrollTop: $(document.body)[0].scrollHeight
}, 100, "linear");
}
}
var enoughForAffixToc = true;
//scrollspy
function generateScrollspy() {
$(document.body).scrollspy({
target: '.scrollspy-body'
});
ui.area.view.scrollspy({
target: '.scrollspy-view'
});
$(document.body).scrollspy('refresh');
ui.area.view.scrollspy('refresh');
if (enoughForAffixToc) {
ui.toc.toc.hide();
ui.toc.affix.show();
} else {
ui.toc.affix.hide();
ui.toc.toc.show();
}
$(document.body).scroll();
ui.area.view.scroll();
}
//fix for wrong autofocus //fix for wrong autofocus
$('#clipboardModal').on('shown.bs.modal', function () { $('#clipboardModal').on('shown.bs.modal', function () {
$('#clipboardModal').blur(); $('#clipboardModal').blur();
@ -549,6 +732,9 @@ $("#clipboardModalConfirm").click(function () {
$("#clipboardModalContent").html(''); $("#clipboardModalContent").html('');
} }
}); });
$('#refreshModalRefresh').click(function () {
location.reload(true);
});
function parseToEditor(data) { function parseToEditor(data) {
var parsed = toMarkdown(data); var parsed = toMarkdown(data);
@ -597,6 +783,7 @@ function isValidURL(str) {
return true; return true;
} }
} }
//mode //mode
ui.toolbar.mode.click(function () { ui.toolbar.mode.click(function () {
toggleMode(); toggleMode();
@ -613,18 +800,63 @@ ui.toolbar.view.click(function () {
ui.toolbar.both.click(function () { ui.toolbar.both.click(function () {
changeMode(modeType.both); changeMode(modeType.both);
}); });
//permission
//freely
ui.infobar.permission.freely.click(function () {
updatePermission("freely");
});
//editable
ui.infobar.permission.editable.click(function () {
updatePermission("editable");
});
//locked
ui.infobar.permission.locked.click(function () {
updatePermission("locked");
});
function updatePermission(_permission) {
if (_permission != permission) {
socket.emit('permission', _permission);
}
}
function checkPermission() {
var label = null;
var title = null;
switch (permission) {
case "freely":
label = '<i class="fa fa-leaf"></i> Freely';
title = "Anyone can edit";
break;
case "editable":
label = '<i class="fa fa-pencil"></i> Editable';
title = "Signed people can edit";
break;
case "locked":
label = '<i class="fa fa-lock"></i> Locked';
title = "Only owner can edit";
break;
}
if (personalInfo.userid == owner) {
label += ' <i class="fa fa-caret-down"></i>';
ui.infobar.permission.label.removeClass('disabled');
} else {
ui.infobar.permission.label.addClass('disabled');
}
ui.infobar.permission.label.html(label).attr('title', title);
}
//socket.io actions //socket.io actions
var socket = io.connect(); var socket = io.connect();
//overwrite original event for checking login state //overwrite original event for checking login state
var on = socket.on; var on = socket.on;
socket.on = function () { socket.on = function () {
if (!checkLoginStateChanged()) if (!checkLoginStateChanged() && !needRefresh)
on.apply(socket, arguments); on.apply(socket, arguments);
}; };
var emit = socket.emit; var emit = socket.emit;
socket.emit = function () { socket.emit = function () {
if (!checkLoginStateChanged()) if (!checkLoginStateChanged() && !needRefresh)
emit.apply(socket, arguments); emit.apply(socket, arguments);
}; };
socket.on('info', function (data) { socket.on('info', function (data) {
@ -653,17 +885,53 @@ socket.on('connect', function (data) {
}); });
socket.on('version', function (data) { socket.on('version', function (data) {
if (data != version) if (data != version)
location.reload(true); setNeedRefresh();
}); });
socket.on('check', function (data) {
if (data.id == socket.id) {
lastchangetime = data.updatetime;
lastchangeui = ui.infobar.lastchange;
updateLastChange();
return;
}
var currentHash = md5(LZString.compressToUTF16(editor.getValue()));
var hashMismatch = (currentHash != data.hash);
if (hashMismatch)
socket.emit('refresh');
else {
lastchangetime = data.updatetime;
lastchangeui = ui.infobar.lastchange;
updateLastChange();
}
});
socket.on('permission', function (data) {
permission = data.permission;
checkPermission();
});
var otk = null;
var owner = null;
var permission = null;
socket.on('refresh', function (data) { socket.on('refresh', function (data) {
var currentHash = md5(LZString.compressToUTF16(editor.getValue()));
var hashMismatch = (currentHash != data.hash);
saveInfo(); saveInfo();
otk = data.otk;
owner = data.owner;
permission = data.permission;
if (hashMismatch) {
var body = data.body; var body = data.body;
body = LZString.decompressFromUTF16(body); body = LZString.decompressFromUTF16(body);
if (body) if (body)
editor.setValue(body); editor.setValue(body);
else else
editor.setValue(""); editor.setValue("");
}
lastchangetime = data.updatetime;
lastchangeui = ui.infobar.lastchange;
updateLastChange();
if (!loaded) { if (!loaded) {
editor.clearHistory(); editor.clearHistory();
@ -673,9 +941,19 @@ socket.on('refresh', function (data) {
loaded = true; loaded = true;
emitUserStatus(); //send first user status emitUserStatus(); //send first user status
updateOnlineStatus(); //update first online status updateOnlineStatus(); //update first online status
setTimeout(function () {
//work around editor not refresh
editor.refresh();
//work around cursor not refresh
for (var i = 0; i < onlineUsers.length; i++) {
buildCursor(onlineUsers[i]);
}
//work around might not scroll to hash
scrollToHash();
}, 1);
} else { } else {
//if current doc is equal to the doc before disconnect //if current doc is equal to the doc before disconnect
if (LZString.compressToUTF16(editor.getValue()) !== data.body) if (hashMismatch)
editor.clearHistory(); editor.clearHistory();
else { else {
if (lastInfo.history) if (lastInfo.history)
@ -684,21 +962,58 @@ socket.on('refresh', function (data) {
lastInfo.history = null; lastInfo.history = null;
} }
if (hashMismatch)
updateView(); updateView();
if (editor.getOption('readOnly')) if (editor.getOption('readOnly'))
editor.setOption('readOnly', false); editor.setOption('readOnly', false);
restoreInfo(); restoreInfo();
checkPermission();
}); });
var changeStack = [];
var changeBusy = false;
socket.on('change', function (data) { socket.on('change', function (data) {
data = LZString.decompressFromUTF16(data); data = LZString.decompressFromUTF16(data);
data = JSON.parse(data); data = JSON.parse(data);
editor.replaceRange(data.text, data.from, data.to, "ignoreHistory"); changeStack.push(data);
isDirty = true; if (!changeBusy)
clearTimeout(finishChangeTimer); executeChange();
finishChangeTimer = setTimeout(finishChange, finishChangeDelay);
}); });
function executeChange() {
if (changeStack.length > 0) {
changeBusy = true;
var data = changeStack.shift();
if (data.otk != otk) {
var found = false;
for (var i = 0, l = changeStack.length; i < l; i++) {
if (changeStack[i].otk == otk) {
changeStack.unshift(data);
data = changeStack[i];
found = true;
break;
}
}
if (!found) {
socket.emit('refresh');
changeBusy = false;
return;
}
}
otk = data.nextotk;
if (data.id == personalInfo.id)
editor.replaceRange(data.text, data.from, data.to, 'self::' + data.origin);
else
editor.replaceRange(data.text, data.from, data.to, "ignoreHistory");
executeChange();
} else {
changeBusy = false;
}
}
socket.on('online users', function (data) { socket.on('online users', function (data) {
data = LZString.decompressFromUTF16(data); data = LZString.decompressFromUTF16(data);
data = JSON.parse(data); data = JSON.parse(data);
@ -795,7 +1110,7 @@ var onlineUserList = new List('online-user-list', options);
var shortOnlineUserList = new List('short-online-user-list', options); var shortOnlineUserList = new List('short-online-user-list', options);
function updateOnlineStatus() { function updateOnlineStatus() {
if (!loaded) return; if (!loaded || !socket.connected) return;
var _onlineUsers = deduplicateOnlineUsers(onlineUsers); var _onlineUsers = deduplicateOnlineUsers(onlineUsers);
showStatus(statusType.online, _onlineUsers.length); showStatus(statusType.online, _onlineUsers.length);
var items = onlineUserList.items; var items = onlineUserList.items;
@ -975,7 +1290,7 @@ function checkCursorTag(coord, ele) {
var offsetTop = defaultTextHeight; var offsetTop = defaultTextHeight;
if (width > 0 && height > 0) { if (width > 0 && height > 0) {
if (left + width + offsetLeft > editorWidth - curosrtagMargin) { if (left + width + offsetLeft > editorWidth - curosrtagMargin) {
offsetLeft = -(width + 4); offsetLeft = -(width + 10);
} }
if (top + height + offsetTop > Math.max(viewportHeight, editorHeight) + curosrtagMargin && top - height > curosrtagMargin) { if (top + height + offsetTop > Math.max(viewportHeight, editorHeight) + curosrtagMargin && top - height > curosrtagMargin) {
offsetTop = -(height); offsetTop = -(height);
@ -1124,16 +1439,61 @@ function buildCursor(user) {
editor.on('beforeChange', function (cm, change) { editor.on('beforeChange', function (cm, change) {
if (debug) if (debug)
console.debug(change); console.debug(change);
var self = change.origin.split('self::');
if (self.length == 2) {
change.origin = self[1];
self = true;
} else {
self = false;
}
if (self) {
change.canceled = true;
} else {
var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(change.origin) != -1);
if (!isIgnoreEmitEvent) {
switch (permission) {
case "freely":
//na
break;
case "editable":
if (!personalInfo.login) {
change.canceled = true;
$('.signin-modal').modal('show');
}
break;
case "locked":
if (personalInfo.userid != owner) {
change.canceled = true;
$('.locked-modal').modal('show');
}
break;
}
}
}
}); });
var ignoreEmitEvents = ['setValue', 'ignoreHistory'];
editor.on('change', function (i, op) { editor.on('change', function (i, op) {
if (debug) if (debug)
console.debug(op); console.debug(op);
if (op.origin != 'setValue' && op.origin != 'ignoreHistory') { var isIgnoreEmitEvent = (ignoreEmitEvents.indexOf(op.origin) != -1);
socket.emit('change', LZString.compressToUTF16(JSON.stringify(op))); if (!isIgnoreEmitEvent) {
var out = {
text: op.text,
from: op.from,
to: op.to,
origin: op.origin
};
socket.emit('change', LZString.compressToUTF16(JSON.stringify(out)));
} }
isDirty = true; isDirty = true;
clearTimeout(doneTypingTimer); clearTimeout(finishChangeTimer);
doneTypingTimer = setTimeout(doneTyping, doneTypingDelay); finishChangeTimer = setTimeout(function () {
if (!isIgnoreEmitEvent)
finishChange(true);
else
finishChange(false);
}, finishChangeDelay);
}); });
editor.on('focus', function (cm) { editor.on('focus', function (cm) {
for (var i = 0; i < onlineUsers.length; i++) { for (var i = 0; i < onlineUsers.length; i++) {
@ -1236,22 +1596,17 @@ function restoreInfo() {
} }
//view actions //view actions
var doneTypingTimer = null;
var finishChangeTimer = null; var finishChangeTimer = null;
var input = editor.getInputField();
//user is "finished typing," do something function finishChange(emit) {
function doneTyping() { if (emit)
emitRefresh(); emitUpdate();
updateView(); updateView();
} }
function finishChange() { function emitUpdate() {
updateView();
}
function emitRefresh() {
var value = editor.getValue(); var value = editor.getValue();
socket.emit('refresh', LZString.compressToUTF16(value)); socket.emit('update', LZString.compressToUTF16(value));
} }
var lastResult = null; var lastResult = null;
@ -1267,6 +1622,11 @@ function updateView() {
updateDataAttrs(result, ui.area.markdown.children().toArray()); updateDataAttrs(result, ui.area.markdown.children().toArray());
lastResult = $(result).clone(); lastResult = $(result).clone();
finishView(ui.area.view); finishView(ui.area.view);
autoLinkify(ui.area.view);
generateToc('toc');
generateToc('toc-affix');
generateScrollspy();
smoothHashScroll();
writeHistory(ui.area.markdown); writeHistory(ui.area.markdown);
isDirty = false; isDirty = false;
clearMap(); clearMap();
@ -1535,7 +1895,7 @@ $(editor.getInputField())
return '$1```' + lang + '=\n\n```'; return '$1```' + lang + '=\n\n```';
}, },
done: function () { done: function () {
editor.doc.cm.moveV(-1, "line"); editor.doc.cm.execCommand("goLineUp");
}, },
context: function () { context: function () {
return isInCode; return isInCode;
@ -1555,6 +1915,28 @@ $(editor.getInputField())
context: function (text) { context: function (text) {
return !isInCode; return !isInCode;
} }
},
{ //blockquote personal info & general info
match: /(?:^|\n|\s)(\>.*)(\[\])(\w*)$/,
search: function (term, callback) {
var list = [];
$.map(supportBlockquoteTags, function (blockquotetag) {
if (blockquotetag.search.indexOf(term) === 0)
list.push(blockquotetag.command());
});
$.map(supportReferrals, function (referral) {
if (referral.search.indexOf(term) === 0)
list.push(referral.text);
})
callback(list);
checkCursorMenu();
},
replace: function (value) {
return '$1' + value;
},
context: function (text) {
return !isInCode;
}
}, },
{ //referral { //referral
match: /(^|\n|\s)(\!|\!|\[\])(\w*)$/, match: /(^|\n|\s)(\!|\!|\[\])(\w*)$/,
@ -1585,41 +1967,6 @@ $(editor.getInputField())
context: function (text) { context: function (text) {
return !isInCode; return !isInCode;
} }
},
{ //blockquote personal info & general info
match: /(^|\n|\s|\>.*)\[(\w*)=$/,
search: function (term, callback) {
var list = typeof personalInfo[term] != 'undefined' ? [personalInfo[term]] : [];
$.map(supportGenerals, function (general) {
if (general.search.indexOf(term) === 0)
list.push(general.command());
});
callback(list);
checkCursorMenu();
},
replace: function (value) {
return '$1[$2=' + value;
},
context: function (text) {
return !isInCode;
}
},
{ //blockquote quick start tag
match: /(^.*(?!>)\n|)(\>\s{0,1})$/,
search: function (term, callback) {
var self = '[name=' + personalInfo.name + '] [time=' + moment().format('llll') + '] [color=' + personalInfo.color + ']';
callback([self]);
checkCursorMenu();
},
template: function (value) {
return '[Your name, time, color tags]';
},
replace: function (value) {
return '$1$2' + value;
},
context: function (text) {
return !isInCode;
}
} }
], { ], {
appendTo: $('.cursor-menu') appendTo: $('.cursor-menu')

View file

@ -1,9 +1,81 @@
var raw = $(".markdown-body").text();
var markdown = LZString.decompressFromBase64(raw);
var result = postProcess(md.render(markdown));
var markdown = $(".markdown-body"); var markdown = $(".markdown-body");
markdown.html(result); var text = $('<textarea/>').html(markdown.html()).text();
markdown.show(); var result = postProcess(md.render(text));
markdown.html(result.html());
$(document.body).show();
finishView(markdown); finishView(markdown);
autoLinkify(markdown); autoLinkify(markdown);
scrollToHash(); generateToc('toc');
generateToc('toc-affix');
smoothHashScroll();
lastchangetime = $('.ui-lastchange').text();
lastchangeui = $('.ui-lastchange');
updateLastChange();
var url = window.location.pathname;
$('.ui-edit').attr('href', url + '/edit');
var toc = $('.ui-toc');
var tocAffix = $('.ui-affix-toc');
var tocDropdown = $('.ui-toc-dropdown');
//toc
tocDropdown.click(function (e) {
e.stopPropagation();
});
var enoughForAffixToc = true;
function generateScrollspy() {
$(document.body).scrollspy({
target: ''
});
$(document.body).scrollspy('refresh');
if (enoughForAffixToc) {
toc.hide();
tocAffix.show();
} else {
tocAffix.hide();
toc.show();
}
$(document.body).scroll();
}
function windowResize() {
//toc right
var paddingRight = parseFloat(markdown.css('padding-right'));
var right = ($(window).width() - (markdown.offset().left + markdown.outerWidth() - paddingRight));
toc.css('right', right + 'px');
//affix toc left
var newbool;
var rightMargin = (markdown.parent().outerWidth() - markdown.outerWidth()) / 2;
//for ipad or wider device
if (rightMargin >= 133) {
newbool = true;
var affixLeftMargin = (tocAffix.outerWidth() - tocAffix.width()) / 2;
var left = markdown.offset().left + markdown.outerWidth() - affixLeftMargin;
tocAffix.css('left', left + 'px');
} else {
newbool = false;
}
if (newbool != enoughForAffixToc) {
enoughForAffixToc = newbool;
generateScrollspy();
}
}
$(window).resize(function () {
windowResize();
});
$(document).ready(function () {
windowResize();
generateScrollspy();
});
function scrollToTop() {
$(document.body).animate({
scrollTop: 0
}, 100, "linear");
}
function scrollToBottom() {
$(document.body).animate({
scrollTop: $(document.body)[0].scrollHeight
}, 100, "linear");
}

View file

@ -8,7 +8,7 @@ md.renderer.rules.blockquote_open = function (tokens, idx /*, options, env */ )
if (tokens[idx].lines && tokens[idx].level === 0) { if (tokens[idx].lines && tokens[idx].level === 0) {
var startline = tokens[idx].lines[0] + 1; var startline = tokens[idx].lines[0] + 1;
var endline = tokens[idx].lines[1]; var endline = tokens[idx].lines[1];
return '<blockquote class="part" data-startline="' + startline + '" data-endline="' + endline + '">\n'; return '<blockquote class="raw part" data-startline="' + startline + '" data-endline="' + endline + '">\n';
} }
return '<blockquote>\n'; return '<blockquote>\n';
}; };
@ -55,9 +55,9 @@ md.renderer.rules.paragraph_open = function (tokens, idx) {
if (tokens[idx].lines && tokens[idx].level === 0) { if (tokens[idx].lines && tokens[idx].level === 0) {
var startline = tokens[idx].lines[0] + 1; var startline = tokens[idx].lines[0] + 1;
var endline = tokens[idx].lines[1]; var endline = tokens[idx].lines[1];
return '<p class="part" data-startline="' + startline + '" data-endline="' + endline + '">'; return tokens[idx].tight ? '' : '<p class="part" data-startline="' + startline + '" data-endline="' + endline + '">';
} }
return ''; return tokens[idx].tight ? '' : '<p>';
}; };
md.renderer.rules.heading_open = function (tokens, idx) { md.renderer.rules.heading_open = function (tokens, idx) {
@ -106,7 +106,7 @@ md.renderer.rules.fence = function (tokens, idx, options, env, self) {
} }
langName = Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(Remarkable.utils.unescapeMd(fenceName))); langName = Remarkable.utils.escapeHtml(Remarkable.utils.replaceEntities(Remarkable.utils.unescapeMd(fenceName)));
langClass = ' class="' + langPrefix + langName + '"'; langClass = ' class="' + langPrefix + langName.replace('=', '') + ' hljs"';
} }
if (options.highlight) { if (options.highlight) {
@ -193,7 +193,8 @@ function buildMapInner(syncBack) {
'line-height': textarea.css('line-height'), 'line-height': textarea.css('line-height'),
'word-wrap': wrap.css('word-wrap'), 'word-wrap': wrap.css('word-wrap'),
'white-space': wrap.css('white-space'), 'white-space': wrap.css('white-space'),
'word-break': wrap.css('word-break') 'word-break': wrap.css('word-break'),
'tab-size': '38px'
}).appendTo('body'); }).appendTo('body');
offset = ui.area.view.scrollTop() - ui.area.view.offset().top; offset = ui.area.view.scrollTop() - ui.area.view.offset().top;
@ -313,7 +314,7 @@ function syncScrollToView(event, _lineNo) {
var textHeight = editor.defaultTextHeight(); var textHeight = editor.defaultTextHeight();
lineNo = Math.floor(scrollInfo.top / textHeight); lineNo = Math.floor(scrollInfo.top / textHeight);
//if reach bottom, then scroll to end //if reach bottom, then scroll to end
if (scrollInfo.top + scrollInfo.clientHeight >= scrollInfo.height - defaultTextHeight) { if (scrollInfo.height > scrollInfo.clientHeight && scrollInfo.top + scrollInfo.clientHeight >= scrollInfo.height - defaultTextHeight) {
posTo = ui.area.view[0].scrollHeight - ui.area.view.height(); posTo = ui.area.view[0].scrollHeight - ui.area.view.height();
} else { } else {
topDiffPercent = (scrollInfo.top % textHeight) / textHeight; topDiffPercent = (scrollInfo.top % textHeight) / textHeight;

View file

@ -57,7 +57,7 @@
cm.operation(function() { cm.operation(function() {
for (var i = ranges.length - 1; i >= 0; i--) for (var i = ranges.length - 1; i >= 0; i--)
cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+insert"); cm.replaceRange(inserts[i], ranges[i].from(), ranges[i].to(), "+input");
}); });
} }

File diff suppressed because one or more lines are too long

View file

@ -1318,10 +1318,12 @@
minimal = hasCopyEvent && minimal = hasCopyEvent &&
(range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000); (range.to().line - range.from().line > 100 || (selected = cm.getSelection()).length > 1000);
var content = minimal ? "-" : selected || cm.getSelection(); var content = minimal ? "-" : selected || cm.getSelection();
if(!this.composing)
this.textarea.value = content; this.textarea.value = content;
if (cm.state.focused) selectInput(this.textarea); if (cm.state.focused) selectInput(this.textarea);
if (ie && ie_version >= 9) this.hasSelection = content; if (ie && ie_version >= 9) this.hasSelection = content;
} else if (!typing) { } else if (!typing) {
if(!this.composing)
this.prevInput = this.textarea.value = ""; this.prevInput = this.textarea.value = "";
if (ie && ie_version >= 9) this.hasSelection = null; if (ie && ie_version >= 9) this.hasSelection = null;
} }

View file

@ -0,0 +1 @@
.hljs{display:block;background:white;padding:0.5em;color:#333333;overflow-x:auto;-webkit-text-size-adjust:none}.hljs-comment,.bash .hljs-shebang,.java .hljs-javadoc,.javascript .hljs-javadoc{color:#969896}.hljs-string,.apache .hljs-sqbracket,.coffeescript .hljs-subst,.coffeescript .hljs-regexp,.cpp .hljs-preprocessor,.c .hljs-preprocessor,.javascript .hljs-regexp,.json .hljs-attribute,.makefile .hljs-variable,.markdown .hljs-value,.markdown .hljs-link_label,.markdown .hljs-strong,.markdown .hljs-emphasis,.markdown .hljs-blockquote,.nginx .hljs-regexp,.nginx .hljs-number,.objectivec .hljs-preprocessor .hljs-title,.perl .hljs-regexp,.php .hljs-regexp,.xml .hljs-value,.less .hljs-built_in,.scss .hljs-built_in{color:#df5000}.hljs-keyword,.css .hljs-at_rule,.css .hljs-important,.http .hljs-request,.ini .hljs-setting,.java .hljs-javadoctag,.javascript .hljs-tag,.javascript .hljs-javadoctag,.nginx .hljs-title,.objectivec .hljs-preprocessor,.php .hljs-phpdoc,.sql .hljs-built_in,.less .hljs-tag,.less .hljs-at_rule,.scss .hljs-tag,.scss .hljs-at_rule,.scss .hljs-important,.stylus .hljs-at_rule,.go .hljs-typename,.swift .hljs-preprocessor{color:#a71d5d}.apache .hljs-common,.apache .hljs-cbracket,.apache .hljs-keyword,.bash .hljs-literal,.bash .hljs-built_in,.coffeescript .hljs-literal,.coffeescript .hljs-built_in,.coffeescript .hljs-number,.cpp .hljs-number,.cpp .hljs-built_in,.c .hljs-number,.c .hljs-built_in,.cs .hljs-number,.cs .hljs-built_in,.css .hljs-attribute,.css .hljs-hexcolor,.css .hljs-number,.css .hljs-function,.http .hljs-literal,.http .hljs-attribute,.java .hljs-number,.javascript .hljs-built_in,.javascript .hljs-literal,.javascript .hljs-number,.json .hljs-number,.makefile .hljs-keyword,.markdown .hljs-link_reference,.nginx .hljs-built_in,.objectivec .hljs-literal,.objectivec .hljs-number,.objectivec .hljs-built_in,.php .hljs-literal,.php .hljs-number,.python .hljs-number,.ruby .hljs-prompt,.ruby .hljs-constant,.ruby .hljs-number,.ruby .hljs-subst .hljs-keyword,.ruby .hljs-symbol,.sql .hljs-number,.puppet .hljs-function,.less .hljs-number,.less .hljs-hexcolor,.less .hljs-function,.less .hljs-attribute,.scss .hljs-preprocessor,.scss .hljs-number,.scss .hljs-hexcolor,.scss .hljs-function,.scss .hljs-attribute,.stylus .hljs-number,.stylus .hljs-hexcolor,.stylus .hljs-attribute,.stylus .hljs-params,.go .hljs-built_in,.go .hljs-constant,.swift .hljs-built_in,.swift .hljs-number{color:#0086b3}.apache .hljs-tag,.cs .hljs-xmlDocTag,.css .hljs-tag,.xml .hljs-title,.stylus .hljs-tag{color:#63a35c}.bash .hljs-variable,.cs .hljs-preprocessor,.cs .hljs-preprocessor .hljs-keyword,.css .hljs-attr_selector,.css .hljs-value,.ini .hljs-value,.ini .hljs-keyword,.javascript .hljs-tag .hljs-title,.makefile .hljs-constant,.nginx .hljs-variable,.xml .hljs-tag,.scss .hljs-variable{color:#333333}.bash .hljs-title,.coffeescript .hljs-title,.cpp .hljs-title,.c .hljs-title,.cs .hljs-title,.css .hljs-id,.css .hljs-class,.css .hljs-pseudo,.ini .hljs-title,.java .hljs-title,.javascript .hljs-title,.makefile .hljs-title,.objectivec .hljs-title,.perl .hljs-sub,.php .hljs-title,.python .hljs-decorator,.python .hljs-title,.ruby .hljs-parent,.ruby .hljs-title,.xml .hljs-attribute,.puppet .hljs-title,.less .hljs-id,.less .hljs-pseudo,.less .hljs-class,.scss .hljs-id,.scss .hljs-pseudo,.scss .hljs-class,.stylus .hljs-class,.stylus .hljs-id,.stylus .hljs-pseudo,.stylus .hljs-title,.swift .hljs-title,.diff .hljs-chunk{color:#795da3}.coffeescript .hljs-reserved,.coffeescript .hljs-attribute{color:#1d3e81}.diff .hljs-chunk{font-weight:bold}.diff .hljs-addition{color:#55a532;background-color:#eaffea}.diff .hljs-deletion{color:#bd2c00;background-color:#ffecec}.markdown .hljs-link_url{text-decoration:underline}

View file

@ -1 +1 @@
.hljs{display:block;overflow-x:auto;padding:0.5em;color:#333;background:#f8f8f8;-webkit-text-size-adjust:none}.hljs-comment,.diff .hljs-header,.hljs-javadoc{color:#998;font-style:italic}.hljs-keyword,.css .rule .hljs-keyword,.hljs-winutils,.nginx .hljs-title,.hljs-subst,.hljs-request,.hljs-status{color:#333;font-weight:bold}.hljs-number,.hljs-hexcolor,.ruby .hljs-constant{color:#008080}.hljs-string,.hljs-tag .hljs-value,.hljs-phpdoc,.hljs-dartdoc,.tex .hljs-formula{color:#d14}.hljs-title,.hljs-id,.scss .hljs-preprocessor{color:#900;font-weight:bold}.hljs-list .hljs-keyword,.hljs-subst{font-weight:normal}.hljs-class .hljs-title,.hljs-type,.vhdl .hljs-literal,.tex .hljs-command{color:#458;font-weight:bold}.hljs-tag,.hljs-tag .hljs-title,.hljs-rules .hljs-property,.django .hljs-tag .hljs-keyword{color:#000080;font-weight:normal}.hljs-attribute,.hljs-variable,.lisp .hljs-body{color:#008080}.hljs-regexp{color:#009926}.hljs-symbol,.ruby .hljs-symbol .hljs-string,.lisp .hljs-keyword,.clojure .hljs-keyword,.scheme .hljs-keyword,.tex .hljs-special,.hljs-prompt{color:#990073}.hljs-built_in{color:#0086b3}.hljs-preprocessor,.hljs-pragma,.hljs-pi,.hljs-doctype,.hljs-shebang,.hljs-cdata{color:#999;font-weight:bold}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.diff .hljs-change{background:#0086b3}.hljs-chunk{color:#aaa} .hljs{display:block;overflow-x:auto;padding:0.5em;color:#333;background:#f8f8f8;-webkit-text-size-adjust:none}.hljs-comment,.diff .hljs-header{color:#998;font-style:italic}.hljs-keyword,.css .rule .hljs-keyword,.hljs-winutils,.nginx .hljs-title,.hljs-subst,.hljs-request,.hljs-status{color:#333;font-weight:bold}.hljs-number,.hljs-hexcolor,.ruby .hljs-constant{color:#008080}.hljs-string,.hljs-tag .hljs-value,.hljs-doctag,.tex .hljs-formula{color:#d14}.hljs-title,.hljs-id,.scss .hljs-preprocessor{color:#900;font-weight:bold}.hljs-list .hljs-keyword,.hljs-subst{font-weight:normal}.hljs-class .hljs-title,.hljs-type,.vhdl .hljs-literal,.tex .hljs-command{color:#458;font-weight:bold}.hljs-tag,.hljs-tag .hljs-title,.hljs-rule .hljs-property,.django .hljs-tag .hljs-keyword{color:#000080;font-weight:normal}.hljs-attribute,.hljs-variable,.lisp .hljs-body,.hljs-name{color:#008080}.hljs-regexp{color:#009926}.hljs-symbol,.ruby .hljs-symbol .hljs-string,.lisp .hljs-keyword,.clojure .hljs-keyword,.scheme .hljs-keyword,.tex .hljs-special,.hljs-prompt{color:#990073}.hljs-built_in{color:#0086b3}.hljs-preprocessor,.hljs-pragma,.hljs-pi,.hljs-doctype,.hljs-shebang,.hljs-cdata{color:#999;font-weight:bold}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.diff .hljs-change{background:#0086b3}.hljs-chunk{color:#aaa}

File diff suppressed because one or more lines are too long

View file

@ -88,6 +88,8 @@
return false; return false;
} }
}); });
return inlineattach;
}; };
inlineAttachment.editors.codemirror4 = codeMirrorEditor4; inlineAttachment.editors.codemirror4 = codeMirrorEditor4;

View file

@ -352,7 +352,7 @@
inlineAttachment.prototype.onFileInserted = function(file, id) { inlineAttachment.prototype.onFileInserted = function(file, id) {
if (this.settings.onFileReceived.call(this, file) !== false) { if (this.settings.onFileReceived.call(this, file) !== false) {
this.lastValue = this.settings.progressText.replace(this.filenameTag, id); this.lastValue = this.settings.progressText.replace(this.filenameTag, id);
this.editor.insertValue(this.lastValue); this.editor.insertValue(this.lastValue + "\n");
} }
}; };

View file

@ -250,9 +250,13 @@ if (typeof jQuery === 'undefined') {
if (context || context === '') { if (context || context === '') {
if (isString(context)) { text = context; } if (isString(context)) { text = context; }
var cursor = editor.getCursor(); var cursor = editor.getCursor();
text = editor.getLine(cursor.line).slice(0, cursor.ch); var line = editor.getLine(cursor.line);
var match = text.match(strategy.match); var linematch = line.match(strategy.match);
if (match) { return [strategy, match[strategy.index], match]; } if(linematch) {
text = line.slice(0, cursor.ch);
var textmatch = text.match(strategy.match);
if (textmatch) { return [strategy, textmatch[strategy.index], textmatch]; }
}
} }
} }
return [] return []
@ -315,12 +319,14 @@ if (typeof jQuery === 'undefined') {
}; };
var dropdownViews = {}; var dropdownViews = {};
$(document).on('click', function (e) { /*
$(document).on('mousedown', function (e) {
var id = e.originalEvent && e.originalEvent.keepTextCompleteDropdown; var id = e.originalEvent && e.originalEvent.keepTextCompleteDropdown;
$.each(dropdownViews, function (key, view) { $.each(dropdownViews, function (key, view) {
if (key !== id) { view.deactivate(); } if (key !== id) { view.deactivate(); }
}); });
}); });
*/
// Dropdown view // Dropdown view
// ============= // =============
@ -335,6 +341,7 @@ if (typeof jQuery === 'undefined') {
this._data = []; // zipped data. this._data = []; // zipped data.
this.$inputEl = $(element); this.$inputEl = $(element);
this.option = option; this.option = option;
this.tap = false;
// Override setPosition method. // Override setPosition method.
if (option.listPosition) { this.setPosition = option.listPosition; } if (option.listPosition) { this.setPosition = option.listPosition; }
@ -499,7 +506,18 @@ if (typeof jQuery === 'undefined') {
// --------------- // ---------------
_bindEvents: function () { _bindEvents: function () {
this.$el.on('touchstart.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)); this.$inputEl.on('blur', $.proxy(this.deactivate, this));
this.$el.on('touchstart.' + this.id, '.textcomplete-item', $.proxy(function(e) {
this.tap = true;
}, this));
this.$el.on('touchmove.' + this.id, '.textcomplete-item', $.proxy(function(e) {
this.tap = false;
}, this));
this.$el.on('touchend.' + this.id, '.textcomplete-item', $.proxy(function(e) {
if(e.cancelable && this.tap) {
this._onClick(e);
}
}, this));
this.$el.on('mousedown.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this)); this.$el.on('mousedown.' + this.id, '.textcomplete-item', $.proxy(this._onClick, this));
this.$el.on('mouseover.' + this.id, '.textcomplete-item', $.proxy(this._onMouseover, this)); this.$el.on('mouseover.' + this.id, '.textcomplete-item', $.proxy(this._onMouseover, this));
this.$inputEl.on('keydown.' + this.id, $.proxy(this._onKeydown, this)); this.$inputEl.on('keydown.' + this.id, $.proxy(this._onKeydown, this));
@ -819,6 +837,7 @@ if (typeof jQuery === 'undefined') {
this.id = completer.id + this.constructor.name; this.id = completer.id + this.constructor.name;
this.completer = completer; this.completer = completer;
this.option = option; this.option = option;
this.lastCurosr = null;
if (this.option.debounce) { if (this.option.debounce) {
this._onKeyup = debounce(this._onKeyup, this.option.debounce); this._onKeyup = debounce(this._onKeyup, this.option.debounce);
@ -866,12 +885,20 @@ if (typeof jQuery === 'undefined') {
// --------------- // ---------------
_bindEvents: function () { _bindEvents: function () {
editor.on('cursorActivity', $.proxy(this._onKeyup, this));
$('.CodeMirror').on('touchend.' + this.id, $.proxy(this._onKeyup, this));
$('.CodeMirror').on('mouseup.' + this.id, $.proxy(this._onKeyup, this));
$(editor.getInputField()).on('focus', $.proxy(this._onKeyup, this));
this.$el.on('keyup.' + this.id, $.proxy(this._onKeyup, this)); this.$el.on('keyup.' + this.id, $.proxy(this._onKeyup, this));
}, },
_onKeyup: function (e) { _onKeyup: function (e) {
var focus = (e.type == 'focus');
var cursor = editor.getCursor();
var samePos = (cursor == this.lastCursor);
if (this._skipSearch(e)) { return; } if (this._skipSearch(e)) { return; }
this.completer.trigger(this.getTextFromHeadToCaret(), true); this.completer.trigger(this.getTextFromHeadToCaret(), focus ? false : samePos);
this.lastCursor = cursor;
}, },
// Suppress searching if it returns true. // Suppress searching if it returns true.

8
public/vendor/jquery.mousewheel.min.js vendored Executable file
View file

@ -0,0 +1,8 @@
/*! Copyright (c) 2013 Brandon Aaron (http://brandon.aaron.sh)
* Licensed under the MIT License (LICENSE.txt).
*
* Version: 3.1.11
*
* Requires: jQuery 1.2.2+
*/
!function(a){"function"==typeof define&&define.amd?define(["jquery"],a):"object"==typeof exports?module.exports=a:a(jQuery)}(function(a){function b(b){var g=b||window.event,h=i.call(arguments,1),j=0,l=0,m=0,n=0,o=0,p=0;if(b=a.event.fix(g),b.type="mousewheel","detail"in g&&(m=-1*g.detail),"wheelDelta"in g&&(m=g.wheelDelta),"wheelDeltaY"in g&&(m=g.wheelDeltaY),"wheelDeltaX"in g&&(l=-1*g.wheelDeltaX),"axis"in g&&g.axis===g.HORIZONTAL_AXIS&&(l=-1*m,m=0),j=0===m?l:m,"deltaY"in g&&(m=-1*g.deltaY,j=m),"deltaX"in g&&(l=g.deltaX,0===m&&(j=-1*l)),0!==m||0!==l){if(1===g.deltaMode){var q=a.data(this,"mousewheel-line-height");j*=q,m*=q,l*=q}else if(2===g.deltaMode){var r=a.data(this,"mousewheel-page-height");j*=r,m*=r,l*=r}if(n=Math.max(Math.abs(m),Math.abs(l)),(!f||f>n)&&(f=n,d(g,n)&&(f/=40)),d(g,n)&&(j/=40,l/=40,m/=40),j=Math[j>=1?"floor":"ceil"](j/f),l=Math[l>=1?"floor":"ceil"](l/f),m=Math[m>=1?"floor":"ceil"](m/f),k.settings.normalizeOffset&&this.getBoundingClientRect){var s=this.getBoundingClientRect();o=b.clientX-s.left,p=b.clientY-s.top}return b.deltaX=l,b.deltaY=m,b.deltaFactor=f,b.offsetX=o,b.offsetY=p,b.deltaMode=0,h.unshift(b,j,l,m),e&&clearTimeout(e),e=setTimeout(c,200),(a.event.dispatch||a.event.handle).apply(this,h)}}function c(){f=null}function d(a,b){return k.settings.adjustOldDeltas&&"mousewheel"===a.type&&b%120===0}var e,f,g=["wheel","mousewheel","DOMMouseScroll","MozMousePixelScroll"],h="onwheel"in document||document.documentMode>=9?["wheel"]:["mousewheel","DomMouseScroll","MozMousePixelScroll"],i=Array.prototype.slice;if(a.event.fixHooks)for(var j=g.length;j;)a.event.fixHooks[g[--j]]=a.event.mouseHooks;var k=a.event.special.mousewheel={version:"3.1.11",setup:function(){if(this.addEventListener)for(var c=h.length;c;)this.addEventListener(h[--c],b,!1);else this.onmousewheel=b;a.data(this,"mousewheel-line-height",k.getLineHeight(this)),a.data(this,"mousewheel-page-height",k.getPageHeight(this))},teardown:function(){if(this.removeEventListener)for(var c=h.length;c;)this.removeEventListener(h[--c],b,!1);else this.onmousewheel=null;a.removeData(this,"mousewheel-line-height"),a.removeData(this,"mousewheel-page-height")},getLineHeight:function(b){var c=a(b)["offsetParent"in a.fn?"offsetParent":"parent"]();return c.length||(c=a("body")),parseInt(c.css("fontSize"),10)},getPageHeight:function(b){return a(b).height()},settings:{adjustOldDeltas:!0,normalizeOffset:!0}};a.fn.extend({mousewheel:function(a){return a?this.bind("mousewheel",a):this.trigger("mousewheel")},unmousewheel:function(a){return this.unbind("mousewheel",a)}})});

121
public/vendor/md-toc.js vendored Executable file
View file

@ -0,0 +1,121 @@
/**
* md-toc.js v1.0.2
* https://github.com/yijian166/md-toc.js
*/
(function (window) {
function Toc(id, options) {
this.el = document.getElementById(id);
if (!this.el) return;
this.options = options || {};
this.tocLevel = parseInt(options.level) || 0;
this.tocClass = options['class'] || 'toc';
this.tocTop = parseInt(options.top) || 0;
this.elChilds = this.el.children;
if (!this.elChilds.length) return;
this._init();
}
Toc.prototype._init = function () {
this._collectTitleElements();
this._createTocContent();
this._showToc();
};
Toc.prototype._collectTitleElements = function () {
this._elTitlesNames = [],
this.elTitleElements = [];
for (var i = 1; i < 7; i++) {
if (this.el.getElementsByTagName('h' + i).length) {
this._elTitlesNames.push('h' + i);
}
}
this._elTitlesNames.length = this._elTitlesNames.length > this.tocLevel ? this.tocLevel : this._elTitlesNames.length;
for (var j = 0; j < this.elChilds.length; j++) {
this._elChildName = this.elChilds[j].tagName.toLowerCase();
if (this._elTitlesNames.toString().match(this._elChildName)) {
this.elTitleElements.push(this.elChilds[j]);
}
}
};
Toc.prototype._createTocContent = function () {
this._elTitleElementsLen = this.elTitleElements.length;
if (!this._elTitleElementsLen) return;
this.tocContent = '';
this._tempLists = [];
var url = location.origin + location.pathname;
for (var i = 0; i < this._elTitleElementsLen; i++) {
var j = i + 1;
this._elTitleElement = this.elTitleElements[i];
this._elTitleElementName = this._elTitleElement.tagName;
this._elTitleElementText = this._elTitleElement.innerHTML.replace(/<(?:.|\n)*?>/gm, '');
var id = this._elTitleElement.getAttribute('id');
if (!id) {
this._elTitleElement.setAttribute('id', 'tip' + i);
id = '#tip' + i;
} else {
id = '#' + id;
}
this.tocContent += '<li><a href="' + id + '">' + this._elTitleElementText + '</a>';
if (j != this._elTitleElementsLen) {
this._elNextTitleElementName = this.elTitleElements[j].tagName;
if (this._elTitleElementName != this._elNextTitleElementName) {
var checkColse = false,
y = 1;
for (var t = this._tempLists.length - 1; t >= 0; t--) {
if (this._tempLists[t].tagName == this._elNextTitleElementName) {
checkColse = true;
break;
}
y++;
}
if (checkColse) {
this.tocContent += new Array(y + 1).join('</li></ul>');
this._tempLists.length = this._tempLists.length - y;
} else {
this._tempLists.push(this._elTitleElement);
this.tocContent += '<ul class="nav">';
}
} else {
this.tocContent += '</li>';
}
} else {
if (this._tempLists.length) {
this.tocContent += new Array(this._tempLists.length + 1).join('</li></ul>');
} else {
this.tocContent += '</li>';
}
}
}
this.tocContent = '<ul class="nav">' + this.tocContent + '</ul>';
};
Toc.prototype._showToc = function () {
this.toc = document.createElement('div');
this.toc.innerHTML = this.tocContent;
this.toc.setAttribute('class', this.tocClass);
if (!this.options.targetId) {
this.el.appendChild(this.toc);
} else {
document.getElementById(this.options.targetId).appendChild(this.toc);
}
var self = this;
if (this.tocTop > -1) {
window.onscroll = function () {
var t = document.documentElement.scrollTop || document.body.scrollTop;
if (t < self.tocTop) {
self.toc.setAttribute('style', 'position:absolute;top:' + self.tocTop + 'px;');
} else {
self.toc.setAttribute('style', 'position:fixed;top:10px;');
}
}
}
};
window.Toc = Toc;
})(window);

1
public/vendor/md5.min.js vendored Executable file
View file

@ -0,0 +1 @@
!function(a){"use strict";function b(a,b){var c=(65535&a)+(65535&b),d=(a>>16)+(b>>16)+(c>>16);return d<<16|65535&c}function c(a,b){return a<<b|a>>>32-b}function d(a,d,e,f,g,h){return b(c(b(b(d,a),b(f,h)),g),e)}function e(a,b,c,e,f,g,h){return d(b&c|~b&e,a,b,f,g,h)}function f(a,b,c,e,f,g,h){return d(b&e|c&~e,a,b,f,g,h)}function g(a,b,c,e,f,g,h){return d(b^c^e,a,b,f,g,h)}function h(a,b,c,e,f,g,h){return d(c^(b|~e),a,b,f,g,h)}function i(a,c){a[c>>5]|=128<<c%32,a[(c+64>>>9<<4)+14]=c;var d,i,j,k,l,m=1732584193,n=-271733879,o=-1732584194,p=271733878;for(d=0;d<a.length;d+=16)i=m,j=n,k=o,l=p,m=e(m,n,o,p,a[d],7,-680876936),p=e(p,m,n,o,a[d+1],12,-389564586),o=e(o,p,m,n,a[d+2],17,606105819),n=e(n,o,p,m,a[d+3],22,-1044525330),m=e(m,n,o,p,a[d+4],7,-176418897),p=e(p,m,n,o,a[d+5],12,1200080426),o=e(o,p,m,n,a[d+6],17,-1473231341),n=e(n,o,p,m,a[d+7],22,-45705983),m=e(m,n,o,p,a[d+8],7,1770035416),p=e(p,m,n,o,a[d+9],12,-1958414417),o=e(o,p,m,n,a[d+10],17,-42063),n=e(n,o,p,m,a[d+11],22,-1990404162),m=e(m,n,o,p,a[d+12],7,1804603682),p=e(p,m,n,o,a[d+13],12,-40341101),o=e(o,p,m,n,a[d+14],17,-1502002290),n=e(n,o,p,m,a[d+15],22,1236535329),m=f(m,n,o,p,a[d+1],5,-165796510),p=f(p,m,n,o,a[d+6],9,-1069501632),o=f(o,p,m,n,a[d+11],14,643717713),n=f(n,o,p,m,a[d],20,-373897302),m=f(m,n,o,p,a[d+5],5,-701558691),p=f(p,m,n,o,a[d+10],9,38016083),o=f(o,p,m,n,a[d+15],14,-660478335),n=f(n,o,p,m,a[d+4],20,-405537848),m=f(m,n,o,p,a[d+9],5,568446438),p=f(p,m,n,o,a[d+14],9,-1019803690),o=f(o,p,m,n,a[d+3],14,-187363961),n=f(n,o,p,m,a[d+8],20,1163531501),m=f(m,n,o,p,a[d+13],5,-1444681467),p=f(p,m,n,o,a[d+2],9,-51403784),o=f(o,p,m,n,a[d+7],14,1735328473),n=f(n,o,p,m,a[d+12],20,-1926607734),m=g(m,n,o,p,a[d+5],4,-378558),p=g(p,m,n,o,a[d+8],11,-2022574463),o=g(o,p,m,n,a[d+11],16,1839030562),n=g(n,o,p,m,a[d+14],23,-35309556),m=g(m,n,o,p,a[d+1],4,-1530992060),p=g(p,m,n,o,a[d+4],11,1272893353),o=g(o,p,m,n,a[d+7],16,-155497632),n=g(n,o,p,m,a[d+10],23,-1094730640),m=g(m,n,o,p,a[d+13],4,681279174),p=g(p,m,n,o,a[d],11,-358537222),o=g(o,p,m,n,a[d+3],16,-722521979),n=g(n,o,p,m,a[d+6],23,76029189),m=g(m,n,o,p,a[d+9],4,-640364487),p=g(p,m,n,o,a[d+12],11,-421815835),o=g(o,p,m,n,a[d+15],16,530742520),n=g(n,o,p,m,a[d+2],23,-995338651),m=h(m,n,o,p,a[d],6,-198630844),p=h(p,m,n,o,a[d+7],10,1126891415),o=h(o,p,m,n,a[d+14],15,-1416354905),n=h(n,o,p,m,a[d+5],21,-57434055),m=h(m,n,o,p,a[d+12],6,1700485571),p=h(p,m,n,o,a[d+3],10,-1894986606),o=h(o,p,m,n,a[d+10],15,-1051523),n=h(n,o,p,m,a[d+1],21,-2054922799),m=h(m,n,o,p,a[d+8],6,1873313359),p=h(p,m,n,o,a[d+15],10,-30611744),o=h(o,p,m,n,a[d+6],15,-1560198380),n=h(n,o,p,m,a[d+13],21,1309151649),m=h(m,n,o,p,a[d+4],6,-145523070),p=h(p,m,n,o,a[d+11],10,-1120210379),o=h(o,p,m,n,a[d+2],15,718787259),n=h(n,o,p,m,a[d+9],21,-343485551),m=b(m,i),n=b(n,j),o=b(o,k),p=b(p,l);return[m,n,o,p]}function j(a){var b,c="";for(b=0;b<32*a.length;b+=8)c+=String.fromCharCode(a[b>>5]>>>b%32&255);return c}function k(a){var b,c=[];for(c[(a.length>>2)-1]=void 0,b=0;b<c.length;b+=1)c[b]=0;for(b=0;b<8*a.length;b+=8)c[b>>5]|=(255&a.charCodeAt(b/8))<<b%32;return c}function l(a){return j(i(k(a),8*a.length))}function m(a,b){var c,d,e=k(a),f=[],g=[];for(f[15]=g[15]=void 0,e.length>16&&(e=i(e,8*a.length)),c=0;16>c;c+=1)f[c]=909522486^e[c],g[c]=1549556828^e[c];return d=i(f.concat(k(b)),512+8*b.length),j(i(g.concat(d),640))}function n(a){var b,c,d="0123456789abcdef",e="";for(c=0;c<a.length;c+=1)b=a.charCodeAt(c),e+=d.charAt(b>>>4&15)+d.charAt(15&b);return e}function o(a){return unescape(encodeURIComponent(a))}function p(a){return l(o(a))}function q(a){return n(p(a))}function r(a,b){return m(o(a),o(b))}function s(a,b){return n(r(a,b))}function t(a,b,c){return b?c?r(b,a):s(b,a):c?p(a):q(a)}"function"==typeof define&&define.amd?define(function(){return t}):a.md5=t}(this);

125
public/vendor/showup/showup.css vendored Executable file
View file

@ -0,0 +1,125 @@
/*
* Showup.js jQuery Plugin
* http://github.com/jonschlinkert/showup
*
* Copyright (c) 2013 Jon Schlinkert, contributors
* Licensed under the MIT License (MIT).
*/
/**
* Docs navbar transitions effects
*/
.navbar-tall,
.navbar-show {
-webkit-transition: -webkit-transform .3s;
-moz-transition: -moz-transform .3s;
-o-transition: -o-transform .3s;
transition: transform .3s;
-webkit-transform: translate(0, 0);
-ms-transform: translate(0, 0);
transform: translate(0, 0);
}
.navbar-hide {
-webkit-transition: -webkit-transform .2s;
-moz-transition: -moz-transform .2s;
-o-transition: -o-transform .2s;
transition: transform .2s;
-webkit-transform: translate(0, -60px);
-ms-transform: translate(0, -60px);
transform: translate(0, -60px);
}
.navbar-tall,
.navbar-short,
.navbar-tall .navbar-brand,
.navbar-short .navbar-brand,
.navbar-tall .navbar-nav > li > a,
.navbar-short .navbar-nav > li > a {
-webkit-transition: all 0.2s linear;
transition: all 0.2s linear;
}
.navbar-short {
min-height: 40px;
}
.navbar-short .navbar-brand {
font-size: 16px;
padding: 13px 15px 10px;
}
.navbar-short .navbar-nav > li > a {
padding-top: 12px;
padding-bottom: 12px;
}
.navbar-tall {
min-height: 70px;
}
.navbar-tall .navbar-brand {
font-size: 24px;
padding: 25px 15px;
}
.navbar-tall .navbar-nav > li > a {
padding-top: 25px;
}
/**
* Docs Buttons
*/
/* Fixed button, bottom right */
.btn-fixed-bottom {
position: fixed;
bottom: 30px;
display: none;
z-index: 5;
width: 40px;
height: 40px;
}
/* Toggles navbar classes */
.btn-hide-show {
margin-right: 10px;
}
/* Light theme */
.btn-light {
color: #555;
background-color: rgba(0, 0, 0,.1);
}
.btn-light:hover {
color: #111;
background-color: rgba(0, 0, 0,.25);
}
/* Dark theme */
.btn-dark {
color: #fff;
background-color: rgba(0, 0, 0,.5);
}
.btn-dark:hover {
color: #fff;
background-color: rgba(0, 0, 0,.9);
}
/* Buttons displayed throughout the content */
.btn-showup {
position: relative;
color: #fff;
font-weight: normal;
background-color: #463265;
border-color: #3F2961;
}
.btn-showup:hover,
.btn-showup:focus {
color: #fff;
outline: none;
background-color: #39235A;
border-color: #39235A;
}

87
public/vendor/showup/showup.js vendored Executable file
View file

@ -0,0 +1,87 @@
/*
* Showup.js jQuery Plugin
* http://github.com/jonschlinkert/showup
*
* Copyright (c) 2013 Jon Schlinkert, contributors
* Licensed under the MIT License (MIT).
*/
(function( $ ) {
$.fn.showUp = function(ele, options) {
options = options || {};
var target = $(ele);
var down = options.down || 'navbar-hide';
var up = options.up || 'navbar-show';
var btnHideShow = options.btnHideShow || '.btn-hide-show';
var hideOffset = options.offset || 60;
var previousScroll = 0;
var isHide = false;
$(window).scroll(function () {
checkScrollTop();
});
$(window).resize(function () {
checkScrollTop();
});
$(window).mousewheel(function () {
checkScrollTop();
});
function checkScrollTop()
{
target.clearQueue();
target.stop();
var currentScroll = $(this).scrollTop();
if (currentScroll > hideOffset) {
if(Math.abs(previousScroll - currentScroll) < 50) return;
if (currentScroll > previousScroll) {
// Action on scroll down
target.removeClass(up).addClass(down);
} else if (currentScroll < previousScroll) {
// Action on scroll up
target.removeClass(down).addClass(up);
}
} else {
target.removeClass(down).addClass(up);
}
previousScroll = $(this).scrollTop();
}
// Toggle visibility of target on click
$(btnHideShow).click(function () {
if (target.hasClass(down)) {
target.removeClass(down).addClass(up);
} else {
target.removeClass(up).addClass(down);
}
});
};
})( jQuery );
// TODO: make customizable
$(document).ready(function () {
var duration = 420;
var showOffset = 220;
var btnFixed = '.btn-fixed-bottom';
var btnToTopClass = '.back-to-top';
$(window).scroll(function () {
if ($(this).scrollTop() > showOffset) {
$(btnFixed).fadeIn(duration);
} else {
$(btnFixed).fadeOut(duration);
}
});
$(btnToTopClass).click(function (event) {
event.preventDefault();
$('html, body').animate({
scrollTop: 0
}, duration);
return false;
});
});

View file

@ -3,7 +3,31 @@
<textarea id="textit"></textarea> <textarea id="textit"></textarea>
</div> </div>
<div class="ui-view-area"> <div class="ui-view-area">
<div class="markdown-body container-fluid"></div> <div class="ui-infobar container-fluid">
<small>
<span class="ui-lastchange text-uppercase"></span>
<span class="ui-permission dropdown pull-right">
<a id="permissionLabel" class="ui-permission-label text-uppercase" data-target="#" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false">
</a>
<ul class="dropdown-menu" aria-labelledby="permissionLabel">
<li class="ui-permission-freely"><a><i class="fa fa-leaf fa-fw"></i> Freely - Anyone can edit</a></li>
<li class="ui-permission-editable"><a><i class="fa fa-pencil fa-fw"></i> Editable - Signed people can edit</a></li>
<li class="ui-permission-locked"><a><i class="fa fa-lock fa-fw"></i> Locked - Only owner can edit</a></li>
</ul>
</span>
</small>
</div>
<div id="doc" class="markdown-body container-fluid"></div>
<div class="ui-toc dropup" style="display:none;">
<div class="pull-right dropdown">
<a id="tocLabel" class="ui-toc-label btn btn-default" data-target="#" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false" title="Table of content">
<i class="fa fa-bars"></i>
</a>
<ul id="toc" class="ui-toc-dropdown dropdown-menu" aria-labelledby="tocLabel">
</ul>
</div>
</div>
<div id="toc-affix" class="ui-affix-toc ui-toc-dropdown" data-spy="affix" style="top:50px;display:none;"></div>
</div> </div>
</div> </div>
<div class="modal fade" id="clipboardModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true"> <div class="modal fade" id="clipboardModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
@ -25,3 +49,65 @@
</div> </div>
</div> </div>
</div> </div>
<div class="modal fade" id="refreshModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel">This page need refresh</h4>
</div>
<div class="modal-body">
<h5>This page have a mismatch client version or incorrect user state.</h5>
<strong>Please refresh this page.</strong>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id="refreshModalRefresh">Refresh</button>
</div>
</div>
</div>
</div>
<!-- signin modal -->
<div class="modal fade signin-modal" tabindex="-1" role="dialog" aria-labelledby="mySmallModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="mySmallModalLabel">Please sign in to edit</h4>
</div>
<div class="modal-body">
<a href="/auth/facebook" class="btn btn-lg btn-block btn-social btn-facebook">
<i class="fa fa-facebook"></i> Sign in via Facebook
</a>
<a href="/auth/twitter" class="btn btn-lg btn-block btn-social btn-twitter">
<i class="fa fa-twitter"></i> Sign in via Twitter
</a>
<a href="/auth/github" class="btn btn-lg btn-block btn-social btn-github">
<i class="fa fa-github"></i> Sign in via GitHub
</a>
<a href="/auth/dropbox" class="btn btn-lg btn-block btn-social btn-dropbox">
<i class="fa fa-dropbox"></i> Sign in via Dropbox
</a>
</div>
</div>
</div>
</div>
<!-- locked modal -->
<div class="modal fade locked-modal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span>
</button>
<h4 class="modal-title" id="myModalLabel"><i class="fa fa-lock"></i> This note is locked</h4>
</div>
<div class="modal-body" style="color:black;">
<h5>Sorry, only owner can edit this note.</h5>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-danger" data-dismiss="modal">OK</button>
</div>
</div>
</div>
</div>

View file

@ -1,5 +1,6 @@
<script src="/vendor/spin.min.js" defer></script> <script src="/vendor/spin.min.js" defer></script>
<script src="/vendor/jquery-1.11.2.min.js" defer></script> <script src="/vendor/jquery-1.11.2.min.js" defer></script>
<script src="/vendor/jquery.mousewheel.min.js" defer></script>
<script src="/vendor/bootstrap/js/bootstrap.min.js" defer></script> <script src="/vendor/bootstrap/js/bootstrap.min.js" defer></script>
<script src="/vendor/greensock-js/TweenMax.min.js" defer></script> <script src="/vendor/greensock-js/TweenMax.min.js" defer></script>
<script src="/vendor/greensock-js/jquery.gsap.min.js" defer></script> <script src="/vendor/greensock-js/jquery.gsap.min.js" defer></script>
@ -13,6 +14,7 @@
<script src="/vendor/remarkable-regex.js" defer></script> <script src="/vendor/remarkable-regex.js" defer></script>
<script src="/vendor/gist-embed.js" defer></script> <script src="/vendor/gist-embed.js" defer></script>
<script src="/vendor/lz-string.min.js" defer></script> <script src="/vendor/lz-string.min.js" defer></script>
<script src="/vendor/string.min.js" defer></script>
<script src="/vendor/highlight-js/highlight.min.js" defer></script> <script src="/vendor/highlight-js/highlight.min.js" defer></script>
<script src="/vendor/js.cookie.js" defer></script> <script src="/vendor/js.cookie.js" defer></script>
<script src="/vendor/moment-with-locales.js" defer></script> <script src="/vendor/moment-with-locales.js" defer></script>
@ -29,6 +31,9 @@
<script src="/vendor/idle.js" defer></script> <script src="/vendor/idle.js" defer></script>
<script src="/vendor/visibility-1.2.1.min.js" defer></script> <script src="/vendor/visibility-1.2.1.min.js" defer></script>
<script src="/vendor/list.min.js" defer></script> <script src="/vendor/list.min.js" defer></script>
<script src="/vendor/md5.min.js" defer></script>
<script src="/vendor/md-toc.js" defer></script>
<script src="/vendor/showup/showup.js" defer></script>
<script type="text/javascript" src="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="rdoizrlnkuha23r" async defer></script> <script type="text/javascript" src="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="rdoizrlnkuha23r" async defer></script>
<script type="text/x-mathjax-config"> <script type="text/x-mathjax-config">
MathJax.Hub.Config({ messageStyle: "none", skipStartupTypeset: true ,tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']], processEscapes: true }}); MathJax.Hub.Config({ messageStyle: "none", skipStartupTypeset: true ,tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']], processEscapes: true }});

View file

@ -4,9 +4,7 @@
<meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black"> <meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="mobile-web-app-capable" content="yes"> <meta name="mobile-web-app-capable" content="yes">
<meta name="description" content="Realtime collaborative markdown notes on all platforms."> <title><%- title %></title>
<meta name="author" content="jackycute">
<title>HackMD - Collaborative notes</title>
<link rel="icon" type="image/png" href="/favicon.png"> <link rel="icon" type="image/png" href="/favicon.png">
<link rel="apple-touch-icon" href="/apple-touch-icon.png"> <link rel="apple-touch-icon" href="/apple-touch-icon.png">
<link rel="stylesheet" href="/vendor/bootstrap/css/bootstrap.min.css"> <link rel="stylesheet" href="/vendor/bootstrap/css/bootstrap.min.css">
@ -19,8 +17,10 @@
<link rel="stylesheet" href="/vendor/codemirror/theme/monokai.css"> <link rel="stylesheet" href="/vendor/codemirror/theme/monokai.css">
<link rel="stylesheet" href="/css/github-extract.css"> <link rel="stylesheet" href="/css/github-extract.css">
<link rel="stylesheet" href="/css/gist.css"> <link rel="stylesheet" href="/css/gist.css">
<link rel="stylesheet" href="/vendor/highlight-js/github.min.css"> <link rel="stylesheet" href="/vendor/highlight-js/github-gist.min.css">
<link rel="stylesheet" href="/vendor/emojify/css/emojify.min.css"> <link rel="stylesheet" href="/vendor/emojify/css/emojify.min.css">
<link rel="stylesheet" href="/vendor/showup/showup.css">
<link rel="stylesheet" href="/css/bootstrap-social.css">
<link rel="stylesheet" href="/css/markdown.css"> <link rel="stylesheet" href="/css/markdown.css">
<link rel="stylesheet" href="/css/index.css"> <link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/extra.css"> <link rel="stylesheet" href="/css/extra.css">

View file

@ -13,15 +13,18 @@
<ul class="dropdown-menu list" role="menu" aria-labelledby="menu"> <ul class="dropdown-menu list" role="menu" aria-labelledby="menu">
</ul> </ul>
</div> </div>
<a class="navbar-brand" href="./"><i class="fa fa-file-text"></i> HackMD</a> <a class="navbar-brand" href="/"><i class="fa fa-file-text"></i> HackMD</a>
<div class="nav-mobile pull-right visible-xs"> <div class="nav-mobile pull-right visible-xs">
<span class="btn btn-link btn-file ui-upload-image" title="Upload Image" style="display:none;">
<i class="fa fa-camera"></i><input type="file" accept="image/*" name="upload" multiple>
</span>
<a data-target="#" data-toggle="dropdown" class="btn btn-link"> <a data-target="#" data-toggle="dropdown" class="btn btn-link">
<i class="fa fa-caret-down"></i> <i class="fa fa-caret-down"></i>
</a> </a>
<ul class="dropdown-menu" role="menu" aria-labelledby="menu"> <ul class="dropdown-menu" role="menu" aria-labelledby="menu">
<li role="presentation"><a role="menuitem" class="ui-new" tabindex="-1" href="./new" target="_blank"><i class="fa fa-plus fa-fw"></i> New</a> <li role="presentation"><a role="menuitem" class="ui-new" tabindex="-1" href="./new" target="_blank"><i class="fa fa-plus fa-fw"></i> New</a>
</li> </li>
<li role="presentation"><a role="menuitem" class="ui-pretty" tabindex="-1" href="#" target="_blank"><i class="fa fa-share-alt fa-fw"></i> Share</a> <li role="presentation"><a role="menuitem" class="ui-share" tabindex="-1" href="#" target="_blank"><i class="fa fa-share-alt fa-fw"></i> Share</a>
</li> </li>
<li class="divider"></li> <li class="divider"></li>
<li class="dropdown-header">Save</li> <li class="dropdown-header">Save</li>
@ -62,6 +65,9 @@
<a href="https://www.facebook.com/messages/866415986748945" class="btn btn-link ui-feedback" title="Feedback" target="_blank"> <a href="https://www.facebook.com/messages/866415986748945" class="btn btn-link ui-feedback" title="Feedback" target="_blank">
<i class="fa fa-bullhorn"></i> <i class="fa fa-bullhorn"></i>
</a> </a>
<span class="btn btn-link btn-file ui-upload-image" title="Upload Image" style="display:none;">
<i class="fa fa-camera"></i><input type="file" accept="image/*" name="upload" multiple>
</span>
</ul> </ul>
<ul class="nav navbar-nav navbar-right"> <ul class="nav navbar-nav navbar-right">
<li id="online-user-list"> <li id="online-user-list">
@ -79,7 +85,7 @@
</a> </a>
</li> </li>
<li> <li>
<a href="#" target="_blank" class="ui-pretty"> <a href="#" target="_blank" class="ui-share">
<i class="fa fa-share-alt"></i> Share <i class="fa fa-share-alt"></i> Share
</a> </a>
</li> </li>

View file

@ -4,43 +4,62 @@
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta http-equiv="X-UA-Compatible" content="IE=edge">
<title> <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
HackMD - Collaborative notes <meta name="apple-mobile-web-app-capable" content="yes">
</title> <meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="mobile-web-app-capable" content="yes">
<title><%- title %></title>
<link rel="icon" type="image/png" href="<%- url %>/favicon.png"> <link rel="icon" type="image/png" href="<%- url %>/favicon.png">
<link rel="apple-touch-icon" href="<%- url %>/apple-touch-icon.png"> <link rel="apple-touch-icon" href="<%- url %>/apple-touch-icon.png">
<link rel="stylesheet" href='<%- url %>/vendor/bootstrap/css/bootstrap.min.css'> <link rel="stylesheet" href='<%- url %>/vendor/bootstrap/css/bootstrap.min.css'>
<link rel="stylesheet" href='<%- url %>/vendor/font-awesome/css/font-awesome.min.css'> <link rel="stylesheet" href='<%- url %>/vendor/font-awesome/css/font-awesome.min.css'>
<link rel="stylesheet" href='<%- url %>/css/github-extract.css'> <link rel="stylesheet" href='<%- url %>/css/github-extract.css'>
<link rel="stylesheet" href='<%- url %>/css/gist.css'> <link rel="stylesheet" href='<%- url %>/css/gist.css'>
<link rel="stylesheet" href='<%- url %>/vendor/highlight-js/github.min.css'> <link rel="stylesheet" href='<%- url %>/vendor/highlight-js/github-gist.min.css'>
<link rel="stylesheet" href='<%- url %>/css/markdown.css'> <link rel="stylesheet" href='<%- url %>/css/markdown.css'>
<link rel="stylesheet" href='<%- url %>/vendor/emojify/css/emojify.min.css'> <link rel="stylesheet" href='<%- url %>/vendor/emojify/css/emojify.min.css'>
<link rel="stylesheet" href='<%- url %>/css/extra.css'> <link rel="stylesheet" href='<%- url %>/css/extra.css'>
<link rel="stylesheet" href='<%- url %>/css/site.css'> <link rel="stylesheet" href='<%- url %>/css/site.css'>
</head> </head>
<body> <body style="display:none;">
<div class="container markdown-body" style="display:none;"> <div class="ui-infobar container-fluid">
<small>
<span class="ui-lastchange text-uppercase"><%- updatetime %></span>
<span class="pull-right"><%- viewcount %> views <a href="#" class="ui-edit" title="Edit this note"><i class="fa fa-pencil"></i></a></span>
</small>
</div>
<div id="doc" class="container markdown-body">
<%- body %> <%- body %>
</div> </div>
<div class="ui-toc dropup" style="display:none;">
<div class="pull-right dropdown">
<a id="tocLabel" class="ui-toc-label btn btn-default" data-target="#" data-toggle="dropdown" href="#" role="button" aria-haspopup="true" aria-expanded="false" title="Table of content">
<i class="fa fa-bars"></i>
</a>
<ul id="toc" class="ui-toc-dropdown dropdown-menu" aria-labelledby="tocLabel">
</ul>
</div>
</div>
<div id="toc-affix" class="ui-affix-toc ui-toc-dropdown" data-spy="affix" style="display:none;"></div>
</body> </body>
</html> </html>
<!--<script src="<%- url %>/js/ga.js" async defer></script>-->
<!--<script src="<%- url %>/js/newrelic.js" async defer></script>-->
<script src="<%- url %>/vendor/jquery-1.11.2.min.js" defer></script> <script src="<%- url %>/vendor/jquery-1.11.2.min.js" defer></script>
<script src="<%- url %>/vendor/bootstrap/js/bootstrap.min.js" defer></script>
<script src="<%- url %>/vendor/lz-string.min.js" defer></script> <script src="<%- url %>/vendor/lz-string.min.js" defer></script>
<script src="<%- url %>/vendor/remarkable.min.js" defer></script> <script src="<%- url %>/vendor/remarkable.min.js" defer></script>
<script src="<%- url %>/vendor/remarkable-regex.js" defer></script> <script src="<%- url %>/vendor/remarkable-regex.js" defer></script>
<script src="<%- url %>/vendor/gist-embed.js" defer></script> <script src="<%- url %>/vendor/gist-embed.js" defer></script>
<script src="<%- url %>/vendor/string.min.js" defer></script> <script src="<%- url %>/vendor/string.min.js" defer></script>
<script src="<%- url %>/vendor/highlight-js/highlight.min.js" defer></script> <script src="<%- url %>/vendor/highlight-js/highlight.min.js" defer></script>
<script src="<%- url %>/vendor/moment-with-locales.js" defer></script>
<script src="<%- url %>/vendor/emojify/js/emojify.min.js" defer></script> <script src="<%- url %>/vendor/emojify/js/emojify.min.js" defer></script>
<script src="<%- url %>/vendor/raphael-min.js" defer></script> <script src="<%- url %>/vendor/raphael-min.js" defer></script>
<script src="<%- url %>/vendor/lodash.min.js" defer></script> <script src="<%- url %>/vendor/lodash.min.js" defer></script>
<script src="<%- url %>/vendor/sequence-diagrams/sequence-diagram-min.js" defer></script> <script src="<%- url %>/vendor/sequence-diagrams/sequence-diagram-min.js" defer></script>
<script src="<%- url %>/vendor/flowchart/flowchart-1.4.0.min.js" defer></script> <script src="<%- url %>/vendor/flowchart/flowchart-1.4.0.min.js" defer></script>
<script src="<%- url %>/vendor/md-toc.js" defer></script>
<script type="text/x-mathjax-config"> <script type="text/x-mathjax-config">
MathJax.Hub.Config({ messageStyle: "none", skipStartupTypeset: true ,tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']], processEscapes: true }}); MathJax.Hub.Config({ messageStyle: "none", skipStartupTypeset: true ,tex2jax: {inlineMath: [['$','$'], ['\\(','\\)']], processEscapes: true }});
</script> </script>

2
run.sh
View file

@ -5,4 +5,4 @@ MONGOLAB_URI='change this' \
PORT='80' \ PORT='80' \
SSLPORT='443' \ SSLPORT='443' \
DOMAIN='change this' \ DOMAIN='change this' \
forever -a --uid hackmd start app.js forever -a --uid hackmd -l ./logs/hackmd_log.log -o ./logs/hackmd_out.log -e ./logs/hackmd_error.log start app.js

0
tmp/.keep Normal file
View file