diff options
| -rw-r--r-- | client/css/pool-list-view.styl | 2 | ||||
| -rw-r--r-- | client/html/pool.tpl | 2 | ||||
| -rw-r--r-- | client/html/pools_page.tpl | 4 | ||||
| -rw-r--r-- | client/js/controllers/pool_controller.js | 4 | ||||
| -rw-r--r-- | client/js/controls/pool_input_control.js | 25 | ||||
| -rw-r--r-- | client/js/models/post.js | 199 | ||||
| -rw-r--r-- | client/js/views/pool_merge_view.js | 9 | ||||
| -rw-r--r-- | server/szurubooru/func/pools.py | 47 |
8 files changed, 130 insertions, 162 deletions
diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index 2daf694..0f57649 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -15,7 +15,7 @@ background: $top-navigation-color .names width: 84% - .usages + .post-count text-align: center width: 8% .creation-time diff --git a/client/html/pool.tpl b/client/html/pool.tpl index 5af10c9..e1e5311 100644 --- a/client/html/pool.tpl +++ b/client/html/pool.tpl @@ -1,5 +1,5 @@ <div class='content-wrapper' id='pool'> - <h1><%- ctx.pool.first_name %></h1> + <h1><%- ctx.pool.names[0] %></h1> <nav class='buttons'><!-- --><ul><!-- --><li data-name='summary'><a href='<%- ctx.formatClientLink('pool', ctx.pool.id) %>'>Summary</a></li><!-- diff --git a/client/html/pools_page.tpl b/client/html/pools_page.tpl index 6b39483..0d81180 100644 --- a/client/html/pools_page.tpl +++ b/client/html/pools_page.tpl @@ -11,9 +11,9 @@ </th> <th class='post-count'> <% if (ctx.parameters.query == 'sort:post-count') { %> - <a href='<%- ctx.formatClientLink('pools', {query: '-sort:post-count'}) %>'>Post Count</a> + <a href='<%- ctx.formatClientLink('pools', {query: '-sort:post-count'}) %>'>Post count</a> <% } else { %> - <a href='<%- ctx.formatClientLink('pools', {query: 'sort:post-count'}) %>'>Post Count</a> + <a href='<%- ctx.formatClientLink('pools', {query: 'sort:post-count'}) %>'>Post count</a> <% } %> </th> <th class='creation-time'> diff --git a/client/js/controllers/pool_controller.js b/client/js/controllers/pool_controller.js index 99e2e9b..e243cd8 100644 --- a/client/js/controllers/pool_controller.js +++ b/client/js/controllers/pool_controller.js @@ -105,13 +105,13 @@ class PoolController { this._view.clearMessages(); this._view.disableForm(); e.detail.pool - .merge(e.detail.targetPoolName, e.detail.addAlias) + .merge(e.detail.targetPoolId, e.detail.addAlias) .then(() => { this._view.showSuccess('Pool merged.'); this._view.enableForm(); router.replace( uri.formatClientLink( - 'pool', e.detail.targetPoolName, 'merge'), + 'pool', e.detail.targetPoolId, 'merge'), null, false); }, error => { this._view.showError(error.message); diff --git a/client/js/controls/pool_input_control.js b/client/js/controls/pool_input_control.js index f53c8c9..a827206 100644 --- a/client/js/controls/pool_input_control.js +++ b/client/js/controls/pool_input_control.js @@ -64,10 +64,6 @@ class PoolInputControl extends events.EventTarget { verticalShift: -2 }); - // dom events - this._poolInputNode.addEventListener( - 'keydown', e => this._evtInputKeyDown(e)); - // show this._hostNode.style.display = 'none'; this._hostNode.parentNode.insertBefore( @@ -117,23 +113,6 @@ class PoolInputControl extends events.EventTarget { this.dispatchEvent(new CustomEvent('change')); } - _evtAddPoolButtonClick(e) { - // TODO - // e.preventDefault(); - // this.addPoolByName(this._poolInputNode.value, SOURCE_USER_INPUT); - // this._poolInputNode.value = ''; - } - - _evtInputKeyDown(e) { - // TODO - if (e.which == KEY_RETURN || e.which == KEY_SPACE) { - e.preventDefault(); - // this._hideAutoComplete(); - // this.addPoolByText(this._poolInputNode.value, SOURCE_USER_INPUT); - // this._poolInputNode.value = ''; - } - } - _createListItemNode(pool) { const className = pool.category ? misc.makeCssName(pool.category, 'pool') : @@ -159,10 +138,6 @@ class PoolInputControl extends events.EventTarget { 'href', uri.formatClientLink( 'posts', {query: "pool:" + pool.id})); searchLinkNode.textContent = pool.names[0] + ' '; - searchLinkNode.addEventListener('click', e => { - // TODO? - // e.preventDefault(); - }); const usagesNode = document.createElement('span'); usagesNode.classList.add('pool-usages'); diff --git a/client/js/models/post.js b/client/js/models/post.js index e3388ec..6022ae0 100644 --- a/client/js/models/post.js +++ b/client/js/models/post.js @@ -8,6 +8,7 @@ const TagList = require('./tag_list.js'); const NoteList = require('./note_list.js'); const CommentList = require('./comment_list.js'); const PoolList = require('./pool_list.js'); +const Pool = require('./pool.js'); const misc = require('../util/misc.js'); class Post extends events.EventTarget { @@ -98,22 +99,36 @@ class Post extends events.EventTarget { _savePoolPosts() { const difference = (a, b) => a.filter(post => !b.hasPoolId(post.id)); + + // find the pools where the post was added or removed const added = difference(this.pools, this._orig._pools); const removed = difference(this._orig._pools, this.pools); + let ops = []; + // update each pool's list of posts for (let pool of added) { - if (!pool.posts.hasPostId(this._id)) { - pool.posts.addById(this._id); - ops.push(pool.save()); - } + let op = Pool.get(pool.id).then(response => { + if (!response.posts.hasPostId(this._id)) { + response.posts.addById(this._id); + return response.save(); + } else { + return Promise.resolve(response); + } + }); + ops.push(op); } for (let pool of removed) { - if (pool.posts.hasPostId(this._id)) { - pool.posts.removeById(this._id); - ops.push(pool.save()); - } + let op = Pool.get(pool.id).then(response => { + if (response.posts.hasPostId(this._id)) { + response.posts.removeById(this._id); + return response.save(); + } else { + return Promise.resolve(response); + } + }); + ops.push(op); } return Promise.all(ops); @@ -156,34 +171,34 @@ class Post extends events.EventTarget { } let apiPromise = this._id ? - api.put(uri.formatApiLink('post', this.id), detail, files) : - api.post(uri.formatApiLink('posts'), detail, files); + api.put(uri.formatApiLink('post', this.id), detail, files) : + api.post(uri.formatApiLink('posts'), detail, files); return apiPromise.then(response => { - if (this._pools !== this._orig._pools) { + if (misc.arraysDiffer(this._pools, this._orig._pools)) { return this._savePoolPosts() - .then(() => Promise.resolve(response)); + .then(() => Promise.resolve(response)); } return Promise.resolve(response); }).then(response => { this._updateFromResponse(response); this.dispatchEvent( - new CustomEvent('change', {detail: {post: this}})); + new CustomEvent('change', {detail: {post: this}})); if (this._newContent) { this.dispatchEvent( - new CustomEvent('changeContent', {detail: {post: this}})); + new CustomEvent('changeContent', {detail: {post: this}})); } if (this._newThumbnail) { this.dispatchEvent( - new CustomEvent('changeThumbnail', {detail: {post: this}})); + new CustomEvent('changeThumbnail', {detail: {post: this}})); } return Promise.resolve(); }, error => { if (error.response && - error.response.name === 'PostAlreadyUploadedError') { + error.response.name === 'PostAlreadyUploadedError') { error.message = - `Post already uploaded (@${error.response.otherPostId})`; + `Post already uploaded (@${error.response.otherPostId})`; } return Promise.reject(error); }); @@ -191,118 +206,118 @@ class Post extends events.EventTarget { feature() { return api.post( - uri.formatApiLink('featured-post'), - {id: this._id}) - .then(response => { - return Promise.resolve(); - }); + uri.formatApiLink('featured-post'), + {id: this._id}) + .then(response => { + return Promise.resolve(); + }); } delete() { return api.delete( - uri.formatApiLink('post', this.id), - {version: this._version}) - .then(response => { - this.dispatchEvent(new CustomEvent('delete', { - detail: { - post: this, - }, - })); - return Promise.resolve(); - }); + uri.formatApiLink('post', this.id), + {version: this._version}) + .then(response => { + this.dispatchEvent(new CustomEvent('delete', { + detail: { + post: this, + }, + })); + return Promise.resolve(); + }); } merge(targetId, useOldContent) { return api.get(uri.formatApiLink('post', targetId)) - .then(response => { - return api.post(uri.formatApiLink('post-merge'), { - removeVersion: this._version, - remove: this._id, - mergeToVersion: response.version, - mergeTo: targetId, - replaceContent: useOldContent, + .then(response => { + return api.post(uri.formatApiLink('post-merge'), { + removeVersion: this._version, + remove: this._id, + mergeToVersion: response.version, + mergeTo: targetId, + replaceContent: useOldContent, + }); + }).then(response => { + this._updateFromResponse(response); + this.dispatchEvent(new CustomEvent('change', { + detail: { + post: this, + }, + })); + return Promise.resolve(); }); - }).then(response => { - this._updateFromResponse(response); - this.dispatchEvent(new CustomEvent('change', { - detail: { - post: this, - }, - })); - return Promise.resolve(); - }); } setScore(score) { return api.put( - uri.formatApiLink('post', this.id, 'score'), - {score: score}) - .then(response => { - const prevFavorite = this._ownFavorite; - this._updateFromResponse(response); - if (this._ownFavorite !== prevFavorite) { - this.dispatchEvent(new CustomEvent('changeFavorite', { + uri.formatApiLink('post', this.id, 'score'), + {score: score}) + .then(response => { + const prevFavorite = this._ownFavorite; + this._updateFromResponse(response); + if (this._ownFavorite !== prevFavorite) { + this.dispatchEvent(new CustomEvent('changeFavorite', { + detail: { + post: this, + }, + })); + } + this.dispatchEvent(new CustomEvent('changeScore', { detail: { post: this, }, })); - } - this.dispatchEvent(new CustomEvent('changeScore', { - detail: { - post: this, - }, - })); - return Promise.resolve(); - }); + return Promise.resolve(); + }); } addToFavorites() { return api.post(uri.formatApiLink('post', this.id, 'favorite')) - .then(response => { - const prevScore = this._ownScore; - this._updateFromResponse(response); - if (this._ownScore !== prevScore) { - this.dispatchEvent(new CustomEvent('changeScore', { + .then(response => { + const prevScore = this._ownScore; + this._updateFromResponse(response); + if (this._ownScore !== prevScore) { + this.dispatchEvent(new CustomEvent('changeScore', { + detail: { + post: this, + }, + })); + } + this.dispatchEvent(new CustomEvent('changeFavorite', { detail: { post: this, }, })); - } - this.dispatchEvent(new CustomEvent('changeFavorite', { - detail: { - post: this, - }, - })); - return Promise.resolve(); - }); + return Promise.resolve(); + }); } removeFromFavorites() { return api.delete(uri.formatApiLink('post', this.id, 'favorite')) - .then(response => { - const prevScore = this._ownScore; - this._updateFromResponse(response); - if (this._ownScore !== prevScore) { - this.dispatchEvent(new CustomEvent('changeScore', { + .then(response => { + const prevScore = this._ownScore; + this._updateFromResponse(response); + if (this._ownScore !== prevScore) { + this.dispatchEvent(new CustomEvent('changeScore', { + detail: { + post: this, + }, + })); + } + this.dispatchEvent(new CustomEvent('changeFavorite', { detail: { post: this, }, })); - } - this.dispatchEvent(new CustomEvent('changeFavorite', { - detail: { - post: this, - }, - })); - return Promise.resolve(); - }); + return Promise.resolve(); + }); } mutateContentUrl() { this._contentUrl = - this._orig._contentUrl + - '?bypass-cache=' + - Math.round(Math.random() * 1000); + this._orig._contentUrl + + '?bypass-cache=' + + Math.round(Math.random() * 1000); } _updateFromResponse(response) { diff --git a/client/js/views/pool_merge_view.js b/client/js/views/pool_merge_view.js index ea9fe6f..4bbef7e 100644 --- a/client/js/views/pool_merge_view.js +++ b/client/js/views/pool_merge_view.js @@ -14,6 +14,7 @@ class PoolMergeView extends events.EventTarget { this._pool = ctx.pool; this._hostNode = ctx.hostNode; + this._target_pool_id = null; ctx.poolNamePattern = api.getPoolNameRegex(); views.replaceContent(this._hostNode, template(ctx)); @@ -22,9 +23,11 @@ class PoolMergeView extends events.EventTarget { this._autoCompleteControl = new PoolAutoCompleteControl( this._targetPoolFieldNode, { - confirm: pool => + confirm: pool => { + this._target_pool_id = pool.id; this._autoCompleteControl.replaceSelectedText( - pool.names[0], false), + pool.names[0], false); + } }); } @@ -56,7 +59,7 @@ class PoolMergeView extends events.EventTarget { this.dispatchEvent(new CustomEvent('submit', { detail: { pool: this._pool, - targetPoolName: this._targetPoolFieldNode.value + targetPoolId: this._target_pool_id }, })); } diff --git a/server/szurubooru/func/pools.py b/server/szurubooru/func/pools.py index 4fc4d36..854650a 100644 --- a/server/szurubooru/func/pools.py +++ b/server/szurubooru/func/pools.py @@ -6,7 +6,6 @@ from szurubooru import config, db, model, errors, rest from szurubooru.func import util, pool_categories, serialization, posts - class PoolNotFoundError(errors.NotFoundError): pass @@ -35,6 +34,10 @@ class InvalidPoolDescriptionError(errors.ValidationError): pass +class InvalidPoolRelationError(errors.ValidationError): + pass + + def _verify_name_validity(name: str) -> None: if util.value_exceeds_column_size(name, model.PoolName.name): raise InvalidPoolNameError('Name is too long.') @@ -211,45 +214,18 @@ def merge_pools(source_pool: model.Pool, target_pool: model.Pool) -> None: raise InvalidPoolRelationError('Cannot merge pool with itself.') def merge_posts(source_pool_id: int, target_pool_id: int) -> None: - pass - # alias1 = model.PostPool - # alias2 = sa.orm.util.aliased(model.PostPool) - # update_stmt = ( - # sa.sql.expression.update(alias1) - # .where(alias1.pool_id == source_pool_id)) - # update_stmt = ( - # update_stmt - # .where( - # ~sa.exists() - # .where(alias1.post_id == alias2.post_id) - # .where(alias2.pool_id == target_pool_id))) - # update_stmt = update_stmt.values(pool_id=target_pool_id) - # db.session.execute(update_stmt) - - def merge_relations( - table: model.Base, source_pool_id: int, target_pool_id: int) -> None: - alias1 = table - alias2 = sa.orm.util.aliased(table) + alias1 = model.PoolPost + alias2 = sa.orm.util.aliased(model.PoolPost) update_stmt = ( sa.sql.expression.update(alias1) - .where(alias1.parent_id == source_pool_id) - .where(alias1.child_id != target_pool_id) - .where( - ~sa.exists() - .where(alias2.child_id == alias1.child_id) - .where(alias2.parent_id == target_pool_id)) - .values(parent_id=target_pool_id)) - db.session.execute(update_stmt) - + .where(alias1.pool_id == source_pool_id)) update_stmt = ( - sa.sql.expression.update(alias1) - .where(alias1.child_id == source_pool_id) - .where(alias1.parent_id != target_pool_id) + update_stmt .where( ~sa.exists() - .where(alias2.parent_id == alias1.parent_id) - .where(alias2.child_id == target_pool_id)) - .values(child_id=target_pool_id)) + .where(alias1.post_id == alias2.post_id) + .where(alias2.pool_id == target_pool_id))) + update_stmt = update_stmt.values(pool_id=target_pool_id) db.session.execute(update_stmt) merge_posts(source_pool.pool_id, target_pool.pool_id) @@ -316,7 +292,6 @@ def update_pool_description(pool: model.Pool, description: str) -> None: pool.description = description or None - def update_pool_posts(pool: model.Pool, post_ids: List[int]) -> None: assert pool if _check_post_duplication(post_ids): |