mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Add list of posts to pools
This commit is contained in:
@ -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
|
||||
|
@ -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>
|
||||
@ -42,6 +42,10 @@
|
||||
<td><code>source</code></td>
|
||||
<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>
|
||||
|
@ -22,6 +22,12 @@
|
||||
value: '',
|
||||
}) %>
|
||||
</li>
|
||||
<li class='posts'>
|
||||
<%= ctx.makeTextInput({
|
||||
text: 'Posts',
|
||||
value: '',
|
||||
}) %>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<% if (ctx.canCreate) { %>
|
||||
|
@ -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) { %>
|
||||
|
@ -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.'}) %>
|
||||
|
@ -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>
|
||||
|
@ -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) %>
|
||||
|
@ -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();
|
||||
|
@ -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 => {
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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 = (
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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)) :
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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/>');
|
||||
|
Reference in New Issue
Block a user