summaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
authorRuin0x11 <ipickering2@gmail.com>2020-05-04 00:09:33 -0700
committerRuin0x11 <ipickering2@gmail.com>2020-05-04 00:09:33 -0700
commite6bf102bc03504d9abc584f15da70a5fed7fa950 (patch)
tree346cbeceeb03185b0fee1c9d96a9e7697726575f /client
parentd59ecb8e230a55e6875076824723580bd3f4419b (diff)
Add list of posts to pools
Diffstat (limited to 'client')
-rw-r--r--client/css/pool-list-view.styl4
-rw-r--r--client/html/help_search_posts.tpl6
-rw-r--r--client/html/pool_create.tpl6
-rw-r--r--client/html/pool_edit.tpl8
-rw-r--r--client/html/pool_merge.tpl2
-rw-r--r--client/html/pool_summary.tpl4
-rw-r--r--client/html/pools_page.tpl4
-rw-r--r--client/js/controllers/pool_controller.js8
-rw-r--r--client/js/controllers/pool_create_controller.js6
-rw-r--r--client/js/controllers/pool_list_controller.js10
-rw-r--r--client/js/controllers/post_list_controller.js7
-rw-r--r--client/js/controllers/tag_list_controller.js7
-rw-r--r--client/js/controllers/user_list_controller.js3
-rw-r--r--client/js/controls/pool_auto_complete_control.js8
-rw-r--r--client/js/models/pool.js27
-rw-r--r--client/js/util/misc.js5
-rw-r--r--client/js/util/views.js8
-rw-r--r--client/js/views/pool_create_view.js36
-rw-r--r--client/js/views/pool_edit_view.js31
-rw-r--r--client/js/views/snapshots_page_view.js15
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/>');