diff --git a/scripts/build-frontend.js b/scripts/build-frontend.js
index 08361b50..7ddf9543 100644
--- a/scripts/build-frontend.js
+++ b/scripts/build-frontend.js
@@ -4,6 +4,11 @@ const fs = require('fs');
const glob = require('glob');
const path = require('path');
const util = require('util');
+const execSync = require('child_process').execSync;
+
+function getVersion() {
+ return execSync('git describe --always --dirty --long --tags').toString();
+}
function getConfig() {
const ini = require('ini');
@@ -34,6 +39,10 @@ function getConfig() {
delete config.database;
config.service.userRanks = config.service.userRanks.split(/,\s*/);
config.service.tagCategories = config.service.tagCategories.split(/,\s*/);
+ config.meta = {
+ version: getVersion(),
+ buildDate: new Date().toUTCString(),
+ };
return config;
}
diff --git a/static/html/home.tpl b/static/html/home.tpl
index 3ac7cdf2..164abc28 100644
--- a/static/html/home.tpl
+++ b/static/html/home.tpl
@@ -1,4 +1,5 @@
{{name}}
+
diff --git a/static/js/util.js b/static/js/util.js
new file mode 100644
index 00000000..0933410c
--- /dev/null
+++ b/static/js/util.js
@@ -0,0 +1,44 @@
+'use strict';
+
+function formatRelativeTime(timeString) {
+ if (!timeString) {
+ return 'never';
+ }
+
+ const then = Date.parse(timeString);
+ const now = Date.now();
+ const difference = Math.abs(now - then) / 1000.0;
+ const future = now < then;
+
+ const descriptions = [
+ [60, 'a few seconds', null],
+ [60*2, 'a minute', null],
+ [60*60, '% minutes', 60],
+ [60*60*2, 'an hour', null],
+ [60*60*24, '% hours', 60*60],
+ [60*60*24*2, 'a day', null],
+ [60*60*24*30.42, '% days', 60*60*24],
+ [60*60*24*30.42*2, 'a month', null],
+ [60*60*24*30.42*12, '% months', 60*60*24*30.42],
+ [60*60*24*30.42*12*2, 'a year', null],
+ [8640000000000000/*max*/, '% years', 60*60*24*30.42*12],
+ ];
+
+ let text = null;
+ for (let kv of descriptions) {
+ const multiplier = kv[0];
+ const template = kv[1];
+ const divider = kv[2];
+ if (difference < multiplier) {
+ text = template.replace(/%/, Math.round(difference / divider));
+ break;
+ }
+ }
+
+ if (text === 'a day') {
+ return future ? 'tomorrow' : 'yesterday';
+ }
+ return future ? 'in ' + text : text + ' ago';
+}
+
+module.exports = {formatRelativeTime: formatRelativeTime};
diff --git a/static/js/views/home_view.js b/static/js/views/home_view.js
index 0847f731..4beca603 100644
--- a/static/js/views/home_view.js
+++ b/static/js/views/home_view.js
@@ -1,5 +1,6 @@
'use strict';
+const util = require('../util.js');
const config = require('../config.js');
const BaseView = require('./base_view.js');
@@ -11,7 +12,9 @@ class HomeView extends BaseView {
render(section) {
this.showView(this.template({
- 'name': config.basic.name,
+ name: config.basic.name,
+ version: config.meta.version,
+ buildDate: util.formatRelativeTime(config.meta.buildDate),
}));
}
}