Update to support new metadata: title, description, tags and google-analytics (GA) and refactor render publish slide response function

This commit is contained in:
Cheng-Han, Wu 2016-06-21 21:42:03 +08:00
parent ad6982e77e
commit 558304ff62
8 changed files with 160 additions and 44 deletions

View file

@ -5,6 +5,7 @@ var fs = require('fs');
var path = require('path'); var path = require('path');
var LZString = require('lz-string'); var LZString = require('lz-string');
var marked = require('marked'); var marked = require('marked');
var metaMarked = require('meta-marked');
var cheerio = require('cheerio'); var cheerio = require('cheerio');
var shortId = require('shortid'); var shortId = require('shortid');
var Sequelize = require("sequelize"); var Sequelize = require("sequelize");
@ -187,13 +188,24 @@ module.exports = function (sequelize, DataTypes) {
}); });
}, },
parseNoteTitle: function (body) { parseNoteTitle: function (body) {
var $ = cheerio.load(marked(body));
var h1s = $("h1");
var title = ""; var title = "";
if (h1s.length > 0 && h1s.first().text().split('\n').length == 1) var meta = null;
title = h1s.first().text(); try {
else var obj = metaMarked(body);
title = "Untitled"; body = obj.markdown;
meta = obj.meta;
} catch (err) {
//na
}
if (meta && meta.title && (typeof meta.title == "string" || typeof meta.title == "number")) {
title = meta.title;
} else {
var $ = cheerio.load(marked(body));
var h1s = $("h1");
if (h1s.length > 0 && h1s.first().text().split('\n').length == 1)
title = h1s.first().text();
}
if (!title) title = "Untitled";
return title; return title;
}, },
decodeTitle: function (title) { decodeTitle: function (title) {
@ -205,6 +217,20 @@ module.exports = function (sequelize, DataTypes) {
generateWebTitle: function (title) { generateWebTitle: function (title) {
title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD"; title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD";
return title; return title;
},
parseMeta: function (meta) {
var _meta = {};
if (meta) {
if (meta.title && (typeof meta.title == "string" || typeof meta.title == "number"))
_meta.title = meta.title;
if (meta.description && (typeof meta.description == "string" || typeof meta.description == "number"))
_meta.description = meta.description;
if (meta.robots && (typeof meta.robots == "string" || typeof meta.robots == "number"))
_meta.robots = meta.robots;
if (meta.GA && (typeof meta.GA == "string" || typeof meta.GA == "number"))
_meta.GA = meta.GA;
}
return _meta;
} }
}, },
hooks: { hooks: {

View file

@ -21,7 +21,7 @@ var models = require("./models");
var md = require('reveal.js/plugin/markdown/markdown'); var md = require('reveal.js/plugin/markdown/markdown');
//reveal.js //reveal.js
var opts = { var slideOptions = {
template: fs.readFileSync(config.slidepath).toString(), template: fs.readFileSync(config.slidepath).toString(),
theme: 'css/theme/black.css', theme: 'css/theme/black.css',
highlightTheme: 'zenburn', highlightTheme: 'zenburn',
@ -107,7 +107,7 @@ function responseHackMD(res, note) {
var body = LZString.decompressFromBase64(note.content); var body = LZString.decompressFromBase64(note.content);
var meta = null; var meta = null;
try { try {
meta = metaMarked(body).meta; meta = models.Note.parseMeta(metaMarked(body).meta);
} catch(err) { } catch(err) {
//na //na
} }
@ -121,7 +121,7 @@ function responseHackMD(res, note) {
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options); var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options);
var html = compiled({ var html = compiled({
url: config.serverurl, url: config.serverurl,
title: title, title: meta.title || title,
useCDN: config.usecdn, useCDN: config.usecdn,
facebook: config.facebook, facebook: config.facebook,
twitter: config.twitter, twitter: config.twitter,
@ -212,7 +212,7 @@ function showPublishNote(req, res, next) {
var body = LZString.decompressFromBase64(note.content); var body = LZString.decompressFromBase64(note.content);
var meta = null; var meta = null;
try { try {
meta = metaMarked(body).meta; meta = models.Note.parseMeta(metaMarked(body).meta);
} catch(err) { } catch(err) {
//na //na
} }
@ -223,7 +223,8 @@ function showPublishNote(req, res, next) {
title = models.Note.generateWebTitle(title); title = models.Note.generateWebTitle(title);
var origin = config.serverurl; var origin = config.serverurl;
var data = { var data = {
title: title, title: meta.title || title,
description: meta.description,
viewcount: note.viewcount, viewcount: note.viewcount,
createtime: createtime, createtime: createtime,
updatetime: updatetime, updatetime: updatetime,
@ -231,7 +232,8 @@ function showPublishNote(req, res, next) {
body: text, body: text,
useCDN: config.usecdn, useCDN: config.usecdn,
lastchangeuserprofile: note.lastchangeuser ? models.User.parseProfile(note.lastchangeuser.profile) : null, lastchangeuserprofile: note.lastchangeuser ? models.User.parseProfile(note.lastchangeuser.profile) : null,
robots: (meta && meta.robots) || false //default allow robots robots: meta.robots || false, //default allow robots
GA: meta.GA
}; };
return renderPublish(data, res); return renderPublish(data, res);
}).catch(function (err) { }).catch(function (err) {
@ -527,14 +529,28 @@ function showPublishSlide(req, res, next) {
} }
var body = LZString.decompressFromBase64(note.content); var body = LZString.decompressFromBase64(note.content);
try { try {
body = metaMarked(body).markdown; var obj = metaMarked(body);
body = obj.markdown;
meta = models.Note.parseMeta(obj.meta);
} catch(err) { } catch(err) {
//na //na
} }
var text = S(body).escapeHTML().s;
var title = models.Note.decodeTitle(note.title); var title = models.Note.decodeTitle(note.title);
title = models.Note.generateWebTitle(title); title = models.Note.generateWebTitle(title);
var text = S(body).escapeHTML().s; var slides = md.slidify(text, slideOptions);
render(res, title, text); var origin = config.serverurl;
var data = {
url: origin,
title: meta.title || title,
description: meta.description,
theme: slideOptions.theme,
highlightTheme: slideOptions.highlightTheme,
slides: slides,
options: JSON.stringify(slideOptions.revealOptions, null, 2),
GA: meta.GA
};
return renderPublishSlide(data, res);
}).catch(function (err) { }).catch(function (err) {
logger.error(err); logger.error(err);
return response.errorInternalError(res); return response.errorInternalError(res);
@ -542,24 +558,14 @@ function showPublishSlide(req, res, next) {
}); });
} }
//reveal.js render function renderPublishSlide(data, res) {
var render = function (res, title, markdown) {
var slides = md.slidify(markdown, opts);
var template = config.slidepath; var template = config.slidepath;
var options = { var options = {
cache: !config.debug, cache: !config.debug,
filename: template filename: template
}; };
var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options); var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options);
var html = compiled({ var html = compiled(data);
url: config.serverurl,
title: title,
theme: opts.theme,
highlightTheme: opts.highlightTheme,
slides: slides,
options: JSON.stringify(opts.revealOptions, null, 2)
});
var buf = html; var buf = html;
res.writeHead(200, { res.writeHead(200, {
'Content-Type': 'text/html; charset=UTF-8', 'Content-Type': 'text/html; charset=UTF-8',
@ -567,6 +573,6 @@ var render = function (res, title, markdown) {
'Content-Length': buf.length 'Content-Length': buf.length
}); });
res.end(buf); res.end(buf);
}; }
module.exports = response; module.exports = response;

View file

@ -18,6 +18,39 @@ YAML metas
Replace the "YAML metas" in this section with any YAML options as below. Replace the "YAML metas" in this section with any YAML options as below.
You can also refer to this note's source code. You can also refer to this note's source code.
title
---
This option will set the note title which prior than content title.
> default: not set
**Example**
```xml
title: meta title
```
description
---
This option will set the note description.
> default: not set
**Example**
```xml
description: meta description
```
tags
---
This option will set the tags which prior than content tags.
> default: not set
**Example**
```xml
tags: features, cool, updated
```
robots robots
--- ---
This option will give below meta in the note head meta: This option will give below meta in the note head meta:
@ -26,7 +59,7 @@ This option will give below meta in the note head meta:
``` ```
So you can prevent any search engine index your note by set `noindex, nofollow`. So you can prevent any search engine index your note by set `noindex, nofollow`.
> default: not > default: not set
**Example** **Example**
```xml ```xml

View file

@ -43,12 +43,16 @@ function updateLastChangeUser() {
//get title //get title
function getTitle(view) { function getTitle(view) {
var h1s = view.find("h1");
var title = ""; var title = "";
if (h1s.length > 0) { if (md && md.meta && md.meta.title && (typeof md.meta.title == "string" || typeof md.meta.title == "number")) {
title = h1s.first().text(); title = md.meta.title;
} else { } else {
title = null; var h1s = view.find("h1");
if (h1s.length > 0) {
title = h1s.first().text();
} else {
title = null;
}
} }
return title; return title;
} }
@ -93,7 +97,7 @@ function parseMeta(md, edit, view, toc, tocAffix) {
spellcheck = meta.spellcheck; spellcheck = meta.spellcheck;
} }
//text language //text language
if (lang) { if (lang && typeof lang == "string") {
view.attr('lang', lang); view.attr('lang', lang);
toc.attr('lang', lang); toc.attr('lang', lang);
tocAffix.attr('lang', lang); tocAffix.attr('lang', lang);
@ -107,7 +111,7 @@ function parseMeta(md, edit, view, toc, tocAffix) {
edit.removeAttr('lang', lang); edit.removeAttr('lang', lang);
} }
//text direction //text direction
if (dir) { if (dir && typeof dir == "string") {
view.attr('dir', dir); view.attr('dir', dir);
toc.attr('dir', dir); toc.attr('dir', dir);
tocAffix.attr('dir', dir); tocAffix.attr('dir', dir);

View file

@ -202,28 +202,44 @@ function writeHistoryToStorage(view) {
} }
} }
if (!Array.isArray) {
Array.isArray = function(arg) {
return Object.prototype.toString.call(arg) === '[object Array]';
};
}
function renderHistory(view) { function renderHistory(view) {
var title = renderFilename(view); var title = renderFilename(view);
var tags = []; var tags = [];
var rawtags = []; var rawtags = [];
view.find('h6').each(function (key, value) { if (md && md.meta && md.meta.tags && (typeof md.meta.tags == "string" || typeof md.meta.tags == "number")) {
if (/^tags/gmi.test($(value).text())) { var metaTags = ('' + md.meta.tags).split(',');
var codes = $(value).find("code"); for (var i = 0; i < metaTags.length; i++) {
for (var i = 0; i < codes.length; i++) var text = metaTags[i].trim();
rawtags.push(codes[i]); if (text) rawtags.push(text);
} }
}); } else {
view.find('h6').each(function (key, value) {
if (/^tags/gmi.test($(value).text())) {
var codes = $(value).find("code");
for (var i = 0; i < codes.length; i++) {
var text = codes[i].innerHTML.trim();
if (text) rawtags.push(text);
}
}
});
}
for (var i = 0; i < rawtags.length; i++) { for (var i = 0; i < rawtags.length; i++) {
var found = false; var found = false;
for (var j = 0; j < tags.length; j++) { for (var j = 0; j < tags.length; j++) {
if (tags[j] == rawtags[i].innerHTML) { if (tags[j] == rawtags[i]) {
found = true; found = true;
break; break;
} }
} }
if (!found) if (!found)
tags.push(rawtags[i].innerHTML); tags.push(rawtags[i]);
} }
//console.debug(tags); //console.debug(tags);
var id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1]; var id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1];

18
public/views/ga.ejs Normal file
View file

@ -0,0 +1,18 @@
<% if(typeof GA !== 'undefined' && GA) { %>
<script>
(function (i, s, o, g, r, a, m) {
i['GoogleAnalyticsObject'] = r;
i[r] = i[r] || function () {
(i[r].q = i[r].q || []).push(arguments)
}, i[r].l = 1 * new Date();
a = s.createElement(o),
m = s.getElementsByTagName(o)[0];
a.async = 1;
a.src = g;
m.parentNode.insertBefore(a, m)
})(window, document, 'script', '//www.google-analytics.com/analytics.js', 'ga');
ga('create', '<%- GA %>', 'auto');
ga('send', 'pageview');
</script>
<% } %>

View file

@ -11,6 +11,9 @@
<% if(typeof robots !== 'undefined' && robots) { %> <% if(typeof robots !== 'undefined' && robots) { %>
<meta name="robots" content="<%- robots %>"> <meta name="robots" content="<%- robots %>">
<% } %> <% } %>
<% if(typeof description !== 'undefined' && description) { %>
<meta name="description" content="<%- description %>">
<% } %>
<title><%- title %></title> <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">
@ -117,4 +120,6 @@
<script src="<%- url %>/js/common.js" defer></script> <script src="<%- url %>/js/common.js" defer></script>
<script src="<%- url %>/js/extra.js" defer></script> <script src="<%- url %>/js/extra.js" defer></script>
<script src="<%- url %>/js/render.js" defer></script> <script src="<%- url %>/js/render.js" defer></script>
<script src="<%- url %>/js/pretty.js" defer></script> <script src="<%- url %>/js/pretty.js" defer></script>
<%- include ga %>

View file

@ -5,6 +5,12 @@
<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">
<% if(typeof robots !== 'undefined' && robots) { %>
<meta name="robots" content="<%- robots %>">
<% } %>
<% if(typeof description !== 'undefined' && description) { %>
<meta name="description" content="<%- description %>">
<% } %>
<title><%- title %></title> <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">
@ -83,3 +89,5 @@
</script> </script>
</body> </body>
</html> </html>
<%- include ga %>