mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 11:16:31 -05:00
Update to support new metadata: title, description, tags and google-analytics (GA) and refactor render publish slide response function
This commit is contained in:
parent
ad6982e77e
commit
558304ff62
8 changed files with 160 additions and 44 deletions
|
@ -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 title = "";
|
||||||
|
var meta = null;
|
||||||
|
try {
|
||||||
|
var obj = metaMarked(body);
|
||||||
|
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 $ = cheerio.load(marked(body));
|
||||||
var h1s = $("h1");
|
var h1s = $("h1");
|
||||||
var title = "";
|
|
||||||
if (h1s.length > 0 && h1s.first().text().split('\n').length == 1)
|
if (h1s.length > 0 && h1s.first().text().split('\n').length == 1)
|
||||||
title = h1s.first().text();
|
title = h1s.first().text();
|
||||||
else
|
}
|
||||||
title = "Untitled";
|
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: {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -43,13 +43,17 @@ function updateLastChangeUser() {
|
||||||
|
|
||||||
//get title
|
//get title
|
||||||
function getTitle(view) {
|
function getTitle(view) {
|
||||||
var h1s = view.find("h1");
|
|
||||||
var title = "";
|
var title = "";
|
||||||
|
if (md && md.meta && md.meta.title && (typeof md.meta.title == "string" || typeof md.meta.title == "number")) {
|
||||||
|
title = md.meta.title;
|
||||||
|
} else {
|
||||||
|
var h1s = view.find("h1");
|
||||||
if (h1s.length > 0) {
|
if (h1s.length > 0) {
|
||||||
title = h1s.first().text();
|
title = h1s.first().text();
|
||||||
} else {
|
} else {
|
||||||
title = null;
|
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);
|
||||||
|
|
|
@ -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 = [];
|
||||||
|
if (md && md.meta && md.meta.tags && (typeof md.meta.tags == "string" || typeof md.meta.tags == "number")) {
|
||||||
|
var metaTags = ('' + md.meta.tags).split(',');
|
||||||
|
for (var i = 0; i < metaTags.length; i++) {
|
||||||
|
var text = metaTags[i].trim();
|
||||||
|
if (text) rawtags.push(text);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
view.find('h6').each(function (key, value) {
|
view.find('h6').each(function (key, value) {
|
||||||
if (/^tags/gmi.test($(value).text())) {
|
if (/^tags/gmi.test($(value).text())) {
|
||||||
var codes = $(value).find("code");
|
var codes = $(value).find("code");
|
||||||
for (var i = 0; i < codes.length; i++)
|
for (var i = 0; i < codes.length; i++) {
|
||||||
rawtags.push(codes[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
18
public/views/ga.ejs
Normal 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>
|
||||||
|
<% } %>
|
|
@ -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">
|
||||||
|
@ -118,3 +121,5 @@
|
||||||
<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 %>
|
|
@ -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 %>
|
Loading…
Reference in a new issue