diff options
| author | Ruin0x11 <ipickering2@gmail.com> | 2020-05-04 00:09:33 -0700 |
|---|---|---|
| committer | Ruin0x11 <ipickering2@gmail.com> | 2020-05-04 00:09:33 -0700 |
| commit | e6bf102bc03504d9abc584f15da70a5fed7fa950 (patch) | |
| tree | 346cbeceeb03185b0fee1c9d96a9e7697726575f /client | |
| parent | d59ecb8e230a55e6875076824723580bd3f4419b (diff) | |
Add list of posts to pools
Diffstat (limited to 'client')
| -rw-r--r-- | client/css/pool-list-view.styl | 4 | ||||
| -rw-r--r-- | client/html/help_search_posts.tpl | 6 | ||||
| -rw-r--r-- | client/html/pool_create.tpl | 6 | ||||
| -rw-r--r-- | client/html/pool_edit.tpl | 8 | ||||
| -rw-r--r-- | client/html/pool_merge.tpl | 2 | ||||
| -rw-r--r-- | client/html/pool_summary.tpl | 4 | ||||
| -rw-r--r-- | client/html/pools_page.tpl | 4 | ||||
| -rw-r--r-- | client/js/controllers/pool_controller.js | 8 | ||||
| -rw-r--r-- | client/js/controllers/pool_create_controller.js | 6 | ||||
| -rw-r--r-- | client/js/controllers/pool_list_controller.js | 10 | ||||
| -rw-r--r-- | client/js/controllers/post_list_controller.js | 7 | ||||
| -rw-r--r-- | client/js/controllers/tag_list_controller.js | 7 | ||||
| -rw-r--r-- | client/js/controllers/user_list_controller.js | 3 | ||||
| -rw-r--r-- | client/js/controls/pool_auto_complete_control.js | 8 | ||||
| -rw-r--r-- | client/js/models/pool.js | 27 | ||||
| -rw-r--r-- | client/js/util/misc.js | 5 | ||||
| -rw-r--r-- | client/js/util/views.js | 8 | ||||
| -rw-r--r-- | client/js/views/pool_create_view.js | 36 | ||||
| -rw-r--r-- | client/js/views/pool_edit_view.js | 31 | ||||
| -rw-r--r-- | client/js/views/snapshots_page_view.js | 15 |
20 files changed, 137 insertions, 68 deletions
diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index 2333a37..2daf694 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -14,7 +14,7 @@ white-space: nowrap background: $top-navigation-color .names - width: 28% + width: 84% .usages text-align: center width: 8% @@ -33,7 +33,7 @@ &:not(:last-child):after content: ', ' @media (max-width: 800px) - .implications, .suggestions + .posts display: none .pool-list-header diff --git a/client/html/help_search_posts.tpl b/client/html/help_search_posts.tpl index d1097b7..30a986f 100644 --- a/client/html/help_search_posts.tpl +++ b/client/html/help_search_posts.tpl @@ -20,7 +20,7 @@ </tr> <tr> <td><code>uploader</code></td> - <td>uploaded by given use (accepts wildcards)r</td> + <td>uploaded by given user (accepts wildcards)</td> </tr> <tr> <td><code>upload</code></td> @@ -43,6 +43,10 @@ <td>having given source URL (accepts wildcards)</td> </tr> <tr> + <td><code>pool</code></td> + <td>belonging to given pool ID</td> + </tr> + <tr> <td><code>tag-count</code></td> <td>having given number of tags</td> </tr> diff --git a/client/html/pool_create.tpl b/client/html/pool_create.tpl index fec95ef..bf84dc9 100644 --- a/client/html/pool_create.tpl +++ b/client/html/pool_create.tpl @@ -22,6 +22,12 @@ value: '', }) %> </li> + <li class='posts'> + <%= ctx.makeTextInput({ + text: 'Posts', + value: '', + }) %> + </li> </ul> <% if (ctx.canCreate) { %> diff --git a/client/html/pool_edit.tpl b/client/html/pool_edit.tpl index 0a0e1b0..1cbb62e 100644 --- a/client/html/pool_edit.tpl +++ b/client/html/pool_edit.tpl @@ -28,6 +28,14 @@ }) %> <% } %> </li> + <li class='posts'> + <% if (ctx.canEditPosts) { %> + <%= ctx.makeTextInput({ + text: 'Posts', + value: ctx.pool.posts.map(post => post.id).join(' ') + }) %> + <% } %> + </li> </ul> <% if (ctx.canEditAnything) { %> diff --git a/client/html/pool_merge.tpl b/client/html/pool_merge.tpl index ce0bf92..ea5776a 100644 --- a/client/html/pool_merge.tpl +++ b/client/html/pool_merge.tpl @@ -6,7 +6,7 @@ </li> <li> - <p>Posts between the two pools will be combined. + <p>Posts in the two pools will be combined. Category needs to be handled manually.</p> <%= ctx.makeCheckbox({required: true, text: 'I confirm that I want to merge this pool.'}) %> diff --git a/client/html/pool_summary.tpl b/client/html/pool_summary.tpl index 7b7c763..8f4e27d 100644 --- a/client/html/pool_summary.tpl +++ b/client/html/pool_summary.tpl @@ -9,7 +9,7 @@ Aliases:<br/> <ul><!-- --><% for (let name of ctx.pool.names.slice(1)) { %><!-- - --><li><%= ctx.makePoolLink(ctx.pool, false, false, name) %></li><!-- + --><li><%= ctx.makePoolLink(ctx.pool.id, false, false, ctx.pool, name) %></li><!-- --><% } %><!-- --></ul> </section> @@ -18,6 +18,6 @@ <section class='description'> <hr/> <%= ctx.makeMarkdown(ctx.pool.description || 'This pool has no description yet.') %> - <p>This pool has <a href='<%- ctx.formatClientLink('posts', {query: ctx.escapeColons(ctx.pool.names[0])}) %>'><%- ctx.pool.postCount %> post(s)</a>.</p> + <p>This pool has <a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + ctx.pool.id}) %>'><%- ctx.pool.postCount %> post(s)</a>.</p> </section> </div> diff --git a/client/html/pools_page.tpl b/client/html/pools_page.tpl index 19f3f2a..6b39483 100644 --- a/client/html/pools_page.tpl +++ b/client/html/pools_page.tpl @@ -30,12 +30,12 @@ <td class='names'> <ul> <% for (let name of pool.names) { %> - <li><%= ctx.makePoolLink(pool, false, false, name) %></li> + <li><%= ctx.makePoolLink(pool.id, false, false, pool, name) %></li> <% } %> </ul> </td> <td class='post-count'> - <a href='<%- ctx.formatClientLink('pools', {query: 'pool:' + pool.id}) %>'><%- pool.postCount %></a> + <a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + pool.id}) %>'><%- pool.postCount %></a> </td> <td class='creation-time'> <%= ctx.makeRelativeTime(pool.creationTime) %> diff --git a/client/js/controllers/pool_controller.js b/client/js/controllers/pool_controller.js index 8623cf8..99e2e9b 100644 --- a/client/js/controllers/pool_controller.js +++ b/client/js/controllers/pool_controller.js @@ -5,6 +5,7 @@ const api = require('../api.js'); const misc = require('../util/misc.js'); const uri = require('../util/uri.js'); const Pool = require('../models/pool.js'); +const Post = require('../models/post.js'); const PoolCategoryList = require('../models/pool_category_list.js'); const topNavigation = require('../models/top_navigation.js'); const PoolView = require('../views/pool_view.js'); @@ -42,6 +43,7 @@ class PoolController { canEditNames: api.hasPrivilege('pools:edit:names'), canEditCategory: api.hasPrivilege('pools:edit:category'), canEditDescription: api.hasPrivilege('pools:edit:description'), + canEditPosts: api.hasPrivilege('pools:edit:posts'), canMerge: api.hasPrivilege('pools:merge'), canDelete: api.hasPrivilege('pools:delete'), categories: categories, @@ -84,6 +86,12 @@ class PoolController { if (e.detail.description !== undefined) { e.detail.pool.description = e.detail.description; } + if (e.detail.posts !== undefined) { + e.detail.pool.posts.clear() + for (let post_id of e.detail.posts) { + e.detail.pool.posts.add(Post.fromResponse({ id: parseInt(post_id) })) + } + } e.detail.pool.save().then(() => { this._view.showSuccess('Pool saved.'); this._view.enableForm(); diff --git a/client/js/controllers/pool_create_controller.js b/client/js/controllers/pool_create_controller.js index 0aa3c76..ef6dded 100644 --- a/client/js/controllers/pool_create_controller.js +++ b/client/js/controllers/pool_create_controller.js @@ -39,7 +39,7 @@ class PoolCreateController { _evtCreate(e) { this._view.clearMessages(); this._view.disableForm(); - e.detail.pool.save() + api.post(uri.formatApiLink('pool'), e.detail) .then(() => { this._view.clearMessages(); misc.disableExitConfirmation(); @@ -50,10 +50,6 @@ class PoolCreateController { this._view.enableForm(); }); } - - _evtChange(e) { - misc.enableExitConfirmation(); - } } module.exports = router => { diff --git a/client/js/controllers/pool_list_controller.js b/client/js/controllers/pool_list_controller.js index 626dc1e..1de3926 100644 --- a/client/js/controllers/pool_list_controller.js +++ b/client/js/controllers/pool_list_controller.js @@ -13,26 +13,26 @@ const EmptyView = require('../views/empty_view.js'); const fields = [ 'id', 'names', - /* 'suggestions', - * 'implications', */ + 'posts', 'creationTime', 'postCount', 'category']; class PoolListController { constructor(ctx) { + this._pageController = new PageController(); + if (!api.hasPrivilege('pools:list')) { this._view = new EmptyView(); this._view.showError('You don\'t have privileges to view pools.'); return; } + this._ctx = ctx; + topNavigation.activate('pools'); topNavigation.setTitle('Listing pools'); - this._ctx = ctx; - this._pageController = new PageController(); - this._headerView = new PoolsHeaderView({ hostNode: this._pageController.view.pageHeaderHolderNode, parameters: ctx.parameters, diff --git a/client/js/controllers/post_list_controller.js b/client/js/controllers/post_list_controller.js index fd1adfe..c79824a 100644 --- a/client/js/controllers/post_list_controller.js +++ b/client/js/controllers/post_list_controller.js @@ -17,18 +17,19 @@ const fields = [ class PostListController { constructor(ctx) { + this._pageController = new PageController(); + if (!api.hasPrivilege('posts:list')) { this._view = new EmptyView(); this._view.showError('You don\'t have privileges to view posts.'); return; } + this._ctx = ctx; + topNavigation.activate('posts'); topNavigation.setTitle('Listing posts'); - this._ctx = ctx; - this._pageController = new PageController(); - this._headerView = new PostsHeaderView({ hostNode: this._pageController.view.pageHeaderHolderNode, parameters: ctx.parameters, diff --git a/client/js/controllers/tag_list_controller.js b/client/js/controllers/tag_list_controller.js index 8bc7dbb..e82461d 100644 --- a/client/js/controllers/tag_list_controller.js +++ b/client/js/controllers/tag_list_controller.js @@ -20,18 +20,19 @@ const fields = [ class TagListController { constructor(ctx) { + this._pageController = new PageController(); + if (!api.hasPrivilege('tags:list')) { this._view = new EmptyView(); this._view.showError('You don\'t have privileges to view tags.'); return; } + this._ctx = ctx; + topNavigation.activate('tags'); topNavigation.setTitle('Listing tags'); - this._ctx = ctx; - this._pageController = new PageController(); - this._headerView = new TagsHeaderView({ hostNode: this._pageController.view.pageHeaderHolderNode, parameters: ctx.parameters, diff --git a/client/js/controllers/user_list_controller.js b/client/js/controllers/user_list_controller.js index fa878d8..5d666f8 100644 --- a/client/js/controllers/user_list_controller.js +++ b/client/js/controllers/user_list_controller.js @@ -12,6 +12,8 @@ const EmptyView = require('../views/empty_view.js'); class UserListController { constructor(ctx) { + this._pageController = new PageController(); + if (!api.hasPrivilege('users:list')) { this._view = new EmptyView(); this._view.showError('You don\'t have privileges to view users.'); @@ -22,7 +24,6 @@ class UserListController { topNavigation.setTitle('Listing users'); this._ctx = ctx; - this._pageController = new PageController(); this._headerView = new UsersHeaderView({ hostNode: this._pageController.view.pageHeaderHolderNode, diff --git a/client/js/controls/pool_auto_complete_control.js b/client/js/controls/pool_auto_complete_control.js index 3c4bff7..0230c41 100644 --- a/client/js/controls/pool_auto_complete_control.js +++ b/client/js/controls/pool_auto_complete_control.js @@ -9,10 +9,6 @@ function _poolListToMatches(pools, options) { return pool2.postCount - pool1.postCount; }).map(pool => { let cssName = misc.makeCssName(pool.category, 'pool'); - // TODO - if (options.isPooledWith(pool.id)) { - cssName += ' disabled'; - } const caption = ( '<span class="' + cssName + '">' + misc.escapeHtml(pool.names[0] + ' (' + pool.postCount + ')') @@ -28,10 +24,6 @@ class PoolAutoCompleteControl extends AutoCompleteControl { constructor(input, options) { const minLengthForPartialSearch = 3; - options = Object.assign({ - isPooledWith: poolId => false, - }, options); - options.getMatches = text => { const term = misc.escapeSearchTerm(text); const query = ( diff --git a/client/js/models/pool.js b/client/js/models/pool.js index e51832b..8099d2b 100644 --- a/client/js/models/pool.js +++ b/client/js/models/pool.js @@ -7,15 +7,13 @@ const misc = require('../util/misc.js'); class Pool extends events.EventTarget { constructor() { - // const PoolList = require('./pool_list.js'); + const PostList = require('./post_list.js'); super(); this._orig = {}; for (let obj of [this, this._orig]) { - // TODO - // obj._suggestions = new PoolList(); - // obj._implications = new PoolList(); + obj._posts = new PostList(); } this._updateFromResponse({}); @@ -25,8 +23,7 @@ class Pool extends events.EventTarget { get names() { return this._names; } get category() { return this._category; } get description() { return this._description; } - /* get suggestions() { return this._suggestions; } - * get implications() { return this._implications; } */ + get posts() { return this._posts; } get postCount() { return this._postCount; } get creationTime() { return this._creationTime; } get lastEditTime() { return this._lastEditTime; } @@ -61,15 +58,9 @@ class Pool extends events.EventTarget { if (this._description !== this._orig._description) { detail.description = this._description; } - // TODO - // if (misc.arraysDiffer(this._implications, this._orig._implications)) { - // detail.implications = this._implications.map( - // relation => relation.names[0]); - // } - // if (misc.arraysDiffer(this._suggestions, this._orig._suggestions)) { - // detail.suggestions = this._suggestions.map( - // relation => relation.names[0]); - // } + if (misc.arraysDiffer(this._posts, this._orig._posts)) { + detail.posts = this._posts.map(post => post.id); + } let promise = this._id ? api.put(uri.formatApiLink('pool', this._id), detail) : @@ -138,13 +129,11 @@ class Pool extends events.EventTarget { _description: response.description, _creationTime: response.creationTime, _lastEditTime: response.lastEditTime, - _postCount: response.usages || 0, + _postCount: response.postCount || 0, }; for (let obj of [this, this._orig]) { - // TODO - // obj._suggestions.sync(response.suggestions); - // obj._implications.sync(response.implications); + obj._posts.sync(response.posts); } Object.assign(this, map); diff --git a/client/js/util/misc.js b/client/js/util/misc.js index 5911a36..aa62097 100644 --- a/client/js/util/misc.js +++ b/client/js/util/misc.js @@ -163,11 +163,6 @@ function escapeHtml(unsafe) { } function arraysDiffer(source1, source2, orderImportant) { - if ((source1 instanceof Array && source2 === undefined) - || (source1 === undefined && source2 instanceof Array)) { - return true - } - source1 = [...source1]; source2 = [...source2]; if (orderImportant === true) { diff --git a/client/js/util/views.js b/client/js/util/views.js index 38020d8..1a4d03d 100644 --- a/client/js/util/views.js +++ b/client/js/util/views.js @@ -221,20 +221,20 @@ function makeTagLink(name, includeHash, includeCount, tag) { misc.escapeHtml(text)); } -function makePoolLink(pool, includeHash, includeCount, name) { - const category = pool.category; +function makePoolLink(id, includeHash, includeCount, pool, name) { + const category = pool ? pool.category : 'unknown'; let text = name ? name : pool.names[0]; if (includeHash === true) { text = '#' + text; } if (includeCount === true) { - text += ' (' + pool.postCount + ')'; + text += ' (' + (pool ? pool.postCount : 0) + ')'; } return api.hasPrivilege('pools:view') ? makeElement( 'a', { - href: uri.formatClientLink('pool', pool.id), + href: uri.formatClientLink('pool', id), class: misc.makeCssName(category, 'pool'), }, misc.escapeHtml(text)) : diff --git a/client/js/views/pool_create_view.js b/client/js/views/pool_create_view.js index 20218e5..26fca59 100644 --- a/client/js/views/pool_create_view.js +++ b/client/js/views/pool_create_view.js @@ -22,8 +22,13 @@ class PoolCreateView extends events.EventTarget { 'input', e => this._evtNameInput(e)); } + if (this._postsFieldNode) { + this._postsFieldNode.addEventListener( + 'input', e => this._evtPostsInput(e)); + } + for (let node of this._formNode.querySelectorAll( - 'input, select, textarea')) { + 'input, select, textarea, posts')) { node.addEventListener( 'change', e => { this.dispatchEvent(new CustomEvent('change')); @@ -74,16 +79,31 @@ class PoolCreateView extends events.EventTarget { this._namesFieldNode.setCustomValidity(''); } + _evtPostsInput(e) { + const regex = /^\d+$/; + const list = misc.splitByWhitespace(this._postsFieldNode.value); + + for (let item of list) { + if (!regex.test(item)) { + this._postsFieldNode.setCustomValidity( + `Pool ID "${item}" is not an integer.`); + return; + } + } + + this._postsFieldNode.setCustomValidity(''); + } + _evtSubmit(e) { e.preventDefault(); - let pool = new Pool() - pool.names = misc.splitByWhitespace(this._namesFieldNode.value); - pool.category = this._categoryFieldNode.value; - pool.description = this._descriptionFieldNode.value; this.dispatchEvent(new CustomEvent('submit', { detail: { - pool: pool, + names: misc.splitByWhitespace(this._namesFieldNode.value), + category: this._categoryFieldNode.value, + description: this._descriptionFieldNode.value, + posts: misc.splitByWhitespace(this._postsFieldNode.value) + .map(i => parseInt(i, 10)) }, })); } @@ -103,6 +123,10 @@ class PoolCreateView extends events.EventTarget { get _descriptionFieldNode() { return this._formNode.querySelector('.description textarea'); } + + get _postsFieldNode() { + return this._formNode.querySelector('.posts input'); + } } module.exports = PoolCreateView; diff --git a/client/js/views/pool_edit_view.js b/client/js/views/pool_edit_view.js index fac8430..7cd8bc9 100644 --- a/client/js/views/pool_edit_view.js +++ b/client/js/views/pool_edit_view.js @@ -4,6 +4,7 @@ const events = require('../events.js'); const api = require('../api.js'); const misc = require('../util/misc.js'); const views = require('../util/views.js'); +const Post = require('../models/post.js'); const template = views.getTemplate('pool-edit'); @@ -22,8 +23,13 @@ class PoolEditView extends events.EventTarget { 'input', e => this._evtNameInput(e)); } + if (this._postsFieldNode) { + this._postsFieldNode.addEventListener( + 'input', e => this._evtPostsInput(e)); + } + for (let node of this._formNode.querySelectorAll( - 'input, select, textarea')) { + 'input, select, textarea, posts')) { node.addEventListener( 'change', e => { this.dispatchEvent(new CustomEvent('change')); @@ -74,6 +80,21 @@ class PoolEditView extends events.EventTarget { this._namesFieldNode.setCustomValidity(''); } + _evtPostsInput(e) { + const regex = /^\d+$/; + const list = misc.splitByWhitespace(this._postsFieldNode.value); + + for (let item of list) { + if (!regex.test(item)) { + this._postsFieldNode.setCustomValidity( + `Pool ID "${item}" is not an integer.`); + return; + } + } + + this._postsFieldNode.setCustomValidity(''); + } + _evtSubmit(e) { e.preventDefault(); this.dispatchEvent(new CustomEvent('submit', { @@ -91,6 +112,10 @@ class PoolEditView extends events.EventTarget { description: this._descriptionFieldNode ? this._descriptionFieldNode.value : undefined, + + posts: this._postsFieldNode ? + misc.splitByWhitespace(this._postsFieldNode.value) : + undefined, }, })); } @@ -110,6 +135,10 @@ class PoolEditView extends events.EventTarget { get _descriptionFieldNode() { return this._formNode.querySelector('.description textarea'); } + + get _postsFieldNode() { + return this._formNode.querySelector('.posts input'); + } } module.exports = PoolEditView; diff --git a/client/js/views/snapshots_page_view.js b/client/js/views/snapshots_page_view.js index 77fbe13..e7ea264 100644 --- a/client/js/views/snapshots_page_view.js +++ b/client/js/views/snapshots_page_view.js @@ -36,6 +36,8 @@ function _makeResourceLink(type, id) { return views.makeTagLink(id, true); } else if (type === 'tag_category') { return 'category "' + id + '"'; + } else if (type === 'pool') { + return views.makePoolLink(id, true); } } @@ -113,6 +115,19 @@ function _makeItemModification(type, data) { if (diff.flags) { _extend(lines, ['Changed flags']); } + + } else if (type === 'pool') { + if (diff.names) { + _extend(lines, _formatBasicChange(diff.names, 'names')); + } + if (diff.category) { + _extend( + lines, _formatBasicChange(diff.category, 'category')); + } + if (diff.posts) { + _extend( + lines, _formatBasicChange(diff.posts, 'posts')); + } } return lines.join('<br/>'); |