From 558304ff62a648e604b03afe3372ef9566aea850 Mon Sep 17 00:00:00 2001 From: Cheng-Han, Wu Date: Tue, 21 Jun 2016 21:42:03 +0800 Subject: Update to support new metadata: title, description, tags and google-analytics (GA) and refactor render publish slide response function --- lib/models/note.js | 38 +++++++++++++++++++++++++++------ lib/response.js | 50 +++++++++++++++++++++++++------------------- public/docs/yaml-metadata.md | 35 ++++++++++++++++++++++++++++++- public/js/extra.js | 16 ++++++++------ public/js/history.js | 32 +++++++++++++++++++++------- public/views/ga.ejs | 18 ++++++++++++++++ public/views/pretty.ejs | 7 ++++++- public/views/slide.ejs | 8 +++++++ 8 files changed, 160 insertions(+), 44 deletions(-) create mode 100644 public/views/ga.ejs diff --git a/lib/models/note.js b/lib/models/note.js index a442f889..db0493b2 100644 --- a/lib/models/note.js +++ b/lib/models/note.js @@ -5,6 +5,7 @@ var fs = require('fs'); var path = require('path'); var LZString = require('lz-string'); var marked = require('marked'); +var metaMarked = require('meta-marked'); var cheerio = require('cheerio'); var shortId = require('shortid'); var Sequelize = require("sequelize"); @@ -187,13 +188,24 @@ module.exports = function (sequelize, DataTypes) { }); }, parseNoteTitle: function (body) { - var $ = cheerio.load(marked(body)); - var h1s = $("h1"); var title = ""; - if (h1s.length > 0 && h1s.first().text().split('\n').length == 1) - title = h1s.first().text(); - else - title = "Untitled"; + 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 h1s = $("h1"); + if (h1s.length > 0 && h1s.first().text().split('\n').length == 1) + title = h1s.first().text(); + } + if (!title) title = "Untitled"; return title; }, decodeTitle: function (title) { @@ -205,6 +217,20 @@ module.exports = function (sequelize, DataTypes) { generateWebTitle: function (title) { title = !title || title == "Untitled" ? "HackMD - Collaborative markdown notes" : title + " - HackMD"; 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: { diff --git a/lib/response.js b/lib/response.js index 5d7fc0a1..133d7a37 100644 --- a/lib/response.js +++ b/lib/response.js @@ -21,7 +21,7 @@ var models = require("./models"); var md = require('reveal.js/plugin/markdown/markdown'); //reveal.js -var opts = { +var slideOptions = { template: fs.readFileSync(config.slidepath).toString(), theme: 'css/theme/black.css', highlightTheme: 'zenburn', @@ -107,7 +107,7 @@ function responseHackMD(res, note) { var body = LZString.decompressFromBase64(note.content); var meta = null; try { - meta = metaMarked(body).meta; + meta = models.Note.parseMeta(metaMarked(body).meta); } catch(err) { //na } @@ -121,7 +121,7 @@ function responseHackMD(res, note) { var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options); var html = compiled({ url: config.serverurl, - title: title, + title: meta.title || title, useCDN: config.usecdn, facebook: config.facebook, twitter: config.twitter, @@ -212,7 +212,7 @@ function showPublishNote(req, res, next) { var body = LZString.decompressFromBase64(note.content); var meta = null; try { - meta = metaMarked(body).meta; + meta = models.Note.parseMeta(metaMarked(body).meta); } catch(err) { //na } @@ -223,7 +223,8 @@ function showPublishNote(req, res, next) { title = models.Note.generateWebTitle(title); var origin = config.serverurl; var data = { - title: title, + title: meta.title || title, + description: meta.description, viewcount: note.viewcount, createtime: createtime, updatetime: updatetime, @@ -231,7 +232,8 @@ function showPublishNote(req, res, next) { body: text, useCDN: config.usecdn, 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); }).catch(function (err) { @@ -527,14 +529,28 @@ function showPublishSlide(req, res, next) { } var body = LZString.decompressFromBase64(note.content); try { - body = metaMarked(body).markdown; + var obj = metaMarked(body); + body = obj.markdown; + meta = models.Note.parseMeta(obj.meta); } catch(err) { //na } + var text = S(body).escapeHTML().s; var title = models.Note.decodeTitle(note.title); title = models.Note.generateWebTitle(title); - var text = S(body).escapeHTML().s; - render(res, title, text); + var slides = md.slidify(text, slideOptions); + 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) { logger.error(err); return response.errorInternalError(res); @@ -542,24 +558,14 @@ function showPublishSlide(req, res, next) { }); } -//reveal.js render -var render = function (res, title, markdown) { - var slides = md.slidify(markdown, opts); - +function renderPublishSlide(data, res) { var template = config.slidepath; var options = { cache: !config.debug, filename: template }; var compiled = ejs.compile(fs.readFileSync(template, 'utf8'), options); - var html = compiled({ - url: config.serverurl, - title: title, - theme: opts.theme, - highlightTheme: opts.highlightTheme, - slides: slides, - options: JSON.stringify(opts.revealOptions, null, 2) - }); + var html = compiled(data); var buf = html; res.writeHead(200, { 'Content-Type': 'text/html; charset=UTF-8', @@ -567,6 +573,6 @@ var render = function (res, title, markdown) { 'Content-Length': buf.length }); res.end(buf); -}; +} module.exports = response; diff --git a/public/docs/yaml-metadata.md b/public/docs/yaml-metadata.md index 5fc6e1b8..539410c8 100644 --- a/public/docs/yaml-metadata.md +++ b/public/docs/yaml-metadata.md @@ -18,6 +18,39 @@ YAML metas Replace the "YAML metas" in this section with any YAML options as below. 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 --- 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`. -> default: not +> default: not set **Example** ```xml diff --git a/public/js/extra.js b/public/js/extra.js index 953770be..e67eee53 100644 --- a/public/js/extra.js +++ b/public/js/extra.js @@ -43,12 +43,16 @@ function updateLastChangeUser() { //get title function getTitle(view) { - var h1s = view.find("h1"); var title = ""; - if (h1s.length > 0) { - title = h1s.first().text(); + if (md && md.meta && md.meta.title && (typeof md.meta.title == "string" || typeof md.meta.title == "number")) { + title = md.meta.title; } else { - title = null; + var h1s = view.find("h1"); + if (h1s.length > 0) { + title = h1s.first().text(); + } else { + title = null; + } } return title; } @@ -93,7 +97,7 @@ function parseMeta(md, edit, view, toc, tocAffix) { spellcheck = meta.spellcheck; } //text language - if (lang) { + if (lang && typeof lang == "string") { view.attr('lang', lang); toc.attr('lang', lang); tocAffix.attr('lang', lang); @@ -107,7 +111,7 @@ function parseMeta(md, edit, view, toc, tocAffix) { edit.removeAttr('lang', lang); } //text direction - if (dir) { + if (dir && typeof dir == "string") { view.attr('dir', dir); toc.attr('dir', dir); tocAffix.attr('dir', dir); diff --git a/public/js/history.js b/public/js/history.js index 9bdca709..0840580d 100644 --- a/public/js/history.js +++ b/public/js/history.js @@ -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) { var title = renderFilename(view); var tags = []; var rawtags = []; - 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++) - rawtags.push(codes[i]); + 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) { + 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++) { var found = false; for (var j = 0; j < tags.length; j++) { - if (tags[j] == rawtags[i].innerHTML) { + if (tags[j] == rawtags[i]) { found = true; break; } } if (!found) - tags.push(rawtags[i].innerHTML); + tags.push(rawtags[i]); } //console.debug(tags); var id = urlpath ? location.pathname.slice(urlpath.length + 1, location.pathname.length).split('/')[1] : location.pathname.split('/')[1]; diff --git a/public/views/ga.ejs b/public/views/ga.ejs new file mode 100644 index 00000000..180832d1 --- /dev/null +++ b/public/views/ga.ejs @@ -0,0 +1,18 @@ +<% if(typeof GA !== 'undefined' && GA) { %> + +<% } %> \ No newline at end of file diff --git a/public/views/pretty.ejs b/public/views/pretty.ejs index 0a541107..5ba2e8e1 100644 --- a/public/views/pretty.ejs +++ b/public/views/pretty.ejs @@ -11,6 +11,9 @@ <% if(typeof robots !== 'undefined' && robots) { %> <% } %> + <% if(typeof description !== 'undefined' && description) { %> + + <% } %> <%- title %> @@ -117,4 +120,6 @@ - \ No newline at end of file + + +<%- include ga %> \ No newline at end of file diff --git a/public/views/slide.ejs b/public/views/slide.ejs index 27ccfdf8..3fe30944 100644 --- a/public/views/slide.ejs +++ b/public/views/slide.ejs @@ -5,6 +5,12 @@ + <% if(typeof robots !== 'undefined' && robots) { %> + + <% } %> + <% if(typeof description !== 'undefined' && description) { %> + + <% } %> <%- title %> @@ -83,3 +89,5 @@ + +<%- include ga %> \ No newline at end of file -- cgit v1.2.3