From 299bcf9742e9addec18dc211ff07d943f92de498 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jakub=20Sko=C5=99epa?= <jakub@skorepa.info> Date: Thu, 29 Dec 2016 01:48:22 +0100 Subject: [PATCH] Working gitInfo and tag pages TODO: - redirects - theme/static - theme/style.css - better looking dates - static files - tag page ordering --- .jshintrc | 34 ---- gitInfo.js | 203 ++++++++++++++++++++ index.js | 82 +++++++- package.json | 2 + src/theme/templates/article.html.nunj | 3 +- src/theme/templates/partials/base.html.nunj | 2 +- src/theme/templates/partials/list.html.nunj | 12 +- 7 files changed, 287 insertions(+), 51 deletions(-) delete mode 100644 .jshintrc create mode 100644 gitInfo.js diff --git a/.jshintrc b/.jshintrc deleted file mode 100644 index cb1d535..0000000 --- a/.jshintrc +++ /dev/null @@ -1,34 +0,0 @@ -{ - // JSHint Default Configuration File (as on JSHint website) - // See http://jshint.com/docs/ for more details - - "maxerr" : 50, // {int} Maximum error before stopping - - // Enforcing - "camelcase" : true, // true: Identifiers must be in camelCase - "curly" : false, // true: Require {} for every new block or scope - "nonew" : true, // true: Prohibit use of constructors for side-effects (without assignment) - "quotmark" : "single", // Quotation mark consistency - "strict" : false, // true: Requires all functions run in ES5 Strict Mode - - "maxparams" : 5, // {int} Max number of formal params allowed per function - "maxdepth" : 5, // {int} Max depth of nested blocks (within functions) - "maxstatements" : 40, // {int} Max number statements per function - "maxcomplexity" : 20, // {int} Max cyclomatic complexity per function - "maxlen" : 120, // {int} Max number of characters per line - - // Relaxing - "asi" : true, // true: Tolerate Automatic Semicolon Insertion (no semicolons) - "esversion" : 6, // {int} Specify the ECMAScript version to which the code must adhere. - "funcscope" : true, // true: Tolerate defining variables inside control statements - - // Environments - "browser" : false, // Web Browser (window, document, etc) - "devel" : true, // Development/debugging (alert, confirm, etc) - "jquery" : false, // jQuery - "mocha" : true, // Mocha - "node" : true, // Node.js - - // Custom Globals - "globals" : {} // additional predefined global variables -} diff --git a/gitInfo.js b/gitInfo.js new file mode 100644 index 0000000..f9af9d6 --- /dev/null +++ b/gitInfo.js @@ -0,0 +1,203 @@ +'use strict'; +/* + * Add date: {creation, modification, modified} and author: {name, email} + * to metadata if not already present + */ + +var nodegit = require('nodegit'); +var path = require('path'); +var moment = require('moment'); + +// Returns promise for patch of single commit +var gitShow = function(repo, commitData) { + var commit, tree, parentTree; + return nodegit.Revparse.single(repo, commitData.commit) + .then((obj) => { + return nodegit.Commit.lookup(repo, obj.id()); + }) + .then((_commit) => { + commit = _commit; + if(commit.parentcount() < 1) return null; + return commit.getParents(1); + }) + .then((parents) => { + if(parents === null) return null; + return parents[0].getTree(); + }) + .then((_parentTree) => { + parentTree = _parentTree; + return commit.getTree(); + }) + .then((_tree) => { + tree = _tree; + return nodegit.Diff.treeToTree(repo, parentTree, tree, null); + }) + .then((diff) => { + return diff.patches(); + }) + .then((patches) => { + return [ + patches, + commitData + ]; + }); +} + +var gitlog = function(repo) { + var map = function(repo){ + return Promise.resolve() + .then(function(){ + return function(commit){ + var author = commit.author(); + var sha = commit.sha(); + + return { + 'commit': sha, + 'author': { + 'name': author.name(), + 'email': author.email() + }, + 'date': commit.date(), + 'message': commit.message() + }; + }; + }); + }; + + + return repo.getHeadCommit() + .then(function(commit) { + return new Promise(function(resolve, reject){ + commit.history() + .on('end', resolve) + .on('error', reject) + .start(); + }) + .then(function(commits){ + return map(repo) + .then(function(log){ + return commits.map(log); + }); + }); + }); +}; + +var getFilesHistory = function(repo, startCommitMessage) { + if(!Array.isArray(startCommitMessage) && startCommitMessage !== undefined) { + startCommitMessage = [startCommitMessage]; + } + return Promise.resolve() + .then(function() { + return gitlog(repo) + }) + .then(function(log){ + var promises = []; + for(var i = 0; i < log.length; i++) { + promises.push(gitShow(repo, log[i])); + } + return Promise.all(promises); + }) + .then(function(diffs) { + var filehistory = {}; + diffs.forEach(function(data) { + var commit = data[1]; + var diff = data[0]; + + diff.forEach(function(entry) { + var p = path.normalize(entry.newFile().path()); + if(filehistory[p]===undefined) { + filehistory[p] = []; + } + filehistory[p].push(commit); + }); + }); + return filehistory; + }) + .then(function(log) { + var sorter = function (a, b) { + var rhs = a.date; + var lhs = b.date; + return lhs > rhs ? 1 : lhs < rhs ? -1 : 0; + }; + for(var file in log) { + log[file].sort(sorter); + } + return log; + }) + .then(function(log) { + if(startCommitMessage === undefined) return log; + function mergeCommit(commits) { + var r; + commits.reverse(); + commits.forEach(function(commit) { + if(r === undefined) + startCommitMessage.forEach(function(matcher) { + if(commit.message.indexOf(matcher) > -1) r = commit; + }) + }) + commits.reverse(); + return r; + } + for(var file in log) { + log[file].mergeCommit = mergeCommit(log[file]); + } + return log; + }); +} + +const gitInfo = function(files, metalsmith, cfg) { + let pathToRepo = path.join(metalsmith._directory, metalsmith._source) + return new Promise(function(resolve,reject) { + return nodegit.Repository.open(pathToRepo) + .catch(function() { + resolve() + }) + .then(function(repo) { + return getFilesHistory(repo, cfg.mergeMessage); + }) + .then((filesHistory) => { + Object.keys(files).forEach(function(file) { + var commits = filesHistory[file]; + var article = files[file]; + if(commits !== undefined) { + commits.sort(function(a,b){ + return b.date - a.date; + }); + var newestCommit = commits[0]; + var oldestCommit = commits[commits.length-1]; + var mergeCommit = commits.mergeCommit; + + if(article.metadata.date === undefined) + article.metadata.date = {}; + + if(article.metadata.date.creation === undefined) { + if(mergeCommit === undefined) { + console.log('[Warning] Article '+ file +' does not have merge commit.'); + article.metadata.date.creation = moment(); + } else + article.metadata.date.creation = mergeCommit.date; + } + + article.metadata.date.modification = newestCommit.date; + + if(article.metadata.author === undefined) + article.metadata.author = {} + if(article.metadata.author.name === undefined) + article.metadata.author.name = oldestCommit.author.name + if(article.metadata.author.email === undefined) + article.metadata.author.email = oldestCommit.author.email + + article.commits = commits; + } else { + console.log('[Warning] Article '+ file +' is not in git repository.'); + } + }); + }) + .then(resolve) + .catch(reject); + }) +} + +module.exports = (cfg) => (files, metalsmith, done) => gitInfo(files,metalsmith,cfg) +.then(() => done()) +.catch(e => done(e)) \ No newline at end of file diff --git a/index.js b/index.js index b4c0543..0a18a37 100644 --- a/index.js +++ b/index.js @@ -6,6 +6,9 @@ var permalinks = require('metalsmith-permalinks'); var ignore = require('metalsmith-ignore'); var frontmatter = require('metalsmith-matters'); var debug = require('metalsmith-debug'); +var tags = require('metalsmith-tags'); +var gitInfo = require('./gitInfo'); +var dateFilter = require('nunjucks-date-filter'); var cons = require('consolidate'); @@ -16,7 +19,32 @@ var util = require('util') cons.requires.nunjucks.addFilter('inspect', function (obj) { return util.inspect(obj); }); -cons.requires.nunjucks.addFilter('date', require('nunjucks-date-filter')); +cons.requires.nunjucks.addFilter('date', dateFilter); +cons.requires.nunjucks.addFilter('date', d => d) +cons.requires.nunjucks.addFilter('paginationList', function(page, count) { + var curPage = page; + var pages = []; + pages.push(curPage); + for(var i = 0; i < count; i++) { + curPage = curPage.metadata.prevpage; + if(curPage === undefined) break; + pages.unshift(curPage); + } + + curPage = page; + for(let i = 0; i < count; i++) { + curPage = curPage.metadata.nextpage; + if(curPage === undefined) break; + pages.push(curPage); + } + return pages; +}); +cons.requires.nunjucks.addFilter('relURL', function(filename, dir){ + if(filename.substr(0,1) == '/' || filename.match('://')) return filename; + if(dir.substr(0,1) !== '/') dir = '/' + dir; + if(filename.match(/\/$/)) return dir+filename; + return dir+'/'+filename; +}) Metalsmith(__dirname) @@ -37,20 +65,58 @@ Metalsmith(__dirname) namespace: 'metadata', delims: ['```', '```'], })) - .use(function(files,metalsmith, done){ - setImmediate(done) - Object.keys(files).forEach(function(file) { - //files[file].contents = files[file].contents.toString() - }) - }) + .use(gitInfo({ + mergeMessage: [ + "See merge request ", + "(Autoexportováno z Joomly)", + "(Autoexportován z Joomly)", + "[publish]" + ] + })) .use(function(files, metalsmith, done) { setImmediate(done); - //console.log(files) + //console.log({files, metalsmith}) }) .use(collections({ // group all blog posts by internally articles: 'articles/*.md',// adding key 'collections':'posts' })) // use `collections.posts` in layouts .use(markdown()) // transpile all md into html + .use(function(files,metalsmith, done){ + setImmediate(done) + Object.keys(files).forEach(function(file) { + files[file].content = files[file].contents.toString() + }) + }) + .use((files,b,done) => { + Object.keys(files).forEach((file) => { + var metadata = files[file].metadata + if(!Array.isArray(tags)) { + metadata.tags = [] + } + files[file].tags = metadata.tags + if(!metadata.notarticle) { + metadata.tags.push("ÄŒlánek") + } + }) + done() + }) + .use(tags({ + handle: 'tags', + path: 'tag/:tag/index.html', + pathPage: 'tag/:tag/:num/index.html', + perPage: 6, + layout: 'tag.html.nunj', + sortBy: 'date', + reverse: true, + })) + .use((files,b,done) => { + Object.keys(files).forEach((file) => { + if(!/^articles/.exec(file)) return + if(!/\.html$/.exec(file)) return + console.log({[file]: files[file] }) + }) + done() + }) .use(permalinks({ // change URLs to permalink URLs relative: false // put css only in /css })) diff --git a/package.json b/package.json index 93d9cc8..536be70 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,8 @@ "metalsmith-markdown": "^0.2.1", "metalsmith-matters": "^1.2.0", "metalsmith-permalinks": "^0.5.0", + "metalsmith-tags": "^2.0.0", + "nodegit": "^0.16.0", "nunjucks": "^3.0.0", "nunjucks-date-filter": "^0.1.1", "toml": "^2.3.0" diff --git a/src/theme/templates/article.html.nunj b/src/theme/templates/article.html.nunj index 5b0b60b..72ca531 100644 --- a/src/theme/templates/article.html.nunj +++ b/src/theme/templates/article.html.nunj @@ -31,12 +31,11 @@ {% block bodyattr %} class="type-article"{% endblock %} {% block content %} - {{ metadata | inspect }} <div class="section container" id="content"> <div class="metadata"> <h2 id="title">{{ metadata.title }}</h2> {% if not metadata.nodate %} - <a class="author" style="color: #a2a2a2; display: block" class="black-text" target="_blank" href="https://git.ok1kvk.cz/ok1kvk.cz/content/tree/master/articles/{{file | replace("clanek/","")}}.md"> + <a class="author" style="color: #a2a2a2; display: block" class="black-text" target="_blank" href="https://git.ok1kvk.cz/ok1kvk.cz/content/tree/master/{{path}}.md"> {% if metadata.date %} <span style="color: #626262">{{ metadata.date.creation | date("D. M. YYYY") }}</span> napsal diff --git a/src/theme/templates/partials/base.html.nunj b/src/theme/templates/partials/base.html.nunj index a771530..08561e0 100644 --- a/src/theme/templates/partials/base.html.nunj +++ b/src/theme/templates/partials/base.html.nunj @@ -48,7 +48,7 @@ {{ svgs.menu("#fff",false) }} </a> <div style="z-index: 2" class="brand-logo"> - <a id="logo-container" href="{{ config.baseurl }}/tag/clanek/1/" > + <a id="logo-container" href="{{ config.baseurl }}/tag/clanek/" > <img class="hide-on-small-and-up" src="{{ config.baseurl }}/theme/icon56.png"> <img class="hide-on-med-and-up hide-on-ultra-small" src="{{ config.baseurl }}/theme/logo56.png"> <img class="hide-on-small-only" src="{{ config.baseurl }}/theme/logo64.png"> diff --git a/src/theme/templates/partials/list.html.nunj b/src/theme/templates/partials/list.html.nunj index c0ba051..de1c998 100644 --- a/src/theme/templates/partials/list.html.nunj +++ b/src/theme/templates/partials/list.html.nunj @@ -2,17 +2,17 @@ <div id=content class=container> <div class="section"> <div class="row"> - {% for page in content %} + {% for page in pagination.files %} <div class="col s12 m6 l4"><!-- s12 m6 l4--> <div class="block article" style=""> - <a href="{{ config.baseurl }}/{{ page.file }}"> + <a href="{{ config.baseurl }}/{{ page.path }}"> <div style="height:120px; - {% if page.metadata.image %}background-image: url('{{ config.baseurl }}{{ page.metadata.image | relURL(page.file) }}'){% endif %} + {% if page.metadata.image %}background-image: url('{{ config.baseurl }}{{ page.metadata.image | relURL(page.path) }}'){% endif %} " class="leadimage light-blue darken-2"></div> </a> <div class="head-title"> - <a href="{{ config.baseurl }}/{{ page.file }}"><h5 class="center">{{ page.metadata.title }}</h5></a> + <a href="{{ config.baseurl }}/{{ page.path }}"><h5 class="center">{{ page.metadata.title }}</h5></a> </div> <div style="padding: 0px 10px;" class="head-perex"> {% if page.metadata.perex %} @@ -30,7 +30,7 @@ <div class="clear"></div> <div style="padding: 10px;" class="head-info"> <span style="color: #a2a2a2; text-align: left;display: inline-block;width: 75%;">{{ page.metadata.date.creation | date("D. M. YYYY") }} napsal {{ page.metadata.author.name }}</span><!-- - --><a style="text-align: right;display: inline-block;width: 25%;" href="{{ config.baseurl }}/{{ page.file }}">ÄŒÃst dál</a> + --><a style="text-align: right;display: inline-block;width: 25%;" href="{{ config.baseurl }}/{{ page.path }}">ÄŒÃst dál</a> </div> </div> @@ -38,7 +38,7 @@ {% endfor %} </div> - {% include "partials/paginator.html.nunj" %} + {# {% include "partials/paginator.html.nunj" %} #} </div> <div class="hide-on-small-only"> -- GitLab