diff options
| author | rr- | 2016-08-17 13:01:17 +0200 |
|---|---|---|
| committer | rr- | 2016-08-20 13:01:17 +0200 |
| commit | 9014baab92007fcdd624be9e81e45ba92dce97ae (patch) | |
| tree | c6b1881c5662dde840f6e3a2695c8c610c8900e2 | |
| parent | 80af79779d01fe949af84fdb43f6b82085cd4a9c (diff) | |
client/snapshots: add snapshots browser
| -rw-r--r-- | client/css/snapshots.styl | 44 | ||||
| -rw-r--r-- | client/html/home_featured_post.tpl | 2 | ||||
| -rw-r--r-- | client/html/home_footer.tpl | 4 | ||||
| -rw-r--r-- | client/html/snapshots_page.tpl | 31 | ||||
| -rw-r--r-- | client/js/controllers/history_controller.js | 16 | ||||
| -rw-r--r-- | client/js/controllers/home_controller.js | 1 | ||||
| -rw-r--r-- | client/js/controllers/snapshots_controller.js | 41 | ||||
| -rw-r--r-- | client/js/main.js | 2 | ||||
| -rw-r--r-- | client/js/models/snapshot.js | 40 | ||||
| -rw-r--r-- | client/js/models/snapshot_list.js | 25 | ||||
| -rw-r--r-- | client/js/util/views.js | 28 | ||||
| -rw-r--r-- | client/js/views/snapshots_page_view.js | 131 |
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>•</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>•</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} → ${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; |