Support show last change user with profile and support YAML config inside the note with robots, lang, dir, breaks options
This commit is contained in:
parent
1672df3dce
commit
2ecec3b59a
18 changed files with 546 additions and 167 deletions
|
@ -29,6 +29,7 @@
|
|||
"handlebars": "~4.0.5",
|
||||
"js-url": "~2.0.2",
|
||||
"socket.io-client": "~1.3.7",
|
||||
"viz.js": "~1.3.0"
|
||||
"viz.js": "~1.3.0",
|
||||
"js-yaml": "~3.4.6"
|
||||
}
|
||||
}
|
||||
|
|
21
lib/note.js
21
lib/note.js
|
@ -26,6 +26,10 @@ var model = mongoose.model('note', {
|
|||
type: String,
|
||||
enum: permissionTypes
|
||||
},
|
||||
lastchangeuser: {
|
||||
type: Schema.Types.ObjectId,
|
||||
ref: 'user'
|
||||
},
|
||||
viewcount: {
|
||||
type: Number,
|
||||
default: 0
|
||||
|
@ -45,7 +49,8 @@ var note = {
|
|||
getNoteTitle: getNoteTitle,
|
||||
generateWebTitle: generateWebTitle,
|
||||
increaseViewCount: increaseViewCount,
|
||||
updatePermission: updatePermission
|
||||
updatePermission: updatePermission,
|
||||
updateLastChangeUser: updateLastChangeUser
|
||||
};
|
||||
|
||||
function checkNoteIdValid(noteId) {
|
||||
|
@ -198,4 +203,18 @@ function updatePermission(note, permission, callback) {
|
|||
});
|
||||
}
|
||||
|
||||
function updateLastChangeUser(note, lastchangeuser, callback) {
|
||||
note.lastchangeuser = lastchangeuser;
|
||||
note.updated = Date.now();
|
||||
note.save(function (err) {
|
||||
if (err) {
|
||||
logger.error('update note lastchangeuser failed: ' + err);
|
||||
callback(err, null);
|
||||
} else {
|
||||
logger.info("update note lastchangeuser success: " + note.id);
|
||||
callback(null, note);
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = note;
|
168
lib/realtime.js
168
lib/realtime.js
|
@ -9,7 +9,6 @@ var shortId = require('shortid');
|
|||
var randomcolor = require("randomcolor");
|
||||
var Chance = require('chance'),
|
||||
chance = new Chance();
|
||||
var md5 = require("blueimp-md5").md5;
|
||||
var moment = require('moment');
|
||||
|
||||
//core
|
||||
|
@ -68,7 +67,9 @@ function secure(socket, next) {
|
|||
|
||||
function emitCheck(note) {
|
||||
var out = {
|
||||
updatetime: note.updatetime
|
||||
updatetime: note.updatetime,
|
||||
lastchangeuser: note.lastchangeuser,
|
||||
lastchangeuserprofile: note.lastchangeuserprofile
|
||||
};
|
||||
realtime.io.to(note.id).emit('check', out);
|
||||
/*
|
||||
|
@ -89,18 +90,52 @@ var updater = setInterval(function () {
|
|||
if (note.server.isDirty) {
|
||||
if (config.debug)
|
||||
logger.info("updater found dirty note: " + key);
|
||||
var body = note.server.document;
|
||||
var title = Note.getNoteTitle(body);
|
||||
title = LZString.compressToBase64(title);
|
||||
body = LZString.compressToBase64(body);
|
||||
db.saveToDB(key, title, body, function (err, result) {
|
||||
if (err) return;
|
||||
note.server.isDirty = false;
|
||||
note.updatetime = Date.now();
|
||||
emitCheck(note);
|
||||
Note.findNote(note.id, function (err, _note) {
|
||||
if (err || !_note) return callback(err, null);
|
||||
//mongo update
|
||||
if (note.lastchangeuser && _note.lastchangeuser != note.lastchangeuser) {
|
||||
var lastchangeuser = note.lastchangeuser;
|
||||
var lastchangeuserprofile = null;
|
||||
User.findUser(lastchangeuser, function (err, user) {
|
||||
if (err) return callback(err, null);
|
||||
if (user && user.profile) {
|
||||
var profile = JSON.parse(user.profile);
|
||||
if (profile) {
|
||||
lastchangeuserprofile = {
|
||||
name: profile.displayName || profile.username,
|
||||
photo: User.parsePhotoByProfile(profile)
|
||||
}
|
||||
note.lastchangeuser = lastchangeuser;
|
||||
note.lastchangeuserprofile = lastchangeuserprofile;
|
||||
Note.updateLastChangeUser(_note, lastchangeuser, function (err, result) {
|
||||
if (err) return callback(err, null);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
note.lastchangeuser = null;
|
||||
note.lastchangeuserprofile = null;
|
||||
Note.updateLastChangeUser(_note, null, function (err, result) {
|
||||
if (err) return callback(err, null);
|
||||
});
|
||||
}
|
||||
//postgres update
|
||||
var body = note.server.document;
|
||||
var title = Note.getNoteTitle(body);
|
||||
title = LZString.compressToBase64(title);
|
||||
body = LZString.compressToBase64(body);
|
||||
db.saveToDB(key, title, body, function (err, result) {
|
||||
if (err) return callback(err, null);
|
||||
note.server.isDirty = false;
|
||||
note.updatetime = Date.now();
|
||||
emitCheck(note);
|
||||
callback(null, null);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
callback(null, null);
|
||||
}
|
||||
callback();
|
||||
}, function (err) {
|
||||
if (err) return logger.error('updater error', err);
|
||||
});
|
||||
|
@ -121,7 +156,7 @@ var cleaner = setInterval(function () {
|
|||
disconnectSocketQueue.push(socket);
|
||||
disconnect(socket);
|
||||
}
|
||||
callback();
|
||||
callback(null, null);
|
||||
}, function (err) {
|
||||
if (err) return logger.error('cleaner error', err);
|
||||
});
|
||||
|
@ -250,7 +285,11 @@ function emitRefresh(socket) {
|
|||
socket.emit('refresh', {
|
||||
docmaxlength: config.documentmaxlength,
|
||||
owner: note.owner,
|
||||
ownerprofile: note.ownerprofile,
|
||||
lastchangeuser: note.lastchangeuser,
|
||||
lastchangeuserprofile: note.lastchangeuserprofile,
|
||||
permission: note.permission,
|
||||
createtime: note.createtime,
|
||||
updatetime: note.updatetime
|
||||
});
|
||||
}
|
||||
|
@ -321,11 +360,15 @@ function startConnection(socket) {
|
|||
isConnectionBusy = false;
|
||||
return logger.error(err);
|
||||
}
|
||||
|
||||
var owner = data.rows[0].owner;
|
||||
var ownerprofile = null;
|
||||
var permission = "freely";
|
||||
if (owner && owner != "null") {
|
||||
permission = "editable";
|
||||
}
|
||||
|
||||
//find or new note
|
||||
Note.findOrNewNote(notename, permission, function (err, note) {
|
||||
if (err) {
|
||||
responseError(res, "404", "Not Found", "oops.");
|
||||
|
@ -333,20 +376,64 @@ function startConnection(socket) {
|
|||
isConnectionBusy = false;
|
||||
return;
|
||||
}
|
||||
|
||||
var body = LZString.decompressFromBase64(data.rows[0].content);
|
||||
//body = LZString.compressToUTF16(body);
|
||||
var createtime = data.rows[0].create_time;
|
||||
var updatetime = data.rows[0].update_time;
|
||||
var server = new ot.EditorSocketIOServer(body, [], notename, ifMayEdit);
|
||||
|
||||
var lastchangeuser = note.lastchangeuser || null;
|
||||
var lastchangeuserprofile = null;
|
||||
|
||||
notes[notename] = {
|
||||
id: notename,
|
||||
owner: owner,
|
||||
ownerprofile: ownerprofile,
|
||||
permission: note.permission,
|
||||
lastchangeuser: lastchangeuser,
|
||||
lastchangeuserprofile: lastchangeuserprofile,
|
||||
socks: [],
|
||||
users: {},
|
||||
createtime: moment(createtime).valueOf(),
|
||||
updatetime: moment(updatetime).valueOf(),
|
||||
server: server
|
||||
};
|
||||
finishConnection(socket, notes[notename], users[socket.id]);
|
||||
|
||||
if (lastchangeuser) {
|
||||
//find last change user profile if lastchangeuser exists
|
||||
User.findUser(lastchangeuser, function (err, user) {
|
||||
if (!err && user && user.profile) {
|
||||
var profile = JSON.parse(user.profile);
|
||||
if (profile) {
|
||||
lastchangeuserprofile = {
|
||||
name: profile.displayName || profile.username,
|
||||
photo: User.parsePhotoByProfile(profile)
|
||||
}
|
||||
notes[notename].lastchangeuserprofile = lastchangeuserprofile;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (owner && owner != "null") {
|
||||
//find owner profile if owner exists
|
||||
User.findUser(owner, function (err, user) {
|
||||
if (!err && user && user.profile) {
|
||||
var profile = JSON.parse(user.profile);
|
||||
if (profile) {
|
||||
ownerprofile = {
|
||||
name: profile.displayName || profile.username,
|
||||
photo: User.parsePhotoByProfile(profile)
|
||||
}
|
||||
notes[notename].ownerprofile = ownerprofile;
|
||||
}
|
||||
}
|
||||
finishConnection(socket, notes[notename], users[socket.id]);
|
||||
});
|
||||
} else {
|
||||
finishConnection(socket, notes[notename], users[socket.id]);
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
|
@ -433,23 +520,7 @@ function updateUserData(socket, user) {
|
|||
//retrieve user data from passport
|
||||
if (socket.request.user && socket.request.user.logged_in) {
|
||||
var profile = JSON.parse(socket.request.user.profile);
|
||||
var photo = null;
|
||||
switch (profile.provider) {
|
||||
case "facebook":
|
||||
photo = 'https://graph.facebook.com/' + profile.id + '/picture';
|
||||
break;
|
||||
case "twitter":
|
||||
photo = profile.photos[0].value;
|
||||
break;
|
||||
case "github":
|
||||
photo = 'https://avatars.githubusercontent.com/u/' + profile.id + '?s=48';
|
||||
break;
|
||||
case "dropbox":
|
||||
//no image api provided, use gravatar
|
||||
photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value);
|
||||
break;
|
||||
}
|
||||
user.photo = photo;
|
||||
user.photo = User.parsePhotoByProfile(profile);
|
||||
user.name = profile.displayName || profile.username;
|
||||
user.userid = socket.request.user._id;
|
||||
user.login = true;
|
||||
|
@ -466,19 +537,28 @@ function ifMayEdit(socket, callback) {
|
|||
var note = notes[notename];
|
||||
var mayEdit = true;
|
||||
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)
|
||||
mayEdit = false;
|
||||
break;
|
||||
case "locked":
|
||||
//only owner can change
|
||||
if (note.owner != socket.request.user._id)
|
||||
mayEdit = false;
|
||||
break;
|
||||
case "freely":
|
||||
//not blocking anyone
|
||||
break;
|
||||
case "editable":
|
||||
//only login user can change
|
||||
if (!socket.request.user || !socket.request.user.logged_in)
|
||||
mayEdit = false;
|
||||
break;
|
||||
case "locked":
|
||||
//only owner can change
|
||||
if (note.owner != socket.request.user._id)
|
||||
mayEdit = false;
|
||||
break;
|
||||
}
|
||||
//if user may edit and this note have owner (not anonymous usage)
|
||||
if (mayEdit && note.owner && note.owner != "null") {
|
||||
//save for the last change user id
|
||||
if (socket.request.user && socket.request.user.logged_in) {
|
||||
note.lastchangeuser = socket.request.user._id;
|
||||
} else {
|
||||
note.lastchangeuser = null;
|
||||
}
|
||||
}
|
||||
callback(mayEdit);
|
||||
}
|
||||
|
|
182
lib/response.js
182
lib/response.js
|
@ -8,6 +8,7 @@ var markdownpdf = require("markdown-pdf");
|
|||
var LZString = require('lz-string');
|
||||
var S = require('string');
|
||||
var shortId = require('shortid');
|
||||
var metaMarked = require('meta-marked');
|
||||
|
||||
//core
|
||||
var config = require("../config.js");
|
||||
|
@ -15,6 +16,7 @@ var config = require("../config.js");
|
|||
//others
|
||||
var db = require("./db.js");
|
||||
var Note = require("./note.js");
|
||||
var User = require("./user.js");
|
||||
|
||||
//slides
|
||||
var md = require('reveal.js/plugin/markdown/markdown');
|
||||
|
@ -104,6 +106,13 @@ function responseHackMD(res, noteId) {
|
|||
responseError(res, "404", "Not Found", "oops.");
|
||||
return;
|
||||
}
|
||||
var body = LZString.decompressFromBase64(data.rows[0].content);
|
||||
var meta = null;
|
||||
try {
|
||||
meta = metaMarked(body).meta;
|
||||
} catch(err) {
|
||||
//na
|
||||
}
|
||||
var title = data.rows[0].title;
|
||||
var decodedTitle = LZString.decompressFromBase64(title);
|
||||
if (decodedTitle) title = decodedTitle;
|
||||
|
@ -116,7 +125,8 @@ function responseHackMD(res, noteId) {
|
|||
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options);
|
||||
var html = compiled({
|
||||
title: title,
|
||||
useCDN: config.usecdn
|
||||
useCDN: config.usecdn,
|
||||
robots: (meta && meta.robots) || false //default allow robots
|
||||
});
|
||||
var buf = html;
|
||||
res.writeHead(200, {
|
||||
|
@ -192,34 +202,47 @@ function showPublishNote(req, res, next) {
|
|||
return;
|
||||
}
|
||||
var body = LZString.decompressFromBase64(data.rows[0].content);
|
||||
var meta = null;
|
||||
try {
|
||||
meta = metaMarked(body).meta;
|
||||
} catch(err) {
|
||||
//na
|
||||
}
|
||||
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 = config.getserverurl();
|
||||
var html = compiled({
|
||||
var data = {
|
||||
title: title,
|
||||
viewcount: note.viewcount,
|
||||
updatetime: updatetime,
|
||||
url: origin,
|
||||
body: text,
|
||||
useCDN: config.usecdn
|
||||
});
|
||||
var buf = html;
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html; charset=UTF-8',
|
||||
'Cache-Control': 'private',
|
||||
'Content-Length': buf.length
|
||||
});
|
||||
res.end(buf);
|
||||
useCDN: config.usecdn,
|
||||
lastchangeuserprofile: null,
|
||||
robots: (meta && meta.robots) || false //default allow robots
|
||||
};
|
||||
if (note.lastchangeuser) {
|
||||
//find last change user profile if lastchangeuser exists
|
||||
User.findUser(note.lastchangeuser, function (err, user) {
|
||||
if (!err && user && user.profile) {
|
||||
var profile = JSON.parse(user.profile);
|
||||
if (profile) {
|
||||
data.lastchangeuserprofile = {
|
||||
name: profile.displayName || profile.username,
|
||||
photo: User.parsePhotoByProfile(profile)
|
||||
}
|
||||
renderPublish(data, res);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
renderPublish(data, res);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -228,6 +251,23 @@ function showPublishNote(req, res, next) {
|
|||
}
|
||||
}
|
||||
|
||||
function renderPublish(data, res) {
|
||||
var template = config.prettypath;
|
||||
var options = {
|
||||
cache: !config.debug,
|
||||
filename: template
|
||||
};
|
||||
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options);
|
||||
var html = compiled(data);
|
||||
var buf = html;
|
||||
res.writeHead(200, {
|
||||
'Content-Type': 'text/html; charset=UTF-8',
|
||||
'Cache-Control': 'private',
|
||||
'Content-Length': buf.length
|
||||
});
|
||||
res.end(buf);
|
||||
}
|
||||
|
||||
function actionPublish(req, res, noteId) {
|
||||
db.readFromDB(noteId, function (err, data) {
|
||||
if (err) {
|
||||
|
@ -269,36 +309,6 @@ function actionSlide(req, res, noteId) {
|
|||
});
|
||||
});
|
||||
}
|
||||
//pretty api is deprecated
|
||||
function actionPretty(req, res, noteId) {
|
||||
db.readFromDB(noteId, function (err, data) {
|
||||
if (err) {
|
||||
responseError(res, "404", "Not Found", "oops.");
|
||||
return;
|
||||
}
|
||||
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 compiled = ejs.compile(fs.readFileSync(template, 'utf8'));
|
||||
var origin = config.getserverurl();
|
||||
var html = compiled({
|
||||
title: title,
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
function actionDownload(req, res, noteId) {
|
||||
db.readFromDB(noteId, function (err, data) {
|
||||
|
@ -325,6 +335,11 @@ function actionPDF(req, res, noteId) {
|
|||
return;
|
||||
}
|
||||
var body = LZString.decompressFromBase64(data.rows[0].content);
|
||||
try {
|
||||
body = metaMarked(body).markdown;
|
||||
} catch(err) {
|
||||
//na
|
||||
}
|
||||
var title = Note.getNoteTitle(body);
|
||||
|
||||
if (!fs.existsSync(config.tmppath)) {
|
||||
|
@ -361,46 +376,46 @@ function noteActions(req, res, next) {
|
|||
}
|
||||
var action = req.params.action;
|
||||
switch (action) {
|
||||
case "publish":
|
||||
case "pretty": //pretty deprecated
|
||||
actionPublish(req, res, noteId);
|
||||
break;
|
||||
case "slide":
|
||||
actionSlide(req, res, noteId);
|
||||
break;
|
||||
case "download":
|
||||
actionDownload(req, res, noteId);
|
||||
break;
|
||||
case "pdf":
|
||||
actionPDF(req, res, noteId);
|
||||
break;
|
||||
default:
|
||||
if (noteId != config.featuresnotename)
|
||||
res.redirect('/' + LZString.compressToBase64(noteId));
|
||||
else
|
||||
res.redirect('/' + noteId);
|
||||
break;
|
||||
case "publish":
|
||||
case "pretty": //pretty deprecated
|
||||
actionPublish(req, res, noteId);
|
||||
break;
|
||||
case "slide":
|
||||
actionSlide(req, res, noteId);
|
||||
break;
|
||||
case "download":
|
||||
actionDownload(req, res, noteId);
|
||||
break;
|
||||
case "pdf":
|
||||
actionPDF(req, res, noteId);
|
||||
break;
|
||||
default:
|
||||
if (noteId != config.featuresnotename)
|
||||
res.redirect('/' + LZString.compressToBase64(noteId));
|
||||
else
|
||||
res.redirect('/' + noteId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function publishNoteActions(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;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -424,6 +439,11 @@ function showPublishSlide(req, res, next) {
|
|||
return;
|
||||
}
|
||||
var body = LZString.decompressFromBase64(data.rows[0].content);
|
||||
try {
|
||||
body = metaMarked(body).markdown;
|
||||
} catch(err) {
|
||||
//na
|
||||
}
|
||||
var title = data.rows[0].title;
|
||||
var decodedTitle = LZString.decompressFromBase64(title);
|
||||
if (decodedTitle) title = decodedTitle;
|
||||
|
|
34
lib/user.js
34
lib/user.js
|
@ -1,6 +1,7 @@
|
|||
//user
|
||||
//external modules
|
||||
var mongoose = require('mongoose');
|
||||
var md5 = require("blueimp-md5").md5;
|
||||
|
||||
//core
|
||||
var config = require("../config.js");
|
||||
|
@ -20,9 +21,30 @@ var user = {
|
|||
findUser: findUser,
|
||||
newUser: newUser,
|
||||
findOrNewUser: findOrNewUser,
|
||||
getUserCount: getUserCount
|
||||
getUserCount: getUserCount,
|
||||
parsePhotoByProfile: parsePhotoByProfile
|
||||
};
|
||||
|
||||
function parsePhotoByProfile(profile) {
|
||||
var photo = null;
|
||||
switch (profile.provider) {
|
||||
case "facebook":
|
||||
photo = 'https://graph.facebook.com/' + profile.id + '/picture';
|
||||
break;
|
||||
case "twitter":
|
||||
photo = profile.photos[0].value;
|
||||
break;
|
||||
case "github":
|
||||
photo = 'https://avatars.githubusercontent.com/u/' + profile.id + '?s=48';
|
||||
break;
|
||||
case "dropbox":
|
||||
//no image api provided, use gravatar
|
||||
photo = 'https://www.gravatar.com/avatar/' + md5(profile.emails[0].value);
|
||||
break;
|
||||
}
|
||||
return photo;
|
||||
}
|
||||
|
||||
function getUserCount(callback) {
|
||||
model.count(function(err, count){
|
||||
if(err) callback(err, null);
|
||||
|
@ -31,9 +53,13 @@ function getUserCount(callback) {
|
|||
}
|
||||
|
||||
function findUser(id, callback) {
|
||||
model.findOne({
|
||||
id: id
|
||||
}, function (err, user) {
|
||||
var rule = {};
|
||||
var checkForHexRegExp = new RegExp("^[0-9a-fA-F]{24}$");
|
||||
if (checkForHexRegExp.test(id))
|
||||
rule._id = id;
|
||||
else
|
||||
rule.id = id;
|
||||
model.findOne(rule, function (err, user) {
|
||||
if (err) {
|
||||
logger.error('find user failed: ' + err);
|
||||
callback(err, null);
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
"lz-string": "1.4.4",
|
||||
"markdown-pdf": "^6.0.0",
|
||||
"marked": "^0.3.5",
|
||||
"meta-marked": "^0.4.0",
|
||||
"method-override": "^2.3.5",
|
||||
"moment": "^2.10.6",
|
||||
"mongoose": "^4.3.1",
|
||||
|
|
|
@ -56,6 +56,7 @@ h6:hover .header-link {
|
|||
.header-link {
|
||||
position: relative;
|
||||
left: 0.5em;
|
||||
right: 0.5em;
|
||||
opacity: 0;
|
||||
font-size: 0.8em;
|
||||
-webkit-transition: opacity 0.2s ease-in-out 0.1s;
|
||||
|
@ -114,6 +115,12 @@ h6:hover .header-link {
|
|||
width: 25vw;
|
||||
max-height: 65vh;
|
||||
overflow: auto;
|
||||
text-align: inherit;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown[dir='rtl'] .nav {
|
||||
padding-right: 0;
|
||||
letter-spacing: 0.0029em;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown a {
|
||||
|
@ -138,6 +145,12 @@ h6:hover .header-link {
|
|||
border-left: 1px solid black;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown[dir='rtl'] .nav>li>a:focus,.ui-toc-dropdown[dir='rtl'] .nav>li>a:hover {
|
||||
padding-right: 19px;
|
||||
border-left: none;
|
||||
border-right: 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;
|
||||
|
@ -146,6 +159,12 @@ h6:hover .header-link {
|
|||
border-left: 2px solid black;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown[dir='rtl'] .nav>.active:focus>a,.ui-toc-dropdown[dir='rtl'] .nav>.active:hover>a,.ui-toc-dropdown[dir='rtl'] .nav>.active>a {
|
||||
padding-right: 18px;
|
||||
border-left: none;
|
||||
border-right: 2px solid black;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown .nav .nav {
|
||||
display: none;
|
||||
padding-bottom: 10px;
|
||||
|
@ -163,6 +182,10 @@ h6:hover .header-link {
|
|||
font-weight: 400;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown[dir='rtl'] .nav .nav>li>a {
|
||||
padding-right: 30px;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown .nav .nav>li>ul>li>a {
|
||||
padding-top: 1px;
|
||||
padding-bottom: 1px;
|
||||
|
@ -171,24 +194,44 @@ h6:hover .header-link {
|
|||
font-weight: 400;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown[dir='rtl'] .nav .nav>li>ul>li>a {
|
||||
padding-right: 40px;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown .nav .nav>li>a:focus,.ui-toc-dropdown .nav .nav>li>a:hover {
|
||||
padding-left: 29px;
|
||||
}
|
||||
|
||||
.ui-toc-dropdown[dir='rtl'] .nav .nav>li>a:focus,.ui-toc-dropdown[dir='rtl'] .nav .nav>li>a:hover {
|
||||
padding-right: 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[dir='rtl'] .nav .nav>li>ul>li>a:focus,.ui-toc-dropdown[dir='rtl'] .nav .nav>li>ul>li>a:hover {
|
||||
padding-right: 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[dir='rtl'] .nav .nav>.active:focus>a,.ui-toc-dropdown[dir='rtl'] .nav .nav>.active:hover>a,.ui-toc-dropdown[dir='rtl'] .nav .nav>.active>a {
|
||||
padding-right: 28px;
|
||||
}
|
||||
|
||||
.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-toc-dropdown[dir='rtl'] .nav .nav>.active>.nav>.active:focus>a,.ui-toc-dropdown[dir='rtl'] .nav .nav>.active>.nav>.active:hover>a,.ui-toc-dropdown[dir='rtl'] .nav .nav>.active>.nav>.active>a {
|
||||
padding-right: 38px;
|
||||
}
|
||||
|
||||
.ui-affix-toc {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
|
@ -216,6 +259,26 @@ h6:hover .header-link {
|
|||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ui-user-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
margin-right: 5px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
.ui-user-icon.small {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
margin: 0 0 0.2em 0;
|
||||
}
|
||||
|
||||
small span {
|
||||
line-height: 22px;
|
||||
}
|
||||
|
|
2
public/css/html.min.css
vendored
2
public/css/html.min.css
vendored
File diff suppressed because one or more lines are too long
|
@ -156,18 +156,7 @@ body {
|
|||
.ui-user-name {
|
||||
margin-top: 2px;
|
||||
}
|
||||
.ui-user-icon {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
display: block;
|
||||
border-radius: 3px;
|
||||
margin-top: 2px;
|
||||
margin-bottom: 2px;
|
||||
margin-right: 5px;
|
||||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
|
||||
.ui-user-status {
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
@ -213,7 +202,6 @@ body {
|
|||
width: 0px;
|
||||
position: absolute;
|
||||
border-right: none;
|
||||
transition: left 0.1s, top 0.1s;
|
||||
}
|
||||
.dropdown-menu.other-cursor {
|
||||
transition: none;
|
||||
|
@ -243,6 +231,10 @@ div[contenteditable]:empty:not(:focus):before{
|
|||
max-height: 80vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.dropdown-menu.list.small {
|
||||
max-height: 40vh;
|
||||
overflow: auto;
|
||||
}
|
||||
.dropdown-menu.list::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
|
@ -279,6 +271,25 @@ div[contenteditable]:empty:not(:focus):before{
|
|||
display: block;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
width: 36%;
|
||||
text-align: right;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
margin-right: 6px;
|
||||
}
|
||||
.popover {
|
||||
width: 100%;
|
||||
font-family: inherit !important;
|
||||
line-height: 25px;
|
||||
}
|
||||
|
||||
.text-ellipsis {
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cm-trailing-space-a:before,
|
||||
.cm-trailing-space-b:before,
|
||||
.cm-trailing-space-new-line:before {
|
||||
|
|
|
@ -36,7 +36,6 @@
|
|||
text-align: right;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
float: right;
|
||||
cursor: default;
|
||||
z-index: 4;
|
||||
padding: 0 8px 0 0;
|
||||
|
@ -89,6 +88,18 @@
|
|||
.markdown-body .flow-chart {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
/*fixed style for rtl in pre and code*/
|
||||
|
||||
.markdown-body[dir='rtl'] pre {
|
||||
direction: ltr;
|
||||
}
|
||||
|
||||
.markdown-body[dir='rtl'] code {
|
||||
direction: ltr;
|
||||
unicode-bidi: embed;
|
||||
}
|
||||
|
||||
svg {
|
||||
width: 100%;
|
||||
max-height: 70vh;
|
||||
|
|
|
@ -1,15 +1,32 @@
|
|||
//auto update last change
|
||||
var lastchangetime = null;
|
||||
var lastchangeui = null;
|
||||
var lastchangeui = {
|
||||
time: $(".ui-lastchange"),
|
||||
user: $(".ui-lastchangeuser"),
|
||||
nouser: $(".ui-no-lastchangeuser")
|
||||
}
|
||||
|
||||
function updateLastChange() {
|
||||
if (lastchangetime && lastchangeui) {
|
||||
lastchangeui.html(' <i class="fa fa-clock-o"></i> change ' + moment(lastchangetime).fromNow());
|
||||
lastchangeui.attr('title', moment(lastchangetime).format('llll'));
|
||||
lastchangeui.time.html(moment(lastchangetime).fromNow());
|
||||
lastchangeui.time.attr('title', moment(lastchangetime).format('llll'));
|
||||
}
|
||||
}
|
||||
setInterval(updateLastChange, 60000);
|
||||
|
||||
function updateLastChangeUser(data) {
|
||||
if (data.lastchangeuserprofile) {
|
||||
var icon = lastchangeui.user.children('i');
|
||||
icon.attr('title', data.lastchangeuserprofile.name).tooltip('fixTitle');
|
||||
icon.attr('style', 'background-image:url(' + data.lastchangeuserprofile.photo + ')');
|
||||
lastchangeui.user.show();
|
||||
lastchangeui.nouser.hide();
|
||||
} else {
|
||||
lastchangeui.user.hide();
|
||||
lastchangeui.nouser.show();
|
||||
}
|
||||
}
|
||||
|
||||
//get title
|
||||
function getTitle(view) {
|
||||
var h1s = view.find("h1");
|
||||
|
@ -48,6 +65,57 @@ function slugifyWithUTF8(text) {
|
|||
return newText;
|
||||
}
|
||||
|
||||
//parse meta
|
||||
function parseMeta(md, view, toc, tocAffix) {
|
||||
var robots = null;
|
||||
var lang = null;
|
||||
var dir = null;
|
||||
var breaks = true;
|
||||
if (md && md.meta) {
|
||||
var meta = md.meta;
|
||||
robots = meta.robots;
|
||||
lang = meta.lang;
|
||||
dir = meta.dir;
|
||||
breaks = meta.breaks;
|
||||
}
|
||||
//robots meta
|
||||
var robotsMeta = $('meta[name=robots]');
|
||||
if (robots) {
|
||||
if (robotsMeta.length > 0)
|
||||
robotsMeta.attr('content', robots);
|
||||
else
|
||||
$('head').prepend('<meta name="robots" content="' + robots + '">')
|
||||
}
|
||||
else
|
||||
robotsMeta.remove();
|
||||
//text language
|
||||
if (lang) {
|
||||
view.attr('lang', lang);
|
||||
toc.attr('lang', lang);
|
||||
tocAffix.attr('lang', lang);
|
||||
} else {
|
||||
view.removeAttr('lang');
|
||||
toc.removeAttr('lang');
|
||||
tocAffix.removeAttr('lang');
|
||||
}
|
||||
//text direction
|
||||
if (dir) {
|
||||
view.attr('dir', dir);
|
||||
toc.attr('dir', dir);
|
||||
tocAffix.attr('dir', dir);
|
||||
} else {
|
||||
view.removeAttr('dir');
|
||||
toc.removeAttr('dir');
|
||||
tocAffix.removeAttr('dir');
|
||||
}
|
||||
//breaks
|
||||
if (typeof breaks === 'boolean' && !breaks) {
|
||||
md.options.breaks = false;
|
||||
} else {
|
||||
md.options.breaks = true;
|
||||
}
|
||||
}
|
||||
|
||||
var viewAjaxCallback = null;
|
||||
|
||||
//regex for extra tags
|
||||
|
@ -329,7 +397,10 @@ function exportToHTML(view) {
|
|||
css: css,
|
||||
html: src[0].outerHTML,
|
||||
toc: toc.html(),
|
||||
'toc-affix': tocAffix.html()
|
||||
'toc-affix': tocAffix.html(),
|
||||
robots: (md && md.meta && md.meta.robots) ? '<meta name="robots" content="' + md.meta.robots + '">' : null,
|
||||
lang: (md && md.meta && md.meta.lang) ? 'lang="' + md.meta.lang + '"' : null,
|
||||
dir: (md && md.meta && md.meta.dir) ? 'dir="' + md.meta.dir + '"' : null
|
||||
};
|
||||
var html = template(context);
|
||||
// console.log(html);
|
||||
|
@ -737,6 +808,49 @@ var speakerdeckPlugin = new Plugin(
|
|||
return div[0].outerHTML;
|
||||
}
|
||||
);
|
||||
|
||||
//yaml meta, from https://github.com/eugeneware/remarkable-meta
|
||||
function get(state, line) {
|
||||
var pos = state.bMarks[line];
|
||||
var max = state.eMarks[line];
|
||||
return state.src.substr(pos, max - pos);
|
||||
}
|
||||
|
||||
function meta(state, start, end, silent) {
|
||||
if (start !== 0 || state.blkIndent !== 0) return false;
|
||||
if (state.tShift[start] < 0) return false;
|
||||
if (!get(state, start).match(/^---$/)) return false;
|
||||
|
||||
var data = [];
|
||||
for (var line = start + 1; line < end; line++) {
|
||||
var str = get(state, line);
|
||||
if (str.match(/^(\.{3}|-{3})$/)) break;
|
||||
if (state.tShift[line] < 0) break;
|
||||
data.push(str);
|
||||
}
|
||||
|
||||
if (line >= end) return false;
|
||||
|
||||
try {
|
||||
md.meta = jsyaml.safeLoad(data.join('\n')) || {};
|
||||
} catch(err) {
|
||||
console.error(err);
|
||||
return false;
|
||||
}
|
||||
|
||||
state.line = line + 1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function metaPlugin(md) {
|
||||
md.meta = md.meta || {};
|
||||
md.block.ruler.before('code', 'meta', meta, {
|
||||
alt: []
|
||||
});
|
||||
}
|
||||
|
||||
md.use(metaPlugin);
|
||||
md.use(youtubePlugin);
|
||||
md.use(vimeoPlugin);
|
||||
md.use(gistPlugin);
|
||||
|
|
|
@ -320,6 +320,8 @@ var ui = {
|
|||
},
|
||||
infobar: {
|
||||
lastchange: $(".ui-lastchange"),
|
||||
lastchangeuser: $(".ui-lastchangeuser"),
|
||||
nolastchangeuser: $(".ui-no-lastchangeuser"),
|
||||
permission: {
|
||||
permission: $(".ui-permission"),
|
||||
label: $(".ui-permission-label"),
|
||||
|
@ -387,9 +389,9 @@ function setHaveUnreadChanges(bool) {
|
|||
function updateTitleReminder() {
|
||||
if (!loaded) return;
|
||||
if (haveUnreadChanges) {
|
||||
document.title = '• ' + renderTitle(ui.area.view);
|
||||
document.title = '• ' + renderTitle(ui.area.markdown);
|
||||
} else {
|
||||
document.title = renderTitle(ui.area.view);
|
||||
document.title = renderTitle(ui.area.markdown);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -465,6 +467,8 @@ $(document).ready(function () {
|
|||
upClass: 'navbar-hide',
|
||||
downClass: 'navbar-show'
|
||||
});
|
||||
//tooltip
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
//when page resize
|
||||
$(window).resize(function () {
|
||||
|
@ -1165,8 +1169,8 @@ socket.on('version', function (data) {
|
|||
});
|
||||
socket.on('check', function (data) {
|
||||
lastchangetime = data.updatetime;
|
||||
lastchangeui = ui.infobar.lastchange;
|
||||
updateLastChange();
|
||||
updateLastChangeUser(data);
|
||||
});
|
||||
socket.on('permission', function (data) {
|
||||
updatePermission(data.permission);
|
||||
|
@ -1182,8 +1186,8 @@ socket.on('refresh', function (data) {
|
|||
owner = data.owner;
|
||||
updatePermission(data.permission);
|
||||
lastchangetime = data.updatetime;
|
||||
lastchangeui = ui.infobar.lastchange;
|
||||
updateLastChange();
|
||||
updateLastChangeUser(data);
|
||||
if (!loaded) {
|
||||
changeMode(currentMode);
|
||||
loaded = true;
|
||||
|
@ -1884,15 +1888,18 @@ var lastResult = null;
|
|||
function updateViewInner() {
|
||||
if (currentMode == modeType.edit || !isDirty) return;
|
||||
var value = editor.getValue();
|
||||
md.meta = {};
|
||||
md.render(value); //only for get meta
|
||||
parseMeta(md, ui.area.markdown, $('#toc'), $('#toc-affix'));
|
||||
var result = postProcess(md.render(value)).children().toArray();
|
||||
partialUpdate(result, lastResult, ui.area.markdown.children().toArray());
|
||||
if (result && lastResult && result.length != lastResult.length)
|
||||
updateDataAttrs(result, ui.area.markdown.children().toArray());
|
||||
lastResult = $(result).clone();
|
||||
finishView(ui.area.view);
|
||||
autoLinkify(ui.area.view);
|
||||
deduplicatedHeaderId(ui.area.view);
|
||||
renderTOC(ui.area.view);
|
||||
finishView(ui.area.markdown);
|
||||
autoLinkify(ui.area.markdown);
|
||||
deduplicatedHeaderId(ui.area.markdown);
|
||||
renderTOC(ui.area.markdown);
|
||||
generateToc('toc');
|
||||
generateToc('toc-affix');
|
||||
generateScrollspy();
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
var markdown = $(".markdown-body");
|
||||
var text = $('<textarea/>').html(markdown.html()).text();
|
||||
md.meta = {};
|
||||
md.render(text); //only for get meta
|
||||
parseMeta(md, markdown, $('#toc'), $('#toc-affix'));
|
||||
var result = postProcess(md.render(text));
|
||||
markdown.html(result.html());
|
||||
$(document.body).show();
|
||||
|
@ -10,8 +13,7 @@ renderTOC(markdown);
|
|||
generateToc('toc');
|
||||
generateToc('toc-affix');
|
||||
smoothHashScroll();
|
||||
lastchangetime = $('.ui-lastchange').text();
|
||||
lastchangeui = $('.ui-lastchange');
|
||||
lastchangetime = lastchangeui.time.text();
|
||||
updateLastChange();
|
||||
var url = window.location.pathname;
|
||||
$('.ui-edit').attr('href', url + '/edit');
|
||||
|
@ -68,6 +70,8 @@ $(window).resize(function () {
|
|||
$(document).ready(function () {
|
||||
windowResize();
|
||||
generateScrollspy();
|
||||
//tooltip
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
|
||||
function scrollToTop() {
|
||||
|
|
|
@ -5,7 +5,12 @@
|
|||
<div class="ui-view-area">
|
||||
<div class="ui-infobar container-fluid unselectable hidden-print">
|
||||
<small>
|
||||
<span class="ui-lastchange text-uppercase"></span>
|
||||
<span>
|
||||
<span class="ui-lastchangeuser"> <i class="ui-user-icon small" data-toggle="tooltip" data-placement="right"></i></span>
|
||||
<span class="ui-no-lastchangeuser"> <i class="fa fa-clock-o"></i></span>
|
||||
<span class="text-uppercase">changed</span>
|
||||
<span class="ui-lastchange text-uppercase"></span>
|
||||
</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>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/TweenMax.min.js" defer></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/gsap/1.18.0/jquery.gsap.min.js" defer></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/socket.io/1.3.7/socket.io.min.js" defer></script>
|
||||
<script src="//cdnjs.cloudflare.com/ajax/libs/js-yaml/3.4.6/js-yaml.min.js" defer></script>
|
||||
<% } else { %>
|
||||
<script src="/vendor/spin.js/spin.min.js" defer></script>
|
||||
<script src="/vendor/jquery/dist/jquery.min.js"></script>
|
||||
|
@ -14,6 +15,7 @@
|
|||
<script src="/vendor/gsap/src/minified/TweenMax.min.js" defer></script>
|
||||
<script src="/vendor/gsap/src/minified/jquery.gsap.min.js" defer></script>
|
||||
<script src="/vendor/socket.io-client/socket.io.js" defer></script>
|
||||
<script src="/vendor/js-yaml/dist/js-yaml.min.js" defer></script>
|
||||
<% } %>
|
||||
<script src="/vendor/jquery-ui/jquery-ui.min.js" defer></script>
|
||||
<!--codemirror-->
|
||||
|
|
|
@ -4,6 +4,9 @@
|
|||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<% if(typeof robots !== 'undefined' && robots) { %>
|
||||
<meta name="robots" content="<%- robots %>">
|
||||
<% } %>
|
||||
<title><%- title %></title>
|
||||
<link rel="icon" type="image/png" href="/favicon.png">
|
||||
<link rel="apple-touch-icon" href="/apple-touch-icon.png">
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
{{{robots}}}
|
||||
<title>
|
||||
{{title}}
|
||||
</title>
|
||||
|
@ -42,7 +43,7 @@
|
|||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div id="toc-affix" class="ui-affix-toc ui-toc-dropdown unselectable hidden-print" data-spy="affix" style="top:17px;display:none;">
|
||||
<div id="toc-affix" class="ui-affix-toc ui-toc-dropdown unselectable hidden-print" data-spy="affix" style="top:17px;display:none;" {{{lang}}} {{{dir}}}>
|
||||
{{{toc-affix}}}
|
||||
</div>
|
||||
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
<meta name="apple-mobile-web-app-capable" content="yes">
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="black">
|
||||
<meta name="mobile-web-app-capable" content="yes">
|
||||
<% if(typeof robots !== 'undefined' && robots) { %>
|
||||
<meta name="robots" content="<%- robots %>">
|
||||
<% } %>
|
||||
<title><%- title %></title>
|
||||
<link rel="icon" type="image/png" href="<%- url %>/favicon.png">
|
||||
<link rel="apple-touch-icon" href="<%- url %>/apple-touch-icon.png">
|
||||
|
@ -37,13 +40,19 @@
|
|||
<body style="display:none;">
|
||||
<div class="ui-infobar container-fluid unselectable hidden-print">
|
||||
<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>
|
||||
<span>
|
||||
<% if(lastchangeuserprofile) { %>
|
||||
<span class="ui-lastchangeuser"> <i class="ui-user-icon small" style="background-image: url(<%- lastchangeuserprofile.photo %>);" data-toggle="tooltip" data-placement="right" title="<%- lastchangeuserprofile.name %>"></i></span>
|
||||
<% } else { %>
|
||||
<span class="ui-no-lastchangeuser"> <i class="fa fa-clock-o"></i></span>
|
||||
<% } %>
|
||||
<span class="text-uppercase">changed</span>
|
||||
<span class="ui-lastchange text-uppercase"><%- updatetime %></span>
|
||||
</span>
|
||||
<span class="pull-right"><%- viewcount %> views <a href="#" class="ui-edit" title="Edit this note"><i class="fa fa-fw fa-pencil"></i></a></span>
|
||||
</small>
|
||||
</div>
|
||||
<div id="doc" class="container markdown-body">
|
||||
<%- body %>
|
||||
</div>
|
||||
<div id="doc" class="container markdown-body"><%- body %></div>
|
||||
<div class="ui-toc dropup unselectable hidden-print" 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">
|
||||
|
@ -60,9 +69,11 @@
|
|||
<% if(useCDN) { %>
|
||||
<script src="https://code.jquery.com/jquery-1.11.3.min.js"></script>
|
||||
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js" defer></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/js-yaml/3.4.6/js-yaml.min.js" defer></script>
|
||||
<% } else { %>
|
||||
<script src="<%- url %>/vendor/jquery/dist/jquery.min.js"></script>
|
||||
<script src="<%- url %>/vendor/bootstrap/dist/js/bootstrap.min.js" defer></script>
|
||||
<script src="<%- url %>/vendor/js-yaml/dist/js-yaml.min.js" defer></script>
|
||||
<% } %>
|
||||
<script src="<%- url %>/vendor/lz-string.min.js" defer></script>
|
||||
<script src="<%- url %>/vendor/remarkable.min.js" defer></script>
|
||||
|
|
Loading…
Reference in a new issue