aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorrr-2016-08-17 13:01:17 +0200
committerrr-2016-08-20 13:01:17 +0200
commit9014baab92007fcdd624be9e81e45ba92dce97ae (patch)
treec6b1881c5662dde840f6e3a2695c8c610c8900e2
parent80af79779d01fe949af84fdb43f6b82085cd4a9c (diff)
client/snapshots: add snapshots browser
-rw-r--r--client/css/snapshots.styl44
-rw-r--r--client/html/home_featured_post.tpl2
-rw-r--r--client/html/home_footer.tpl4
-rw-r--r--client/html/snapshots_page.tpl31
-rw-r--r--client/js/controllers/history_controller.js16
-rw-r--r--client/js/controllers/home_controller.js1
-rw-r--r--client/js/controllers/snapshots_controller.js41
-rw-r--r--client/js/main.js2
-rw-r--r--client/js/models/snapshot.js40
-rw-r--r--client/js/models/snapshot_list.js25
-rw-r--r--client/js/util/views.js28
-rw-r--r--client/js/views/snapshots_page_view.js131
12 files changed, 338 insertions, 27 deletions
diff --git a/client/css/snapshots.styl b/client/css/snapshots.styl
new file mode 100644
index 0000000..dc94bd0
--- /dev/null
+++ b/client/css/snapshots.styl
@@ -0,0 +1,44 @@
+$snapshot-created-background-color = #E0F5E0
+$snapshot-modified-background-color = #E0F5FF
+$snapshot-deleted-background-color = #FDE5E5
+$snapshot-merged-background-color = #FEC
+
+.snapshot-list
+ text-align: left
+
+ ul
+ margin: 0 auto
+ width: 100%
+ max-width: 35em
+ list-style-type: none
+
+ li
+ .time
+ float: right
+
+ div
+ padding: 0.1em 0.5em
+ .thumbnail
+ margin-left: 0
+ &:empty
+ padding: 0
+
+ div.operation-created
+ background: $snapshot-created-background-color
+ &+.details
+ background: lighten($snapshot-created-background-color, 50%)
+ div.operation-modified
+ background: $snapshot-modified-background-color
+ &+.details
+ background: lighten($snapshot-modified-background-color, 50%)
+ div.operation-deleted
+ background: $snapshot-deleted-background-color
+ &+.details
+ background: lighten($snapshot-deleted-background-color, 50%)
+ div.operation-merged
+ background: $snapshot-merged-background-color
+ &+.details
+ background: lighten($snapshot-merged-background-color, 50%)
+
+ div.details
+ margin-bottom: 2em
diff --git a/client/html/home_featured_post.tpl b/client/html/home_featured_post.tpl
index 90851d7..33b9c3c 100644
--- a/client/html/home_featured_post.tpl
+++ b/client/html/home_featured_post.tpl
@@ -1,7 +1,7 @@
<div class='post-container'></div>
<% if (ctx.featuredPost) { %>
<aside>
- Featured post: <%= ctx.makePostLink(ctx.featuredPost.id) %>,
+ Featured post: <%= ctx.makePostLink(ctx.featuredPost.id, true) %>,
posted
<%= ctx.makeRelativeTime(ctx.featuredPost.creationTime) %>
by
diff --git a/client/html/home_footer.tpl b/client/html/home_footer.tpl
index 47d25e8..80baccb 100644
--- a/client/html/home_footer.tpl
+++ b/client/html/home_footer.tpl
@@ -4,3 +4,7 @@
<span class=sep>&bull;</span>
Build <a class='version' href='https://github.com/rr-/szurubooru/commits/master'><%- ctx.version %></a>
from <%= ctx.makeRelativeTime(ctx.buildDate) %>
+<% if (ctx.canListSnapshots) { %>
+ <span class=sep>&bull;</span>
+ <a href='/history'>History</a>
+<% } %>
diff --git a/client/html/snapshots_page.tpl b/client/html/snapshots_page.tpl
new file mode 100644
index 0000000..c84e19f
--- /dev/null
+++ b/client/html/snapshots_page.tpl
@@ -0,0 +1,31 @@
+<div class='snapshot-list'>
+ <% if (ctx.results.length) { %>
+ <ul>
+ <% for (let item of ctx.results) { %>
+ <li>
+ <div class='header operation-<%= item.operation %>'>
+ <span class='time'>
+ <%= ctx.makeRelativeTime(item.time) %>
+ </span>
+
+ <%= ctx.makeUserLink(item.user) %>
+
+ <%= item.operation %>
+
+ <%= ctx.makeResourceLink(item.type, item.id) %>
+ </div>
+
+ <div class='details'><!--
+ --><% if (item.operation === 'created') { %><!--
+ --><%= ctx.makeItemCreation(item.type, item.data) %><!--
+ --><% } else if (item.operation === 'modified') { %><!--
+ --><%= ctx.makeItemModification(item.type, item.data) %><!--
+ --><% } else if (item.operation === 'merged') { %><!--
+ -->Merged to <%= ctx.makeResourceLink(item.data[0], item.data[1]) %><!--
+ --><% } %><!--
+ --></div>
+ </li>
+ <% } %>
+ </ul>
+ <% } %>
+</div>
diff --git a/client/js/controllers/history_controller.js b/client/js/controllers/history_controller.js
deleted file mode 100644
index 05c7c91..0000000
--- a/client/js/controllers/history_controller.js
+++ /dev/null
@@ -1,16 +0,0 @@
-'use strict';
-
-const topNavigation = require('../models/top_navigation.js');
-
-class HistoryController {
- constructor() {
- topNavigation.activate('');
- topNavigation.setTitle('History');
- }
-}
-
-module.exports = router => {
- router.enter('/history', (ctx, next) => {
- ctx.controller = new HistoryController();
- });
-};
diff --git a/client/js/controllers/home_controller.js b/client/js/controllers/home_controller.js
index 4def183..c8f81c3 100644
--- a/client/js/controllers/home_controller.js
+++ b/client/js/controllers/home_controller.js
@@ -15,6 +15,7 @@ class HomeController {
name: config.name,
version: config.meta.version,
buildDate: config.meta.buildDate,
+ canListSnapshots: api.hasPrivilege('snapshots:list'),
canListPosts: api.hasPrivilege('posts:list'),
});
diff --git a/client/js/controllers/snapshots_controller.js b/client/js/controllers/snapshots_controller.js
new file mode 100644
index 0000000..e1f67bd
--- /dev/null
+++ b/client/js/controllers/snapshots_controller.js
@@ -0,0 +1,41 @@
+'use strict';
+
+const api = require('../api.js');
+const misc = require('../util/misc.js');
+const SnapshotList = require('../models/snapshot_list.js');
+const PageController = require('../controllers/page_controller.js');
+const topNavigation = require('../models/top_navigation.js');
+const SnapshotsPageView = require('../views/snapshots_page_view.js');
+
+class SnapshotsController {
+ constructor(ctx) {
+ topNavigation.activate('');
+ topNavigation.setTitle('History');
+
+ this._pageController = new PageController({
+ parameters: ctx.parameters,
+ getClientUrlForPage: page => {
+ const parameters = Object.assign(
+ {}, ctx.parameters, {page: page});
+ return '/history/' + misc.formatUrlParameters(parameters);
+ },
+ requestPage: page => {
+ return SnapshotList.search('', page, 25);
+ },
+ pageRenderer: pageCtx => {
+ Object.assign(pageCtx, {
+ canViewPosts: api.hasPrivilege('posts:view'),
+ canViewUsers: api.hasPrivilege('users:view'),
+ canViewTags: api.hasPrivilege('tags:view'),
+ });
+ return new SnapshotsPageView(pageCtx);
+ },
+ });
+ }
+}
+
+module.exports = router => {
+ router.enter('/history/:parameters?',
+ (ctx, next) => { misc.parseUrlParametersRoute(ctx, next); },
+ (ctx, next) => { ctx.controller = new SnapshotsController(ctx); });
+};
diff --git a/client/js/main.js b/client/js/main.js
index 158e787..25b6469 100644
--- a/client/js/main.js
+++ b/client/js/main.js
@@ -33,7 +33,7 @@ controllers.push(require('./controllers/help_controller.js'));
controllers.push(require('./controllers/auth_controller.js'));
controllers.push(require('./controllers/password_reset_controller.js'));
controllers.push(require('./controllers/comments_controller.js'));
-controllers.push(require('./controllers/history_controller.js'));
+controllers.push(require('./controllers/snapshots_controller.js'));
controllers.push(require('./controllers/post_controller.js'));
controllers.push(require('./controllers/post_list_controller.js'));
controllers.push(require('./controllers/post_upload_controller.js'));
diff --git a/client/js/models/snapshot.js b/client/js/models/snapshot.js
new file mode 100644
index 0000000..78b367c
--- /dev/null
+++ b/client/js/models/snapshot.js
@@ -0,0 +1,40 @@
+'use strict';
+
+const api = require('../api.js');
+const events = require('../events.js');
+
+class Snapshot extends events.EventTarget {
+ constructor() {
+ super();
+ this._orig = {};
+ this._updateFromResponse({});
+ }
+
+ get operation() { return this._operation; }
+ get type() { return this._type; }
+ get id() { return this._id; }
+ get user() { return this._user; }
+ get data() { return this._data; }
+ get time() { return this._time; }
+
+ static fromResponse(response) {
+ const ret = new Snapshot();
+ ret._updateFromResponse(response);
+ return ret;
+ }
+
+ _updateFromResponse(response) {
+ const map = {
+ _operation: response.operation,
+ _type: response.type,
+ _id: response.id,
+ _user: response.user,
+ _data: response.data,
+ _time: response.time,
+ };
+
+ Object.assign(this, map);
+ }
+}
+
+module.exports = Snapshot;
diff --git a/client/js/models/snapshot_list.js b/client/js/models/snapshot_list.js
new file mode 100644
index 0000000..628ee29
--- /dev/null
+++ b/client/js/models/snapshot_list.js
@@ -0,0 +1,25 @@
+'use strict';
+
+const api = require('../api.js');
+const AbstractList = require('./abstract_list.js');
+const Snapshot = require('./snapshot.js');
+
+class SnapshotList extends AbstractList {
+ static search(text, page, pageSize) {
+ const url =
+ `/snapshots/?query=${text}` +
+ `&page=${page}` +
+ `&pageSize=${pageSize}`;
+ return api.get(url).then(response => {
+ return Promise.resolve(Object.assign(
+ {},
+ response,
+ {results: SnapshotList.fromResponse(response.results)}));
+ });
+ }
+}
+
+SnapshotList._itemClass = Snapshot;
+SnapshotList._itemName = 'snapshot';
+
+module.exports = SnapshotList;
diff --git a/client/js/util/views.js b/client/js/util/views.js
index a98e4c0..2bd3531 100644
--- a/client/js/util/views.js
+++ b/client/js/util/views.js
@@ -169,28 +169,36 @@ function getPostEditUrl(id, parameters) {
return url;
}
-function makePostLink(id) {
- const text = '@' + id;
+function makePostLink(id, includeHash) {
+ let text = id;
+ if (includeHash) {
+ text = '@' + id;
+ }
return api.hasPrivilege('posts:view') ?
makeNonVoidElement(
'a', {'href': '/post/' + encodeURIComponent(id)}, text) :
text;
}
-function makeTagLink(name) {
+function makeTagLink(name, includeHash) {
const tag = tags.getTagByName(name);
const category = tag ? tag.category : 'unknown';
+ let text = name;
+ if (includeHash === true) {
+ text = '#' + text;
+ }
return api.hasPrivilege('tags:view') ?
makeNonVoidElement(
- 'a', {
+ 'a',
+ {
'href': '/tag/' + encodeURIComponent(name),
'class': misc.makeCssName(category, 'tag'),
- }, name) :
- makeNonVoidElement(
- 'span', {
- 'class': misc.makeCssName(category, 'tag'),
},
- name);
+ text) :
+ makeNonVoidElement(
+ 'span',
+ {'class': misc.makeCssName(category, 'tag')},
+ text);
}
function makeUserLink(user) {
@@ -477,6 +485,8 @@ module.exports = misc.arrayToObject([
decorateValidator,
makeVoidElement,
makeNonVoidElement,
+ makeTagLink,
+ makePostLink,
syncScrollPosition,
slideDown,
slideUp,
diff --git a/client/js/views/snapshots_page_view.js b/client/js/views/snapshots_page_view.js
new file mode 100644
index 0000000..77fbe13
--- /dev/null
+++ b/client/js/views/snapshots_page_view.js
@@ -0,0 +1,131 @@
+'use strict';
+
+const views = require('../util/views.js');
+
+const template = views.getTemplate('snapshots-page');
+
+function _extend(target, source) {
+ target.push.apply(target, source);
+}
+
+function _formatBasicChange(diff, text) {
+ const lines = [];
+ if (diff.type === 'list change') {
+ const addedItems = diff.added;
+ const removedItems = diff.removed;
+ if (addedItems && addedItems.length) {
+ lines.push(`Added ${text} (${addedItems.join(', ')})`);
+ }
+ if (removedItems && removedItems.length) {
+ lines.push(`Removed ${text} (${removedItems.join(', ')})`);
+ }
+ } else if (diff.type === 'primitive change') {
+ const oldValue = diff['old-value'];
+ const newValue = diff['new-value'];
+ lines.push(`Changed ${text} (${oldValue} &rarr; ${newValue})`);
+ } else {
+ lines.push(`Changed ${text}`);
+ }
+ return lines;
+}
+
+function _makeResourceLink(type, id) {
+ if (type === 'post') {
+ return views.makePostLink(id, true);
+ } else if (type === 'tag') {
+ return views.makeTagLink(id, true);
+ } else if (type === 'tag_category') {
+ return 'category "' + id + '"';
+ }
+}
+
+function _makeItemCreation(type, data) {
+ const lines = [];
+ for (let key of Object.keys(data)) {
+ if (!data[key]) {
+ continue;
+ }
+ let text = key[0].toUpperCase() + key.substr(1).toLowerCase();
+ if (Array.isArray(data[key])) {
+ if (data[key].length) {
+ lines.push(`${text}: ${data[key].join(', ')}`);
+ }
+ } else {
+ lines.push(`${text}: ${data[key]}`);
+ }
+ }
+ return lines.join('<br/>');
+}
+
+function _makeItemModification(type, data) {
+ const lines = [];
+ const diff = data.value;
+ if (type === 'tag_category') {
+ if (diff.name) {
+ _extend(lines, _formatBasicChange(diff.name, 'name'));
+ }
+ if (diff.color) {
+ _extend(lines, _formatBasicChange(diff.color, 'color'));
+ }
+ if (diff.default) {
+ _extend(lines, ['Made into default category']);
+ }
+
+ } else if (type === 'tag') {
+ if (diff.names) {
+ _extend(lines, _formatBasicChange(diff.names, 'names'));
+ }
+ if (diff.category) {
+ _extend(
+ lines, _formatBasicChange(diff.category, 'category'));
+ }
+ if (diff.suggestions) {
+ _extend(
+ lines, _formatBasicChange(diff.suggestions, 'suggestions'));
+ }
+ if (diff.implications) {
+ _extend(
+ lines, _formatBasicChange(diff.implications, 'implications'));
+ }
+
+ } else if (type === 'post') {
+ if (diff.checksum) {
+ _extend(lines, ['Changed content']);
+ }
+ if (diff.featured) {
+ _extend(lines, ['Featured on front page']);
+ }
+ if (diff.source) {
+ _extend(lines, _formatBasicChange(diff.source, 'source'));
+ }
+ if (diff.safety) {
+ _extend(lines, _formatBasicChange(diff.safety, 'safety'));
+ }
+ if (diff.tags) {
+ _extend(lines, _formatBasicChange(diff.tags, 'tags'));
+ }
+ if (diff.relations) {
+ _extend(lines, _formatBasicChange(diff.relations, 'relations'));
+ }
+ if (diff.notes) {
+ _extend(lines, ['Changed notes']);
+ }
+ if (diff.flags) {
+ _extend(lines, ['Changed flags']);
+ }
+ }
+
+ return lines.join('<br/>');
+}
+
+class SnapshotsPageView {
+ constructor(ctx) {
+ views.replaceContent(ctx.hostNode, template(Object.assign({
+ makeResourceLink: _makeResourceLink,
+ makeItemCreation: _makeItemCreation,
+ makeItemModification: _makeItemModification,
+ }, ctx)));
+ }
+}
+
+module.exports = SnapshotsPageView;

© 2015 - 2026 Jakob L. Kreuze