From c0504692e1935004cab5e05f2e1180d917a0c9df Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 17:16:40 +0100 Subject: [PATCH 01/45] server: poolpost nearby implementation --- doc/API.md | 37 ++++++++++++++++++++++++ server/szurubooru/api/post_api.py | 15 ++++++++-- server/szurubooru/func/posts.py | 48 ++++++++++++++++++++++++++++++- 3 files changed, 97 insertions(+), 3 deletions(-) diff --git a/doc/API.md b/doc/API.md index 70c495ab..56d15d11 100644 --- a/doc/API.md +++ b/doc/API.md @@ -975,6 +975,43 @@ data. Retrieves information about posts that are before or after an existing post. +## Getting pools around post +- **Request** + + `GET /post//pools-nearby` + +- **Output** + + ```json5 + [ + { + "pool": , + "firstPost": , + "lastPost": , + "nextPost": , + "previousPost": + }, + ... + ] + ``` + +- **Field meaning** + +- ``: The associated [micro pool resource](#micro-pool). +- `firstPost`: A [micro post resource](#micro-post) that displays the first post in the pool. +- `lastPost`: A [micro post resource](#micro-post) that displays the last post in the pool. +- `nextPost`: A [micro post resource](#micro-post) that displays the next post in the pool. +- `prevPost`: A [micro post resource](#micro-post) that displays the previous post in the pool. + +- **Errors** + + - the post does not exist + - privileges are too low + +- **Description** + + Retrieves extra information about any pools that the post is in. + ## Deleting post - **Request** diff --git a/server/szurubooru/api/post_api.py b/server/szurubooru/api/post_api.py index daba7f7e..34a2136c 100644 --- a/server/szurubooru/api/post_api.py +++ b/server/szurubooru/api/post_api.py @@ -5,12 +5,10 @@ from szurubooru import db, errors, model, rest, search from szurubooru.func import ( auth, favorites, - mime, posts, scores, serialization, snapshots, - tags, versions, ) @@ -283,6 +281,19 @@ def get_posts_around( ctx, post_id, lambda post: _serialize_post(ctx, post) ) +@rest.routes.get("/post/(?P[^/]+)/pools-nearby/?") +def get_pools_around( + ctx: rest.Context, params: Dict[str, str] +) -> rest.Response: + auth.verify_privilege(ctx.user, "posts:list") + auth.verify_privilege(ctx.user, "posts:view") + auth.verify_privilege(ctx.user, "pools:list") + _search_executor_config.user = ctx.user + post = _get_post(params) + results = posts.get_pools_nearby(post) + return posts.serialize_pool_posts_nearby(results) + + @rest.routes.post("/posts/reverse-search/?") def get_posts_by_image( diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index be2259cf..eeea4509 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -1,5 +1,6 @@ import hmac import logging +from collections import namedtuple from datetime import datetime from typing import Any, Callable, Dict, List, Optional, Tuple @@ -15,7 +16,6 @@ from szurubooru.func import ( pools, scores, serialization, - snapshots, tags, users, util, @@ -968,3 +968,49 @@ def search_by_image(image_content: bytes) -> List[Tuple[float, model.Post]]: ] else: return [] + +PoolPostsNearby = namedtuple('PoolPostsNearby', 'pool first_post prev_post next_post last_post') +def get_pools_nearby( + post: model.Post +) -> List[PoolPostsNearby]: + response = [] + pools = post.pools + + for pool in pools: + prev_post_id = None + next_post_id = None + break_loop = False + + for pool_post in pools.posts: + next_post_id = pool_post.post_id + + if break_loop == True: + break + + if post.id == pool_post.post_id: + break_loop = True + + prev_post_id = pool_post.post_id + + resp_entry = PoolPostsNearby( + pool=pool, + first_post=pool.posts[0].post_id, + last_post=pool.posts[-1].post_id, + prev_post=next_post_id, + next_post=prev_post_id, + ) + response.append(resp_entry) + return response + +def serialize_pool_posts_nearby( + nearby: List[PoolPostsNearby] +) -> Optional[rest.Response]: + return [ + { + "pool": pools.serialize_pool(entry.pool), + "firstPost": serialize_micro_post(try_get_post_by_id(entry.first_post)), + "lastPost": serialize_micro_post(try_get_post_by_id(entry.last_post)), + "prevPost": serialize_micro_post(try_get_post_by_id(entry.prev_post)), + "nextPost": serialize_micro_post(try_get_post_by_id(entry.first_post)), + } for entry in nearby + ] From b30db8caf821dbf301e86b3f731d9dd10a0d58c9 Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 20:04:37 +0100 Subject: [PATCH 02/45] server: fix small typo --- server/szurubooru/func/posts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index eeea4509..96d2783c 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -981,7 +981,7 @@ def get_pools_nearby( next_post_id = None break_loop = False - for pool_post in pools.posts: + for pool_post in pool.posts: next_post_id = pool_post.post_id if break_loop == True: From 9b2e1c306468e685e75798dbb1f56681174edb60 Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 20:06:31 +0100 Subject: [PATCH 03/45] server: rename incorrect flag --- server/szurubooru/func/posts.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index 96d2783c..daade7c1 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -987,7 +987,7 @@ def get_pools_nearby( if break_loop == True: break - if post.id == pool_post.post_id: + if post.post_id == pool_post.post_id: break_loop = True prev_post_id = pool_post.post_id From 377998fdbc47bc6033e4c42e286f867ffe9298ef Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 20:09:20 +0100 Subject: [PATCH 04/45] server: add missing None argument --- server/szurubooru/func/posts.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index daade7c1..d8f7f012 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -1008,9 +1008,9 @@ def serialize_pool_posts_nearby( return [ { "pool": pools.serialize_pool(entry.pool), - "firstPost": serialize_micro_post(try_get_post_by_id(entry.first_post)), - "lastPost": serialize_micro_post(try_get_post_by_id(entry.last_post)), - "prevPost": serialize_micro_post(try_get_post_by_id(entry.prev_post)), - "nextPost": serialize_micro_post(try_get_post_by_id(entry.first_post)), + "firstPost": serialize_micro_post(try_get_post_by_id(entry.first_post), None), + "lastPost": serialize_micro_post(try_get_post_by_id(entry.last_post), None), + "prevPost": serialize_micro_post(try_get_post_by_id(entry.prev_post), None), + "nextPost": serialize_micro_post(try_get_post_by_id(entry.first_post), None), } for entry in nearby ] From 10be19050d8ef37243d0c78de8c7f5016175dd57 Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 20:45:44 +0100 Subject: [PATCH 05/45] server: fix incorrect values being used --- server/szurubooru/func/posts.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index d8f7f012..a4d4e185 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -996,8 +996,8 @@ def get_pools_nearby( pool=pool, first_post=pool.posts[0].post_id, last_post=pool.posts[-1].post_id, - prev_post=next_post_id, - next_post=prev_post_id, + prev_post=prev_post_id, + next_post=next_post_id, ) response.append(resp_entry) return response @@ -1011,6 +1011,6 @@ def serialize_pool_posts_nearby( "firstPost": serialize_micro_post(try_get_post_by_id(entry.first_post), None), "lastPost": serialize_micro_post(try_get_post_by_id(entry.last_post), None), "prevPost": serialize_micro_post(try_get_post_by_id(entry.prev_post), None), - "nextPost": serialize_micro_post(try_get_post_by_id(entry.first_post), None), + "nextPost": serialize_micro_post(try_get_post_by_id(entry.next_post), None), } for entry in nearby ] From 7586d92db58896d9978ac6199933c80c77cf11b7 Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 20:53:09 +0100 Subject: [PATCH 06/45] server: slightly better way of prev/next --- server/szurubooru/func/posts.py | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index a4d4e185..f502d9a0 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -979,23 +979,21 @@ def get_pools_nearby( for pool in pools: prev_post_id = None next_post_id = None - break_loop = False - - for pool_post in pool.posts: - next_post_id = pool_post.post_id - - if break_loop == True: - break + first_post_id = pool.posts[0].post_id, + last_post_id = pool.posts[-1].post_id, + for idx, pool_post in enumerate(pool.posts): if post.post_id == pool_post.post_id: - break_loop = True - - prev_post_id = pool_post.post_id + if post.post_id != first_post_id: + prev_post_id = pool.posts[idx-1].post_id + if post.post_id != last_post_id: + next_post_id = pool.posts[idx+1].post_id + break resp_entry = PoolPostsNearby( pool=pool, - first_post=pool.posts[0].post_id, - last_post=pool.posts[-1].post_id, + first_post=first_post_id, + last_post=last_post_id, prev_post=prev_post_id, next_post=next_post_id, ) @@ -1007,7 +1005,7 @@ def serialize_pool_posts_nearby( ) -> Optional[rest.Response]: return [ { - "pool": pools.serialize_pool(entry.pool), + "pool": pools.serialize_micro_pool(entry.pool), "firstPost": serialize_micro_post(try_get_post_by_id(entry.first_post), None), "lastPost": serialize_micro_post(try_get_post_by_id(entry.last_post), None), "prevPost": serialize_micro_post(try_get_post_by_id(entry.prev_post), None), From f8dfde9a61c8e14169dbabe100abb11bfec44323 Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 21:04:53 +0100 Subject: [PATCH 07/45] server: better iterable logic --- server/szurubooru/func/posts.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index f502d9a0..eeed83ec 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -2,6 +2,7 @@ import hmac import logging from collections import namedtuple from datetime import datetime +from itertools import tee, chain, islice, izip from typing import Any, Callable, Dict, List, Optional, Tuple import sqlalchemy as sa @@ -96,6 +97,13 @@ FLAG_MAP = { model.Post.FLAG_SOUND: "sound", } +# https://stackoverflow.com/a/1012089 +def _get_nearby_iter(post_list): + previous_item, current_item, next_item = tee(post_list, 3) + previous_item = chain([None], previous_item) + next_item = chain(islice(next_item, 1, None), [None]) + return izip(previous_item, current_item, next_item) + def get_post_security_hash(id: int) -> str: return hmac.new( @@ -982,12 +990,10 @@ def get_pools_nearby( first_post_id = pool.posts[0].post_id, last_post_id = pool.posts[-1].post_id, - for idx, pool_post in enumerate(pool.posts): - if post.post_id == pool_post.post_id: - if post.post_id != first_post_id: - prev_post_id = pool.posts[idx-1].post_id - if post.post_id != last_post_id: - next_post_id = pool.posts[idx+1].post_id + for previous_item, current_item, next_item in _get_nearby_iter(pool.posts): + if post.post_id == current_item.post_id: + prev_post_id = previous_item.post_id + next_post_id = next_item.post_id break resp_entry = PoolPostsNearby( From 75138525e861417b29a5eee5a7dfbfe5c7139a3b Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 21:06:17 +0100 Subject: [PATCH 08/45] server: izip doesnt exist anymore --- server/szurubooru/func/posts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index eeed83ec..8c8067d3 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -2,7 +2,7 @@ import hmac import logging from collections import namedtuple from datetime import datetime -from itertools import tee, chain, islice, izip +from itertools import tee, chain, islice from typing import Any, Callable, Dict, List, Optional, Tuple import sqlalchemy as sa @@ -102,7 +102,7 @@ def _get_nearby_iter(post_list): previous_item, current_item, next_item = tee(post_list, 3) previous_item = chain([None], previous_item) next_item = chain(islice(next_item, 1, None), [None]) - return izip(previous_item, current_item, next_item) + return zip(previous_item, current_item, next_item) def get_post_security_hash(id: int) -> str: From c3a4cb6cd1e6d012fbc20439f7db43a938a80941 Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 21:07:17 +0100 Subject: [PATCH 09/45] server: add none check --- server/szurubooru/func/posts.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index 8c8067d3..f1e21889 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -992,8 +992,10 @@ def get_pools_nearby( for previous_item, current_item, next_item in _get_nearby_iter(pool.posts): if post.post_id == current_item.post_id: - prev_post_id = previous_item.post_id - next_post_id = next_item.post_id + if previous_item != None: + prev_post_id = previous_item.post_id + if next_item != None: + next_post_id = next_item.post_id break resp_entry = PoolPostsNearby( From 7c12abeffac14813627a309b83a8595cfca7c77f Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 22:00:12 +0100 Subject: [PATCH 10/45] client: add pool navigation elements this implementation was *heavily* cherry-picked from PR #403. --- client/css/pool-navigator-control.styl | 34 +++++++++++++ client/css/pool-navigator-list.styl | 9 ++++ client/html/pool_navigator.tpl | 49 ++++++++++++++++++ client/html/pool_navigator_list.tpl | 4 ++ client/html/post_main.tpl | 4 ++ client/js/controllers/post_main_controller.js | 12 ++++- client/js/controls/pool_navigator_control.js | 33 ++++++++++++ .../controls/pool_navigator_list_control.js | 51 +++++++++++++++++++ client/js/models/post_list.js | 9 ++++ client/js/views/post_main_view.js | 15 ++++++ 10 files changed, 219 insertions(+), 1 deletion(-) create mode 100644 client/css/pool-navigator-control.styl create mode 100644 client/css/pool-navigator-list.styl create mode 100644 client/html/pool_navigator.tpl create mode 100644 client/html/pool_navigator_list.tpl create mode 100644 client/js/controls/pool_navigator_control.js create mode 100644 client/js/controls/pool_navigator_list_control.js diff --git a/client/css/pool-navigator-control.styl b/client/css/pool-navigator-control.styl new file mode 100644 index 00000000..56c22d28 --- /dev/null +++ b/client/css/pool-navigator-control.styl @@ -0,0 +1,34 @@ +@import colors + +.pool-navigator-container + padding: 0 + margin: 0 auto + + .pool-info-wrapper + box-sizing: border-box + width: 100% + margin: 0 0 1em 0 + display: flex + padding: 0.5em 1em + border: 1px solid $pool-navigator-border-color + background: $pool-navigator-background-color + + .pool-name + flex: 1 1; + text-align: center; + overflow: hidden; + white-space: nowrap; + -o-text-overflow: ellipsis; + text-overflow: ellipsis; + + .first, .last + flex-basis: 1em; + + .first, .prev, .next, .last + flex: 0 1; + margin: 0 .25em; + white-space: nowrap; + + +.darktheme .pool-navigator-container + background: $pool-navigator-header-background-color-darktheme \ No newline at end of file diff --git a/client/css/pool-navigator-list.styl b/client/css/pool-navigator-list.styl new file mode 100644 index 00000000..080ad01a --- /dev/null +++ b/client/css/pool-navigator-list.styl @@ -0,0 +1,9 @@ +.pool-navigators>ul + list-style-type: none + margin: 0 + padding: 0 + + >li + margin-bottom: 1em + &:last-child + margin-bottom: 0 diff --git a/client/html/pool_navigator.tpl b/client/html/pool_navigator.tpl new file mode 100644 index 00000000..647684d8 --- /dev/null +++ b/client/html/pool_navigator.tpl @@ -0,0 +1,49 @@ +
+
+ + <% if (ctx.canViewPosts && ctx.firstPost) { %> + + <% } %> + « + <% if (ctx.canViewPosts && ctx.firstPost) { %> + + <% } %> + + + <% if (ctx.canViewPosts && ctx.previousPost) { %> + + <% } %> + ‹ prev + <% if (ctx.canViewPosts && ctx.previousPost) { %> + + <% } %> + + + <% if (ctx.canViewPools) { %> + + <% } %> + Pool: <%- ctx.pool.names[0] %> + <% if (ctx.canViewPools) { %> + + <% } %> + + + <% if (ctx.canViewPosts && ctx.nextPost) { %> + + <% } %> + next › + <% if (ctx.canViewPosts && ctx.nextPost) { %> + + <% } %> + + + <% if (ctx.canViewPosts && ctx.lastPost) { %> + + <% } %> + » + <% if (ctx.canViewPosts && ctx.lastPost) { %> + + <% } %> + +
+
diff --git a/client/html/pool_navigator_list.tpl b/client/html/pool_navigator_list.tpl new file mode 100644 index 00000000..6c60e4e2 --- /dev/null +++ b/client/html/pool_navigator_list.tpl @@ -0,0 +1,4 @@ +
+
    +
+
\ No newline at end of file diff --git a/client/html/post_main.tpl b/client/html/post_main.tpl index 84e48b1c..51ef5626 100644 --- a/client/html/post_main.tpl +++ b/client/html/post_main.tpl @@ -52,6 +52,10 @@
+ <% if (ctx.canListPools && ctx.canViewPools) { %> +
+ <% } %> +
<% if (ctx.canCreateComments) { %>

Add comment

diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js index bd338129..9ffd7e50 100644 --- a/client/js/controllers/post_main_controller.js +++ b/client/js/controllers/post_main_controller.js @@ -11,11 +11,17 @@ const PostList = require("../models/post_list.js"); const PostMainView = require("../views/post_main_view.js"); const BasePostController = require("./base_post_controller.js"); const EmptyView = require("../views/empty_view.js"); +const PoolNavigatorListControl = require("../controls/pool_navigator_list_control.js"); class PostMainController extends BasePostController { constructor(ctx, editMode) { super(ctx); + let poolPostsNearby = Promise.resolve({results: []}); + if (api.hasPrivilege("pools:list") && api.hasPrivilege("pools:view")) { + poolPostsNearby = PostList.getNearbyPoolPosts(ctx.parameters.id) + } + let parameters = ctx.parameters; Promise.all([ Post.get(ctx.parameters.id), @@ -23,9 +29,10 @@ class PostMainController extends BasePostController { ctx.parameters.id, parameters ? parameters.query : null ), + poolPostsNearby ]).then( (responses) => { - const [post, aroundResponse] = responses; + const [post, aroundResponse, poolPostsNearby] = responses; // remove junk from query, but save it into history so that it can // be still accessed after history navigation / page refresh @@ -44,6 +51,7 @@ class PostMainController extends BasePostController { this._post = post; this._view = new PostMainView({ post: post, + poolPostsNearby: poolPostsNearby, editMode: editMode, prevPostId: aroundResponse.prev ? aroundResponse.prev.id @@ -56,6 +64,8 @@ class PostMainController extends BasePostController { canFeaturePosts: api.hasPrivilege("posts:feature"), canListComments: api.hasPrivilege("comments:list"), canCreateComments: api.hasPrivilege("comments:create"), + canListPools: api.hasPrivilege("pools:list"), + canViewPools: api.hasPrivilege("pools:view"), parameters: parameters, }); diff --git a/client/js/controls/pool_navigator_control.js b/client/js/controls/pool_navigator_control.js new file mode 100644 index 00000000..fa4394d1 --- /dev/null +++ b/client/js/controls/pool_navigator_control.js @@ -0,0 +1,33 @@ +"use strict"; + +const api = require("../api.js"); +const misc = require("../util/misc.js"); +const events = require("../events.js"); +const views = require("../util/views.js"); + +const template = views.getTemplate("pool-navigator"); + +class PoolNavigatorControl extends events.EventTarget { + constructor(hostNode, poolPostNearby) { + super(); + this._hostNode = hostNode; + this._poolPostNearby = poolPostNearby; + + views.replaceContent( + this._hostNode, + template({ + pool: poolPostNearby.pool, + parameters: { query: `pool:${poolPostNearby.pool.id}` }, + linkClass: misc.makeCssName(poolPostNearby.pool.category, "pool"), + canViewPosts: api.hasPrivilege("posts:view"), + canViewPools: api.hasPrivilege("pools:view"), + firstPost: poolPostNearby.firstPost, + previousPost: poolPostNearby.previousPost, + nextPost: poolPostNearby.nextPost, + lastPost: poolPostNearby.lastPost, + }) + ); + } +} + +module.exports = PoolNavigatorControl; \ No newline at end of file diff --git a/client/js/controls/pool_navigator_list_control.js b/client/js/controls/pool_navigator_list_control.js new file mode 100644 index 00000000..38ec2f12 --- /dev/null +++ b/client/js/controls/pool_navigator_list_control.js @@ -0,0 +1,51 @@ +"use strict"; + +const events = require("../events.js"); +const views = require("../util/views.js"); +const PoolNavigatorControl = require("../controls/pool_navigator_control.js"); + +const template = views.getTemplate("pool-navigator-list"); + +class PoolNavigatorListControl extends events.EventTarget { + constructor(hostNode, poolPostNearby) { + super(); + this._hostNode = hostNode; + this._poolPostNearby = poolPostNearby; + this._indexToNode = {}; + + for (let [i, entry] of this._poolPostNearby.entries()) { + this._installPoolNavigatorNode(entry, i); + } + } + + get _poolNavigatorListNode() { + return this._hostNode; + } + + _installPoolNavigatorNode(poolPostNearby, i) { + const poolListItemNode = document.createElement("div"); + const poolControl = new PoolNavigatorControl( + poolListItemNode, + poolPostNearby, + ); + // events.proxyEvent(commentControl, this, "submit"); + // events.proxyEvent(commentControl, this, "score"); + // events.proxyEvent(commentControl, this, "delete"); + this._indexToNode[poolPostNearby.id] = poolListItemNode; + } + + _uninstallPoolNavigatorNode(index) { + const poolListItemNode = this._indexToNode[index]; + poolListItemNode.parentNode.removeChild(poolListItemNode); + } + + _evtAdd(e) { + this._installPoolNavigatorNode(e.detail.index); + } + + _evtRemove(e) { + this._uninstallPoolNavigatorNode(e.detail.index); + } +} + +module.exports = PoolNavigatorListControl; \ No newline at end of file diff --git a/client/js/models/post_list.js b/client/js/models/post_list.js index 8c2c9d4e..0d3655be 100644 --- a/client/js/models/post_list.js +++ b/client/js/models/post_list.js @@ -16,6 +16,15 @@ class PostList extends AbstractList { ); } + static getNearbyPoolPosts(id) { + return api.get( + uri.formatApiLink("post", id, "pools-nearby", { + query: PostList._decorateSearchQuery(searchQuery || ""), + fields: "id", + }) + ); + } + static search(text, offset, limit, fields) { return api .get( diff --git a/client/js/views/post_main_view.js b/client/js/views/post_main_view.js index 5ef7f61e..88b056d4 100644 --- a/client/js/views/post_main_view.js +++ b/client/js/views/post_main_view.js @@ -57,6 +57,7 @@ class PostMainView { this._installSidebar(ctx); this._installCommentForm(); this._installComments(ctx.post.comments); + this._installPoolNavigators(ctx.poolPostsAround); const showPreviousImage = () => { if (ctx.prevPostId) { @@ -137,6 +138,20 @@ class PostMainView { } } + _installPoolNavigators(poolPostsAround) { + const poolNavigatorsContainerNode = document.querySelector( + "#content-holder .pool-navigators-container" + ); + if (!poolNavigatorsContainerNode) { + return; + } + + this.poolNavigatorsControl = new PoolNavigatorListControl( + poolNavigatorsContainerNode, + poolPostsAround, + ); + } + _installCommentForm() { const commentFormContainer = document.querySelector( "#content-holder .comment-form-container" From e4f7e9e8e0f8676b9e9b1a84cf4f45e83f3b82fb Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 22:07:32 +0100 Subject: [PATCH 11/45] client: add missing import --- client/js/views/post_main_view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/js/views/post_main_view.js b/client/js/views/post_main_view.js index 88b056d4..fa44eb27 100644 --- a/client/js/views/post_main_view.js +++ b/client/js/views/post_main_view.js @@ -10,6 +10,7 @@ const PostContentControl = require("../controls/post_content_control.js"); const PostNotesOverlayControl = require("../controls/post_notes_overlay_control.js"); const PostReadonlySidebarControl = require("../controls/post_readonly_sidebar_control.js"); const PostEditSidebarControl = require("../controls/post_edit_sidebar_control.js"); +const PoolNavigatorListControl = require("../controls/pool_navigator_list_control.js"); const CommentControl = require("../controls/comment_control.js"); const CommentListControl = require("../controls/comment_list_control.js"); From ae899853d2cc918042df5f021bfc4fc0c4fa018d Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 22:12:22 +0100 Subject: [PATCH 12/45] client: append missing child --- client/js/controls/pool_navigator_list_control.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/js/controls/pool_navigator_list_control.js b/client/js/controls/pool_navigator_list_control.js index 38ec2f12..cc9a8151 100644 --- a/client/js/controls/pool_navigator_list_control.js +++ b/client/js/controls/pool_navigator_list_control.js @@ -32,6 +32,7 @@ class PoolNavigatorListControl extends events.EventTarget { // events.proxyEvent(commentControl, this, "score"); // events.proxyEvent(commentControl, this, "delete"); this._indexToNode[poolPostNearby.id] = poolListItemNode; + this._poolNavigatorListNode.appendChild(poolListItemNode); } _uninstallPoolNavigatorNode(index) { From 86320fe2271c40ff17cda29e1b838addfe20d221 Mon Sep 17 00:00:00 2001 From: noirscape Date: Wed, 4 Jan 2023 22:34:48 +0100 Subject: [PATCH 13/45] client: fix some incorrect references --- client/js/controllers/post_main_controller.js | 2 +- client/js/controls/pool_navigator_list_control.js | 5 +---- client/js/models/post_list.js | 1 - client/js/views/post_main_view.js | 6 +++--- server/szurubooru/func/posts.py | 2 +- 5 files changed, 6 insertions(+), 10 deletions(-) diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js index 9ffd7e50..4089673c 100644 --- a/client/js/controllers/post_main_controller.js +++ b/client/js/controllers/post_main_controller.js @@ -19,7 +19,7 @@ class PostMainController extends BasePostController { let poolPostsNearby = Promise.resolve({results: []}); if (api.hasPrivilege("pools:list") && api.hasPrivilege("pools:view")) { - poolPostsNearby = PostList.getNearbyPoolPosts(ctx.parameters.id) + poolPostsNearby = PostList.getNearbyPoolPosts(ctx.parameters.id); } let parameters = ctx.parameters; diff --git a/client/js/controls/pool_navigator_list_control.js b/client/js/controls/pool_navigator_list_control.js index cc9a8151..6aa5302f 100644 --- a/client/js/controls/pool_navigator_list_control.js +++ b/client/js/controls/pool_navigator_list_control.js @@ -28,9 +28,6 @@ class PoolNavigatorListControl extends events.EventTarget { poolListItemNode, poolPostNearby, ); - // events.proxyEvent(commentControl, this, "submit"); - // events.proxyEvent(commentControl, this, "score"); - // events.proxyEvent(commentControl, this, "delete"); this._indexToNode[poolPostNearby.id] = poolListItemNode; this._poolNavigatorListNode.appendChild(poolListItemNode); } @@ -49,4 +46,4 @@ class PoolNavigatorListControl extends events.EventTarget { } } -module.exports = PoolNavigatorListControl; \ No newline at end of file +module.exports = PoolNavigatorListControl; diff --git a/client/js/models/post_list.js b/client/js/models/post_list.js index 0d3655be..16df6eb8 100644 --- a/client/js/models/post_list.js +++ b/client/js/models/post_list.js @@ -19,7 +19,6 @@ class PostList extends AbstractList { static getNearbyPoolPosts(id) { return api.get( uri.formatApiLink("post", id, "pools-nearby", { - query: PostList._decorateSearchQuery(searchQuery || ""), fields: "id", }) ); diff --git a/client/js/views/post_main_view.js b/client/js/views/post_main_view.js index fa44eb27..805a9e73 100644 --- a/client/js/views/post_main_view.js +++ b/client/js/views/post_main_view.js @@ -58,7 +58,7 @@ class PostMainView { this._installSidebar(ctx); this._installCommentForm(); this._installComments(ctx.post.comments); - this._installPoolNavigators(ctx.poolPostsAround); + this._installPoolNavigators(ctx.poolPostsNearby); const showPreviousImage = () => { if (ctx.prevPostId) { @@ -139,7 +139,7 @@ class PostMainView { } } - _installPoolNavigators(poolPostsAround) { + _installPoolNavigators(poolPostsNearby) { const poolNavigatorsContainerNode = document.querySelector( "#content-holder .pool-navigators-container" ); @@ -149,7 +149,7 @@ class PostMainView { this.poolNavigatorsControl = new PoolNavigatorListControl( poolNavigatorsContainerNode, - poolPostsAround, + poolPostsNearby, ); } diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index f1e21889..c2d88c8d 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -1016,7 +1016,7 @@ def serialize_pool_posts_nearby( "pool": pools.serialize_micro_pool(entry.pool), "firstPost": serialize_micro_post(try_get_post_by_id(entry.first_post), None), "lastPost": serialize_micro_post(try_get_post_by_id(entry.last_post), None), - "prevPost": serialize_micro_post(try_get_post_by_id(entry.prev_post), None), + "previousPost": serialize_micro_post(try_get_post_by_id(entry.prev_post), None), "nextPost": serialize_micro_post(try_get_post_by_id(entry.next_post), None), } for entry in nearby ] From 4f663293c04d42fa8c33184392c8f52da2151fc8 Mon Sep 17 00:00:00 2001 From: noirscape Date: Sun, 8 Jan 2023 17:38:15 +0100 Subject: [PATCH 14/45] doc: properly name API elements --- doc/API.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/API.md b/doc/API.md index 56d15d11..cfc918a4 100644 --- a/doc/API.md +++ b/doc/API.md @@ -998,10 +998,10 @@ data. - **Field meaning** - ``: The associated [micro pool resource](#micro-pool). -- `firstPost`: A [micro post resource](#micro-post) that displays the first post in the pool. -- `lastPost`: A [micro post resource](#micro-post) that displays the last post in the pool. -- `nextPost`: A [micro post resource](#micro-post) that displays the next post in the pool. -- `prevPost`: A [micro post resource](#micro-post) that displays the previous post in the pool. +- ``: A [micro post resource](#micro-post) that displays the first post in the pool. +- ``: A [micro post resource](#micro-post) that displays the last post in the pool. +- ``: A [micro post resource](#micro-post) that displays the next post in the pool. +- ``: A [micro post resource](#micro-post) that displays the previous post in the pool. - **Errors** From 650d9784c010680d441ec7b50617ba16f0c6b0d8 Mon Sep 17 00:00:00 2001 From: noirscape Date: Sun, 8 Jan 2023 17:49:31 +0100 Subject: [PATCH 15/45] server/tests: add some tests --- .../tests/api/test_post_retrieving.py | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/server/szurubooru/tests/api/test_post_retrieving.py b/server/szurubooru/tests/api/test_post_retrieving.py index ac984c24..8d8dc2a0 100644 --- a/server/szurubooru/tests/api/test_post_retrieving.py +++ b/server/szurubooru/tests/api/test_post_retrieving.py @@ -125,3 +125,55 @@ def test_trying_to_retrieve_single_without_privileges( context_factory(user=user_factory(rank=model.User.RANK_ANONYMOUS)), {"post_id": 999}, ) + +def test_get_pool_post_around(user_factory, post_factory, pool_factory, pool_post_factory, context_factory): + p1 = post_factory(id=1) + p2 = post_factory(id=2) + p3 = post_factory(id=3) + db.session.add_all([p1, p2, p3]) + + pool = pool_factory(id=1) + db.session.add(pool) + + pool_posts = [pool_post_factory(pool=pool, post=p1), pool_post_factory(pool=pool, post=p2), pool_post_factory(pool=pool, post=p3)] + db.session.add_all(pool_posts) + + result = api.post_api.get_pools_around(context_factory(user=user_factory(rank=model.User.RANK_REGULAR)), {"post_id": 2}) + assert result[0]["previousPost"]["id"] == 1 and result[0]["nextPost"]["id"] == 3 + +def test_get_pool_post_around_start(user_factory, post_factory, pool_factory, pool_post_factory, context_factory): + p1 = post_factory(id=1) + p2 = post_factory(id=2) + p3 = post_factory(id=3) + db.session.add_all([p1, p2, p3]) + + pool = pool_factory(id=1) + db.session.add(pool) + + pool_posts = [pool_post_factory(pool=pool, post=p1), pool_post_factory(pool=pool, post=p2), pool_post_factory(pool=pool, post=p3)] + db.session.add_all(pool_posts) + + result = api.post_api.get_pools_around(context_factory(user=user_factory(rank=model.User.RANK_REGULAR)), {"post_id": 1}) + assert result[0]["previousPost"] == None and result[0]["nextPost"]["id"] == 2 + +def test_get_pool_post_around_end(user_factory, post_factory, pool_factory, pool_post_factory, context_factory): + p1 = post_factory(id=1) + p2 = post_factory(id=2) + p3 = post_factory(id=3) + db.session.add_all([p1, p2, p3]) + + pool = pool_factory(id=1) + db.session.add(pool) + + pool_posts = [pool_post_factory(pool=pool, post=p1), pool_post_factory(pool=pool, post=p2), pool_post_factory(pool=pool, post=p3)] + db.session.add_all(pool_posts) + + result = api.post_api.get_pools_around(context_factory(user=user_factory(rank=model.User.RANK_REGULAR)), {"post_id": 3}) + assert result[0]["previousPost"]["id"] == 2 and result[0]["nextPost"] == None + +def test_get_pool_post_around_no_pool(user_factory, post_factory, pool_factory, pool_post_factory, context_factory): + p1 = post_factory(id=1) + db.session.add(p1) + + result = api.post_api.get_pools_around(context_factory(user=user_factory(rank=model.User.RANK_REGULAR)), {"post_id": 1}) + assert result == [] From 871ebe408385ce4cdc9c78e048446a3941dcc401 Mon Sep 17 00:00:00 2001 From: noirscape Date: Sun, 8 Jan 2023 18:02:07 +0100 Subject: [PATCH 16/45] server/tests: add necessary privilege to fixture --- server/szurubooru/tests/api/test_post_retrieving.py | 1 + 1 file changed, 1 insertion(+) diff --git a/server/szurubooru/tests/api/test_post_retrieving.py b/server/szurubooru/tests/api/test_post_retrieving.py index 8d8dc2a0..aa70f77a 100644 --- a/server/szurubooru/tests/api/test_post_retrieving.py +++ b/server/szurubooru/tests/api/test_post_retrieving.py @@ -14,6 +14,7 @@ def inject_config(config_injector): "privileges": { "posts:list": model.User.RANK_REGULAR, "posts:view": model.User.RANK_REGULAR, + "pools:list": model.User.RANK_REGULAR, }, } ) From c3705f6ee2968795e25a4be779b83537ce627228 Mon Sep 17 00:00:00 2001 From: Ruin0x11 Date: Sat, 8 May 2021 05:04:39 -0700 Subject: [PATCH 17/45] client/pools: thumbnail view in pool list --- client/css/pool-list-view.styl | 109 +++++++++++------- client/html/pools_page.tpl | 59 +++------- client/js/controllers/pool_list_controller.js | 6 + 3 files changed, 90 insertions(+), 84 deletions(-) diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index b7ac15ed..272ac931 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -1,47 +1,60 @@ @import colors .pool-list - table - width: 100% - border-spacing: 0 - text-align: left - line-height: 1.3em - tr:hover td - background: $top-navigation-color - th, td - padding: 0.1em 0.5em - th - white-space: nowrap - background: $top-navigation-color - .names - width: 84% - .post-count - text-align: center - width: 8% - .creation-time - text-align: center - width: 8% - white-space: pre - ul - list-style-type: none - margin: 0 - padding: 0 - display: inline - li - padding: 0 - display: inline - &:not(:last-child):after - content: ', ' - @media (max-width: 800px) - .posts - display: none + ul + list-style-type: none + margin: 0 + padding: 0 + display: flex + align-content: flex-end + flex-wrap: wrap + margin: 0 -0.25em -.darktheme .pool-list - table - tr:hover td - background: $top-navigation-color-darktheme - th - background: $top-navigation-color-darktheme + li + position: relative + flex-grow: 1 + margin: 0 0.25em 0.5em 0.25em + display: inline-block + text-align: left + min-width: 10em + width: 12vw + &:not(.flexbox-dummy) + min-height: 7.5em + height: 9vw + + .thumbnail-wrapper + display: inline-block + width: 100% + height: 100% + line-height: 80% + font-size: 80% + color: white + outline-offset: -3px + box-shadow: 0 0 0 1px rgba(0,0,0,0.2) + + .thumbnail + width: 100% + height: 100% + outline-offset: -3px + &:not(.empty) + background-position: 50% 30% + + .pool-name + color: black + font-size: 1em + text-align: center + a + width: 100% + display: inline-block + + &:hover + background: $post-thumbnail-border-color + .thumbnail + opacity: .9 + + &:hover a, a:active, a:focus + .thumbnail + outline: 4px solid $main-color !important .pool-list-header label @@ -61,3 +74,19 @@ .darktheme .pool-list-header .append color: $inactive-link-color-darktheme + +.post-flow + ul + li + min-width: inherit + width: inherit + &:not(.flexbox-dummy) + height: 14vw + .thumbnail + outline-offset: -1px + .thumbnail-wrapper.no-tags + .thumbnail + outline: 2px solid $post-thumbnail-no-tags-border-color + &:hover a, a:active, a:focus + .thumbnail + outline: 2px solid $main-color !important diff --git a/client/html/pools_page.tpl b/client/html/pools_page.tpl index 0d811808..48530ea3 100644 --- a/client/html/pools_page.tpl +++ b/client/html/pools_page.tpl @@ -1,48 +1,19 @@ -
+<% if (ctx.postFlow) { %>
<% } else { %>
<% } %> <% if (ctx.response.results.length) { %> - - - - - - - - <% for (let pool of ctx.response.results) { %> - - - - - - <% } %> - -
- <% if (ctx.parameters.query == 'sort:name' || !ctx.parameters.query) { %> - '>Pool name(s) - <% } else { %> - '>Pool name(s) + - <% if (ctx.parameters.query == 'sort:post-count') { %> - '>Post count - <% } else { %> - '>Post count - <% } %> - - <% if (ctx.parameters.query == 'sort:creation-time') { %> - '>Created on - <% } else { %> - '>Created on - <% } %> -
-
    - <% for (let name of pool.names) { %> -
  • <%= ctx.makePoolLink(pool.id, false, false, pool, name) %>
  • - <% } %> -
-
- '><%- pool.postCount %> - - <%= ctx.makeRelativeTime(pool.creationTime) %> -
+ +
+ <%= ctx.makePoolLink(pool.id, false, false, pool, name) %> +
+ + <% } %> + <%= ctx.makeFlexboxAlign() %> + <% } %>
diff --git a/client/js/controllers/pool_list_controller.js b/client/js/controllers/pool_list_controller.js index a66f8163..37a45cfc 100644 --- a/client/js/controllers/pool_list_controller.js +++ b/client/js/controllers/pool_list_controller.js @@ -2,6 +2,7 @@ const router = require("../router.js"); const api = require("../api.js"); +const settings = require("../models/settings.js"); const uri = require("../util/uri.js"); const PoolList = require("../models/pool_list.js"); const topNavigation = require("../models/top_navigation.js"); @@ -108,6 +109,11 @@ class PoolListController { ); }, pageRenderer: (pageCtx) => { + Object.assign(pageCtx, { + canViewPosts: api.hasPrivilege("posts:view"), + canViewPools: api.hasPrivilege("pools:view"), + postFlow: settings.get().postFlow, + }); return new PoolsPageView(pageCtx); }, }); From 63dbff36a032b6de428c13a6154a0c493c386596 Mon Sep 17 00:00:00 2001 From: Ruin0x11 Date: Sat, 8 May 2021 16:15:59 -0700 Subject: [PATCH 18/45] client/pools: stacked thumbnail appearance for pool list page --- client/css/pool-list-view.styl | 22 ++++++++++++++++++++-- client/html/pools_page.tpl | 4 ++-- client/js/util/views.js | 24 +++++++++++++++++++++--- 3 files changed, 43 insertions(+), 7 deletions(-) diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index 272ac931..37ec877c 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -3,7 +3,6 @@ .pool-list ul list-style-type: none - margin: 0 padding: 0 display: flex align-content: flex-end @@ -13,7 +12,7 @@ li position: relative flex-grow: 1 - margin: 0 0.25em 0.5em 0.25em + margin: 2em 1.5em 2em 1.2em; display: inline-block text-align: left min-width: 10em @@ -38,6 +37,23 @@ outline-offset: -3px &:not(.empty) background-position: 50% 30% + position: absolute + display: inline-block + + .thumbnail-1 + right: -0px; + top: -0px; + z-index: 30; + + .thumbnail-2 + right: -10px; + top: -10px; + z-index: 20; + + .thumbnail-3 + right: -20px; + top: -20px; + z-index: 10; .pool-name color: black @@ -80,9 +96,11 @@ li min-width: inherit width: inherit + margin: 0 0.25em 0.5em 0.25em &:not(.flexbox-dummy) height: 14vw .thumbnail + position: static outline-offset: -1px .thumbnail-wrapper.no-tags .thumbnail diff --git a/client/html/pools_page.tpl b/client/html/pools_page.tpl index 48530ea3..c935785f 100644 --- a/client/html/pools_page.tpl +++ b/client/html/pools_page.tpl @@ -4,8 +4,8 @@ <% for (let pool of ctx.response.results) { %>
  • - <% if (ctx.canViewPosts && pool.posts.length > 0) { %> - <%= ctx.makeThumbnail(pool.posts.at(0).thumbnailUrl) %> + <% if (ctx.canViewPosts) { %> + <%= ctx.makePoolThumbnails(pool.posts, ctx.postFlow) %> <% } %>
    diff --git a/client/js/util/views.js b/client/js/util/views.js index f6280a1c..cbaefc11 100644 --- a/client/js/util/views.js +++ b/client/js/util/views.js @@ -40,12 +40,12 @@ function makeRelativeTime(time) { ); } -function makeThumbnail(url) { +function makeThumbnail(url, klass) { return makeElement( "span", url ? { - class: "thumbnail", + class: klass || "thumbnail", style: `background-image: url(\'${url}\')`, } : { class: "thumbnail empty" }, @@ -53,6 +53,23 @@ function makeThumbnail(url) { ); } +function makePoolThumbnails(posts, postFlow) { + if (posts.length == 0) { + return makeThumbnail(null); + } + if (postFlow) { + return makeThumbnail(posts.at(0).thumbnailUrl); + } + + let s = ""; + + for (let i = 0; i < Math.min(3, posts.length); i++) { + s += makeThumbnail(posts.at(i).thumbnailUrl, "thumbnail thumbnail-" + (i+1)); + } + + return s; +} + function makeRadio(options) { _imbueId(options); return makeElement( @@ -254,7 +271,7 @@ function makePoolLink(id, includeHash, includeCount, pool, name) { misc.escapeHtml(text) ) : makeElement( - "span", + "div", { class: misc.makeCssName(category, "pool") }, misc.escapeHtml(text) ); @@ -436,6 +453,7 @@ function getTemplate(templatePath) { makeFileSize: makeFileSize, makeMarkdown: makeMarkdown, makeThumbnail: makeThumbnail, + makePoolThumbnails: makePoolThumbnails, makeRadio: makeRadio, makeCheckbox: makeCheckbox, makeSelect: makeSelect, From 745186062ddd69cc715aa6c45ce7f071080b0572 Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 17 May 2023 07:01:15 +0200 Subject: [PATCH 19/45] client/css: use zoomed-in thumbnails for pools --- client/css/pool-list-view.styl | 1 + 1 file changed, 1 insertion(+) diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index 37ec877c..5ca378af 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -35,6 +35,7 @@ width: 100% height: 100% outline-offset: -3px + background-size: cover &:not(.empty) background-position: 50% 30% position: absolute From d59eac948b7cf919161c9d557641da7506dd8460 Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 17 May 2023 09:47:50 +0200 Subject: [PATCH 20/45] client/pool_navigator: respect 'display underscores as spaces' setting --- client/html/pool_navigator.tpl | 2 +- client/js/controls/pool_navigator_control.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/client/html/pool_navigator.tpl b/client/html/pool_navigator.tpl index 647684d8..455b92ce 100644 --- a/client/html/pool_navigator.tpl +++ b/client/html/pool_navigator.tpl @@ -22,7 +22,7 @@ <% if (ctx.canViewPools) { %> <% } %> - Pool: <%- ctx.pool.names[0] %> + Pool: <%- ctx.getPrettyName(ctx.pool.names[0]) %> <% if (ctx.canViewPools) { %> <% } %> diff --git a/client/js/controls/pool_navigator_control.js b/client/js/controls/pool_navigator_control.js index fa4394d1..6a52cd48 100644 --- a/client/js/controls/pool_navigator_control.js +++ b/client/js/controls/pool_navigator_control.js @@ -25,6 +25,7 @@ class PoolNavigatorControl extends events.EventTarget { previousPost: poolPostNearby.previousPost, nextPost: poolPostNearby.nextPost, lastPost: poolPostNearby.lastPost, + getPrettyName: misc.getPrettyName, }) ); } From 1507e6bf2b1816ac0ce0324e00c0cdfc7a185455 Mon Sep 17 00:00:00 2001 From: Eva Date: Fri, 19 May 2023 03:39:34 +0200 Subject: [PATCH 21/45] client/pools: thumbnail hover animation and thinner focus outline Update stylus for :has support --- client/css/pool-list-view.styl | 54 ++++--- client/package-lock.json | 264 +++++++-------------------------- client/package.json | 2 +- 3 files changed, 91 insertions(+), 229 deletions(-) diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index 5ca378af..a599a728 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -12,7 +12,7 @@ li position: relative flex-grow: 1 - margin: 2em 1.5em 2em 1.2em; + margin: 2em 1.5em 2em 1.2em display: inline-block text-align: left min-width: 10em @@ -28,33 +28,42 @@ line-height: 80% font-size: 80% color: white - outline-offset: -3px - box-shadow: 0 0 0 1px rgba(0,0,0,0.2) + outline-offset: -2px + border-right: 20px solid transparent + &:before + content: ' '; + display: block; + position: relative; + width: 100%; + height: 20px; + left: 20px; + bottom: 20px; .thumbnail width: 100% height: 100% - outline-offset: -3px + outline-offset: -2px background-size: cover + transition: top .1s ease-in-out, right .1s ease-in-out &:not(.empty) background-position: 50% 30% position: absolute display: inline-block .thumbnail-1 - right: -0px; - top: -0px; - z-index: 30; + right: -0px + top: -0px + z-index: 30 .thumbnail-2 - right: -10px; - top: -10px; - z-index: 20; + right: -10px + top: -10px + z-index: 20 .thumbnail-3 - right: -20px; - top: -20px; - z-index: 10; + right: -20px + top: -20px + z-index: 10 .pool-name color: black @@ -64,14 +73,21 @@ width: 100% display: inline-block - &:hover - background: $post-thumbnail-border-color + a:active, a:focus .thumbnail - opacity: .9 + outline: 2px solid $main-color !important - &:hover a, a:active, a:focus - .thumbnail - outline: 4px solid $main-color !important +.pool-list ul li:hover, .pool-list ul li:has(a:focus), .pool-list ul li:has(a:active) + a.thumbnail-wrapper + outline: none + + .thumbnail-1 + right: -4px + top: -4px + + .thumbnail-3 + right: -16px + top: -16px .pool-list-header label diff --git a/client/package-lock.json b/client/package-lock.json index 3aa4ca4a..31b85342 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -27,13 +27,19 @@ "html-minifier": "^3.5.18", "jimp": "^0.13.0", "pretty-error": "^3.0.3", - "stylus": "^0.54.8", + "stylus": "^0.59.0", "terser": "^4.8.1", "underscore": "^1.12.1", "watchify": "^4.0.0", "ws": "^7.4.6" } }, + "node_modules/@adobe/css-tools": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", + "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==", + "dev": true + }, "node_modules/@babel/runtime": { "version": "7.10.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", @@ -516,18 +522,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, "node_modules/available-typed-arrays": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", @@ -1737,27 +1731,6 @@ "node": "*" } }, - "node_modules/css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - } - }, - "node_modules/css-parse": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", - "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", - "dev": true, - "dependencies": { - "css": "^2.0.0" - } - }, "node_modules/css-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", @@ -1795,15 +1768,6 @@ "url": "https://github.com/sponsors/fb55" } }, - "node_modules/css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/csso": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz", @@ -1830,15 +1794,6 @@ "ms": "2.0.0" } }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true, - "engines": { - "node": ">=0.10" - } - }, "node_modules/define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -3676,13 +3631,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "deprecated": "https://github.com/lydell/resolve-url#deprecated", - "dev": true - }, "node_modules/ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -3698,12 +3646,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, "node_modules/sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -3794,19 +3736,6 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "dependencies": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "node_modules/source-map-support": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", @@ -3816,12 +3745,6 @@ "source-map": "^0.5.6" } }, - "node_modules/source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, "node_modules/stream-browserify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", @@ -3912,18 +3835,15 @@ } }, "node_modules/stylus": { - "version": "0.54.8", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", - "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", "dev": true, "dependencies": { - "css-parse": "~2.0.0", - "debug": "~3.1.0", + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", "glob": "^7.1.6", - "mkdirp": "~1.0.4", - "safer-buffer": "^2.1.2", "sax": "~1.2.4", - "semver": "^6.3.0", "source-map": "^0.7.3" }, "bin": { @@ -3931,28 +3851,33 @@ }, "engines": { "node": "*" + }, + "funding": { + "url": "https://opencollective.com/stylus" } }, - "node_modules/stylus/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/stylus/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" + "dependencies": { + "ms": "2.1.2" }, "engines": { - "node": ">=10" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/stylus/node_modules/semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - } + "node_modules/stylus/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/stylus/node_modules/source-map": { "version": "0.7.3", @@ -4219,13 +4144,6 @@ "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", "dev": true }, - "node_modules/urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "deprecated": "Please see https://github.com/lydell/urix#deprecated", - "dev": true - }, "node_modules/url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", @@ -4602,6 +4520,12 @@ } }, "dependencies": { + "@adobe/css-tools": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz", + "integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==", + "dev": true + }, "@babel/runtime": { "version": "7.10.3", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz", @@ -5072,12 +4996,6 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true - }, "available-typed-arrays": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz", @@ -6246,35 +6164,6 @@ "randomfill": "^1.0.3" } }, - "css": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz", - "integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "source-map": "^0.6.1", - "source-map-resolve": "^0.5.2", - "urix": "^0.1.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, - "css-parse": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz", - "integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=", - "dev": true, - "requires": { - "css": "^2.0.0" - } - }, "css-select": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz", @@ -6326,12 +6215,6 @@ "ms": "2.0.0" } }, - "decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "dev": true - }, "define-properties": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", @@ -7829,12 +7712,6 @@ "path-parse": "^1.0.6" } }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true - }, "ripemd160": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", @@ -7850,12 +7727,6 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true - }, "sax": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", @@ -7931,19 +7802,6 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "dev": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, "source-map-support": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", @@ -7953,12 +7811,6 @@ "source-map": "^0.5.6" } }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "dev": true - }, "stream-browserify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz", @@ -8040,31 +7892,31 @@ } }, "stylus": { - "version": "0.54.8", - "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz", - "integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==", + "version": "0.59.0", + "resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz", + "integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==", "dev": true, "requires": { - "css-parse": "~2.0.0", - "debug": "~3.1.0", + "@adobe/css-tools": "^4.0.1", + "debug": "^4.3.2", "glob": "^7.1.6", - "mkdirp": "~1.0.4", - "safer-buffer": "^2.1.2", "sax": "~1.2.4", - "semver": "^6.3.0", "source-map": "^0.7.3" }, "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } }, - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, "source-map": { @@ -8287,12 +8139,6 @@ "integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=", "dev": true }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true - }, "url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", diff --git a/client/package.json b/client/package.json index 76376f82..ca575aa5 100644 --- a/client/package.json +++ b/client/package.json @@ -28,7 +28,7 @@ "html-minifier": "^3.5.18", "jimp": "^0.13.0", "pretty-error": "^3.0.3", - "stylus": "^0.54.8", + "stylus": "^0.59.0", "terser": "^4.8.1", "underscore": "^1.12.1", "watchify": "^4.0.0", From ef48b079667022f4e93ed8cd9c53ea9a65d61552 Mon Sep 17 00:00:00 2001 From: Eva Date: Fri, 19 May 2023 03:52:59 +0200 Subject: [PATCH 22/45] client/pools: expand animation --- client/css/pool-list-view.styl | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index a599a728..b6eaec9a 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -36,7 +36,6 @@ position: relative; width: 100%; height: 20px; - left: 20px; bottom: 20px; .thumbnail @@ -51,8 +50,8 @@ display: inline-block .thumbnail-1 - right: -0px - top: -0px + right: -4px + top: -4px z-index: 30 .thumbnail-2 @@ -61,8 +60,8 @@ z-index: 20 .thumbnail-3 - right: -20px - top: -20px + right: -16px + top: -16px z-index: 10 .pool-name @@ -82,12 +81,12 @@ outline: none .thumbnail-1 - right: -4px - top: -4px + right: -0px + top: -0px .thumbnail-3 - right: -16px - top: -16px + right: -20px + top: -20px .pool-list-header label From 2ff79a6aa2bd4f850a10b0f4be30b2714ce644de Mon Sep 17 00:00:00 2001 From: Ruin0x11 Date: Sat, 8 May 2021 02:38:40 -0700 Subject: [PATCH 23/45] server/search: support sorting post search results by pool post order --- .../search/configs/post_search_config.py | 37 +++++++++++++---- server/szurubooru/search/configs/util.py | 1 + server/szurubooru/search/executor.py | 16 +++++--- server/szurubooru/search/typing.py | 2 +- .../search/configs/test_post_search_config.py | 40 +++++++++++++++++++ 5 files changed, 81 insertions(+), 15 deletions(-) diff --git a/server/szurubooru/search/configs/post_search_config.py b/server/szurubooru/search/configs/post_search_config.py index 8d4672d4..e82c006c 100644 --- a/server/szurubooru/search/configs/post_search_config.py +++ b/server/szurubooru/search/configs/post_search_config.py @@ -1,4 +1,4 @@ -from typing import Any, Dict, Optional, Tuple +from typing import Any, Dict, Optional, Tuple, Callable, Union import sqlalchemy as sa @@ -114,12 +114,24 @@ def _pool_filter( query: SaQuery, criterion: Optional[criteria.BaseCriterion], negated: bool ) -> SaQuery: assert criterion - return search_util.create_subquery_filter( - model.Post.post_id, - model.PoolPost.post_id, - model.PoolPost.pool_id, - search_util.create_num_filter, - )(query, criterion, negated) + from szurubooru.search.configs import util as search_util + subquery = db.session.query(model.PoolPost.post_id.label("foreign_id")) + subquery = subquery.options(sa.orm.lazyload("*")) + subquery = search_util.create_num_filter(model.PoolPost.pool_id)(subquery, criterion, False) + subquery = subquery.subquery("t") + expression = model.Post.post_id.in_(subquery) + if negated: + expression = ~expression + return query.filter(expression) + + +def _pool_sort( + query: SaQuery, pool_id: Optional[int] +) -> SaQuery: + if pool_id is None: + return query + return query.join(model.PoolPost, sa.and_(model.PoolPost.post_id == model.Post.post_id, model.PoolPost.pool_id == pool_id)) \ + .order_by(model.PoolPost.order.desc()) def _category_filter( @@ -153,6 +165,7 @@ def _category_filter( class PostSearchConfig(BaseSearchConfig): def __init__(self) -> None: self.user = None # type: Optional[model.User] + self.pool_id = None # type: Optional[int] def on_search_query_parsed(self, search_query: SearchQuery) -> SaQuery: new_special_tokens = [] @@ -177,6 +190,10 @@ class PostSearchConfig(BaseSearchConfig): else: new_special_tokens.append(token) search_query.special_tokens = new_special_tokens + self.pool_id = None + for token in search_query.named_tokens: + if token.name == "pool" and isinstance(token.criterion, criteria.PlainCriterion): + self.pool_id = token.criterion.value def create_around_query(self) -> SaQuery: return db.session.query(model.Post).options(sa.orm.lazyload("*")) @@ -382,7 +399,7 @@ class PostSearchConfig(BaseSearchConfig): ) @property - def sort_columns(self) -> Dict[str, Tuple[SaColumn, str]]: + def sort_columns(self) -> Dict[str, Union[Tuple[SaColumn, str], Callable[[SaQuery], None]]]: return util.unalias_dict( [ ( @@ -444,6 +461,10 @@ class PostSearchConfig(BaseSearchConfig): ["feature-date", "feature-time"], (model.Post.last_feature_time, self.SORT_DESC), ), + ( + ["pool"], + lambda subquery: _pool_sort(subquery, self.pool_id) + ) ] ) diff --git a/server/szurubooru/search/configs/util.py b/server/szurubooru/search/configs/util.py index 58e6ebe5..fd40b43d 100644 --- a/server/szurubooru/search/configs/util.py +++ b/server/szurubooru/search/configs/util.py @@ -205,6 +205,7 @@ def create_subquery_filter( filter_column: SaColumn, filter_factory: SaColumn, subquery_decorator: Callable[[SaQuery], None] = None, + order: SaQuery = None, ) -> Filter: filter_func = filter_factory(filter_column) diff --git a/server/szurubooru/search/executor.py b/server/szurubooru/search/executor.py index a5ef9625..beb17f8a 100644 --- a/server/szurubooru/search/executor.py +++ b/server/szurubooru/search/executor.py @@ -181,14 +181,18 @@ class Executor: _format_dict_keys(self.config.sort_columns), ) ) - column, default_order = self.config.sort_columns[ + entry = self.config.sort_columns[ sort_token.name ] - order = _get_order(sort_token.order, default_order) - if order == sort_token.SORT_ASC: - db_query = db_query.order_by(column.asc()) - elif order == sort_token.SORT_DESC: - db_query = db_query.order_by(column.desc()) + if callable(entry): + db_query = entry(db_query) + else: + column, default_order = entry + order = _get_order(sort_token.order, default_order) + if order == sort_token.SORT_ASC: + db_query = db_query.order_by(column.asc()) + elif order == sort_token.SORT_DESC: + db_query = db_query.order_by(column.desc()) db_query = self.config.finalize_query(db_query) return db_query diff --git a/server/szurubooru/search/typing.py b/server/szurubooru/search/typing.py index 686c2cb6..011f7eae 100644 --- a/server/szurubooru/search/typing.py +++ b/server/szurubooru/search/typing.py @@ -1,4 +1,4 @@ -from typing import Any, Callable +from typing import Any, Callable, Union SaColumn = Any SaQuery = Any diff --git a/server/szurubooru/tests/search/configs/test_post_search_config.py b/server/szurubooru/tests/search/configs/test_post_search_config.py index b86fa273..c3fa8393 100644 --- a/server/szurubooru/tests/search/configs/test_post_search_config.py +++ b/server/szurubooru/tests/search/configs/test_post_search_config.py @@ -725,6 +725,7 @@ def test_filter_by_feature_date( "sort:fav-time", "sort:feature-date", "sort:feature-time", + "sort:pool", ], ) def test_sort_tokens(verify_unpaged, post_factory, input): @@ -865,6 +866,45 @@ def test_tumbleweed( verify_unpaged("-special:tumbleweed", [1, 2, 3]) + def test_sort_pool( + post_factory, pool_factory, pool_category_factory, verify_unpaged + ): + post1 = post_factory(id=1) + post2 = post_factory(id=2) + post3 = post_factory(id=3) + post4 = post_factory(id=4) + pool1 = pool_factory( + id=1, + names=["pool1"], + description="desc", + category=pool_category_factory("test-cat1"), + ) + pool1.posts = [post1, post4, post3] + pool2 = pool_factory( + id=2, + names=["pool2"], + description="desc", + category=pool_category_factory("test-cat2"), + ) + pool2.posts = [post3, post4, post2] + db.session.add_all( + [ + post1, + post2, + post3, + post4, + pool1, + pool2 + ] + ) + db.session.flush() + verify_unpaged("pool:1 sort:pool", [1, 4, 3]) + verify_unpaged("pool:2 sort:pool", [3, 4, 2]) + verify_unpaged("pool:1 pool:2 sort:pool", [4, 3]) + verify_unpaged("pool:2 pool:1 sort:pool", [3, 4]) + verify_unpaged("sort:pool", [1, 2, 3, 4]) + + @pytest.mark.parametrize( "input,expected_post_ids", [ From 5034121be618d1b2e61885298a3d0c2b3baedac6 Mon Sep 17 00:00:00 2001 From: Eva Date: Sat, 20 May 2023 11:52:07 +0200 Subject: [PATCH 24/45] client/help: document sort:pool --- client/html/help_search_posts.tpl | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/client/html/help_search_posts.tpl b/client/html/help_search_posts.tpl index 0fe584fa..311551c4 100644 --- a/client/html/help_search_posts.tpl +++ b/client/html/help_search_posts.tpl @@ -329,6 +329,10 @@ feature-time alias of feature-time + + pool + pool order, requires pool named token + From 7823682b39a78c1a181b7a775caf0fd91734b6b6 Mon Sep 17 00:00:00 2001 From: Eva Date: Sat, 20 May 2023 12:16:46 +0200 Subject: [PATCH 25/45] server/search: allow negating sort:pool --- server/szurubooru/search/configs/post_search_config.py | 10 ++++++---- server/szurubooru/search/executor.py | 3 ++- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/server/szurubooru/search/configs/post_search_config.py b/server/szurubooru/search/configs/post_search_config.py index e82c006c..18194085 100644 --- a/server/szurubooru/search/configs/post_search_config.py +++ b/server/szurubooru/search/configs/post_search_config.py @@ -126,12 +126,14 @@ def _pool_filter( def _pool_sort( - query: SaQuery, pool_id: Optional[int] + query: SaQuery, pool_id: Optional[int], order: str ) -> SaQuery: if pool_id is None: return query - return query.join(model.PoolPost, sa.and_(model.PoolPost.post_id == model.Post.post_id, model.PoolPost.pool_id == pool_id)) \ - .order_by(model.PoolPost.order.desc()) + db_query = query.join(model.PoolPost, sa.and_(model.PoolPost.post_id == model.Post.post_id, model.PoolPost.pool_id == pool_id)) + if order == tokens.SortToken.SORT_DESC: + return db_query.order_by(model.PoolPost.order.desc()) + return db_query.order_by(model.PoolPost.order.asc()) def _category_filter( @@ -463,7 +465,7 @@ class PostSearchConfig(BaseSearchConfig): ), ( ["pool"], - lambda subquery: _pool_sort(subquery, self.pool_id) + lambda subquery, order: _pool_sort(subquery, self.pool_id, order) ) ] ) diff --git a/server/szurubooru/search/executor.py b/server/szurubooru/search/executor.py index beb17f8a..4e01915f 100644 --- a/server/szurubooru/search/executor.py +++ b/server/szurubooru/search/executor.py @@ -185,7 +185,8 @@ class Executor: sort_token.name ] if callable(entry): - db_query = entry(db_query) + order = _get_order(sort_token.order, sort_token.SORT_DESC) + db_query = entry(db_query, order) else: column, default_order = entry order = _get_order(sort_token.order, default_order) From 769b4f0e228f2c453a86c0b0cb65b905cf236349 Mon Sep 17 00:00:00 2001 From: Eva Date: Sat, 20 May 2023 12:22:49 +0200 Subject: [PATCH 26/45] client/pools: sort by negated pool order by default --- client/html/pool_delete.tpl | 2 +- client/html/pool_summary.tpl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/html/pool_delete.tpl b/client/html/pool_delete.tpl index 1ef7fb53..b8042756 100644 --- a/client/html/pool_delete.tpl +++ b/client/html/pool_delete.tpl @@ -1,6 +1,6 @@
    -

    This pool has '><%- ctx.pool.postCount %> post(s).

    +

    This pool has '><%- ctx.pool.postCount %> post(s).

    • diff --git a/client/html/pool_summary.tpl b/client/html/pool_summary.tpl index 8f4e27d0..3bfa6ef1 100644 --- a/client/html/pool_summary.tpl +++ b/client/html/pool_summary.tpl @@ -18,6 +18,6 @@

      <%= ctx.makeMarkdown(ctx.pool.description || 'This pool has no description yet.') %> -

      This pool has '><%- ctx.pool.postCount %> post(s).

      +

      This pool has '><%- ctx.pool.postCount %> post(s).

    From 0ff359d613de73f7707d03ad11636d3be0c27179 Mon Sep 17 00:00:00 2001 From: Eva Date: Sat, 20 May 2023 13:19:21 +0200 Subject: [PATCH 27/45] client/posts: replace main navigation with pool navigation when in pool --- client/js/controllers/post_main_controller.js | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js index 4089673c..706588c4 100644 --- a/client/js/controllers/post_main_controller.js +++ b/client/js/controllers/post_main_controller.js @@ -33,6 +33,7 @@ class PostMainController extends BasePostController { ]).then( (responses) => { const [post, aroundResponse, poolPostsNearby] = responses; + let activePool = null; // remove junk from query, but save it into history so that it can // be still accessed after history navigation / page refresh @@ -46,6 +47,12 @@ class PostMainController extends BasePostController { ) : uri.formatClientLink("post", ctx.parameters.id); router.replace(url, ctx.state, false); + parameters.query.split(" ").forEach((item) => { + const found = item.match(/^pool:([0-9]+)/i); + if (found) { + activePool = parseInt(found[1]); + } + }); } this._post = post; @@ -53,12 +60,12 @@ class PostMainController extends BasePostController { post: post, poolPostsNearby: poolPostsNearby, editMode: editMode, - prevPostId: aroundResponse.prev - ? aroundResponse.prev.id - : null, - nextPostId: aroundResponse.next - ? aroundResponse.next.id - : null, + prevPostId: activePool && poolPostsNearby.length > 0 + ? (poolPostsNearby[0].previousPost ? poolPostsNearby[0].previousPost.id : null) + : (aroundResponse.prev ? aroundResponse.prev.id : null), + nextPostId: activePool && poolPostsNearby.length > 0 + ? (poolPostsNearby[0].nextPost ? poolPostsNearby[0].nextPost.id : null) + : (aroundResponse.next ? aroundResponse.next.id : null), canEditPosts: api.hasPrivilege("posts:edit"), canDeletePosts: api.hasPrivilege("posts:delete"), canFeaturePosts: api.hasPrivilege("posts:feature"), From 2e0dd251b270dd32ab734f75525050d40ab69e92 Mon Sep 17 00:00:00 2001 From: Eva Date: Sat, 20 May 2023 13:37:15 +0200 Subject: [PATCH 28/45] client/posts: remove unavailable first and last links in pool navigator --- client/html/pool_navigator.tpl | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/html/pool_navigator.tpl b/client/html/pool_navigator.tpl index 455b92ce..7e6962a1 100644 --- a/client/html/pool_navigator.tpl +++ b/client/html/pool_navigator.tpl @@ -1,11 +1,11 @@
    - <% if (ctx.canViewPosts && ctx.firstPost) { %> + <% if (ctx.canViewPosts && ctx.previousPost && ctx.firstPost) { %> <% } %> « - <% if (ctx.canViewPosts && ctx.firstPost) { %> + <% if (ctx.canViewPosts && ctx.previousPost && ctx.firstPost) { %> <% } %> @@ -37,11 +37,11 @@ <% } %> - <% if (ctx.canViewPosts && ctx.lastPost) { %> + <% if (ctx.canViewPosts && ctx.nextPost && ctx.lastPost) { %> <% } %> » - <% if (ctx.canViewPosts && ctx.lastPost) { %> + <% if (ctx.canViewPosts && ctx.nextPost && ctx.lastPost) { %> <% } %> From cf0a64d832ca754d5645ef250fa5a05325beaea9 Mon Sep 17 00:00:00 2001 From: Eva Date: Sat, 20 May 2023 13:50:52 +0200 Subject: [PATCH 29/45] client/css: pool navigator styling --- client/css/pool-navigator-control.styl | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/client/css/pool-navigator-control.styl b/client/css/pool-navigator-control.styl index 56c22d28..f1ee14f8 100644 --- a/client/css/pool-navigator-control.styl +++ b/client/css/pool-navigator-control.styl @@ -10,8 +10,8 @@ margin: 0 0 1em 0 display: flex padding: 0.5em 1em - border: 1px solid $pool-navigator-border-color - background: $pool-navigator-background-color + border: 1px solid $line-color + background: $top-navigation-color .pool-name flex: 1 1; @@ -26,9 +26,14 @@ .first, .prev, .next, .last flex: 0 1; - margin: 0 .25em; white-space: nowrap; + >span + padding-top: 2px + padding-bottom: 2px + margin: 0 .25em; -.darktheme .pool-navigator-container - background: $pool-navigator-header-background-color-darktheme \ No newline at end of file + +.darktheme .pool-navigator-container .pool-info-wrapper + border: 1px solid $top-navigation-color-darktheme + background: $window-color-darktheme \ No newline at end of file From 4792f013622fc4a78d9a03356ab03067b00042b1 Mon Sep 17 00:00:00 2001 From: Eva Date: Sat, 20 May 2023 19:10:19 +0200 Subject: [PATCH 30/45] client/posts: use correct pool's posts when overriding navigation We were always using the first pool the current post belongs to. --- client/js/controllers/post_main_controller.js | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js index 706588c4..4df4a496 100644 --- a/client/js/controllers/post_main_controller.js +++ b/client/js/controllers/post_main_controller.js @@ -33,7 +33,7 @@ class PostMainController extends BasePostController { ]).then( (responses) => { const [post, aroundResponse, poolPostsNearby] = responses; - let activePool = null; + let aroundPool = null; // remove junk from query, but save it into history so that it can // be still accessed after history navigation / page refresh @@ -50,7 +50,12 @@ class PostMainController extends BasePostController { parameters.query.split(" ").forEach((item) => { const found = item.match(/^pool:([0-9]+)/i); if (found) { - activePool = parseInt(found[1]); + const activePool = parseInt(found[1]); + poolPostsNearby.forEach((nearbyPosts) => { + if (nearbyPosts.pool.id == activePool) { + aroundPool = nearbyPosts + } + }); } }); } @@ -60,11 +65,11 @@ class PostMainController extends BasePostController { post: post, poolPostsNearby: poolPostsNearby, editMode: editMode, - prevPostId: activePool && poolPostsNearby.length > 0 - ? (poolPostsNearby[0].previousPost ? poolPostsNearby[0].previousPost.id : null) + prevPostId: aroundPool + ? (aroundPool.previousPost ? aroundPool.previousPost.id : null) : (aroundResponse.prev ? aroundResponse.prev.id : null), - nextPostId: activePool && poolPostsNearby.length > 0 - ? (poolPostsNearby[0].nextPost ? poolPostsNearby[0].nextPost.id : null) + nextPostId: aroundPool + ? (aroundPool.nextPost ? aroundPool.nextPost.id : null) : (aroundResponse.next ? aroundResponse.next.id : null), canEditPosts: api.hasPrivilege("posts:edit"), canDeletePosts: api.hasPrivilege("posts:delete"), From 0601c32598bb1d0b0107617297c719d3295b24ec Mon Sep 17 00:00:00 2001 From: Eva Date: Mon, 22 May 2023 06:47:44 +0200 Subject: [PATCH 31/45] client/css: fix pool thumbnail animations and outline on firefox --- client/css/pool-list-view.styl | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index b6eaec9a..4999a51a 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -28,7 +28,7 @@ line-height: 80% font-size: 80% color: white - outline-offset: -2px + outline: none border-right: 20px solid transparent &:before content: ' '; @@ -76,10 +76,18 @@ .thumbnail outline: 2px solid $main-color !important -.pool-list ul li:hover, .pool-list ul li:has(a:focus), .pool-list ul li:has(a:active) - a.thumbnail-wrapper - outline: none +.pool-list ul li:hover + .thumbnail-wrapper + .thumbnail-1 + right: -0px + top: -0px + .thumbnail-3 + right: -20px + top: -20px + +.pool-list ul li:has(a:focus), .pool-list ul li:has(a:active) + .thumbnail-wrapper .thumbnail-1 right: -0px top: -0px From 64b7b6d0bc5996c0fd1552f9d8ec1ae962c8ff68 Mon Sep 17 00:00:00 2001 From: Eva Date: Mon, 22 May 2023 18:25:03 +0200 Subject: [PATCH 32/45] server/posts: optimize nearby pool posts This was very slow when any entry was unavailable, such as on single-post pools, or edges of pools (first/last post). Also only fetch id. Previously it would get the thumbnail url. --- server/szurubooru/func/posts.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index c2d88c8d..60a5d703 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -1014,9 +1014,9 @@ def serialize_pool_posts_nearby( return [ { "pool": pools.serialize_micro_pool(entry.pool), - "firstPost": serialize_micro_post(try_get_post_by_id(entry.first_post), None), - "lastPost": serialize_micro_post(try_get_post_by_id(entry.last_post), None), - "previousPost": serialize_micro_post(try_get_post_by_id(entry.prev_post), None), - "nextPost": serialize_micro_post(try_get_post_by_id(entry.next_post), None), + "firstPost": {"id": getattr(try_get_post_by_id(entry.first_post), "post_id", None)} if entry.first_post else None, + "lastPost": {"id": getattr(try_get_post_by_id(entry.last_post), "post_id", None)} if entry.last_post else None, + "previousPost": {"id": getattr(try_get_post_by_id(entry.prev_post), "post_id", None)} if entry.prev_post else None, + "nextPost": {"id": getattr(try_get_post_by_id(entry.next_post), "post_id", None)} if entry.next_post else None, } for entry in nearby ] From 7708b4e5a319b8607a35625029847c785456b900 Mon Sep 17 00:00:00 2001 From: Eva Date: Thu, 25 May 2023 04:04:29 +0200 Subject: [PATCH 33/45] client/pools: fix empty pool thumbnail display --- client/css/pool-list-view.styl | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index 4999a51a..0147c01a 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -44,12 +44,11 @@ outline-offset: -2px background-size: cover transition: top .1s ease-in-out, right .1s ease-in-out - &:not(.empty) - background-position: 50% 30% - position: absolute - display: inline-block + background-position: 50% 30% + position: absolute + display: inline-block - .thumbnail-1 + .thumbnail-1, .thumbnail.empty right: -4px top: -4px z-index: 30 From 3875ec173f609a60133bf179f3a132915fcd63b8 Mon Sep 17 00:00:00 2001 From: Eva Date: Thu, 3 Apr 2025 01:39:09 +0200 Subject: [PATCH 34/45] client, server: merge nearby pool posts into regular post serialization Can still be cleaned up some more. Need to compare speed of the get_around query vs nearby pool posts. --- client/js/controllers/post_main_controller.js | 15 +--- client/js/controls/pool_navigator_control.js | 6 +- .../controls/pool_navigator_list_control.js | 6 +- client/js/models/pool.js | 20 +++++ client/js/models/post_list.js | 8 -- client/js/views/post_main_view.js | 6 +- server/szurubooru/api/post_api.py | 13 ---- server/szurubooru/func/posts.py | 74 +++++++++---------- 8 files changed, 67 insertions(+), 81 deletions(-) diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js index 4df4a496..00db7a1c 100644 --- a/client/js/controllers/post_main_controller.js +++ b/client/js/controllers/post_main_controller.js @@ -17,11 +17,6 @@ class PostMainController extends BasePostController { constructor(ctx, editMode) { super(ctx); - let poolPostsNearby = Promise.resolve({results: []}); - if (api.hasPrivilege("pools:list") && api.hasPrivilege("pools:view")) { - poolPostsNearby = PostList.getNearbyPoolPosts(ctx.parameters.id); - } - let parameters = ctx.parameters; Promise.all([ Post.get(ctx.parameters.id), @@ -29,10 +24,9 @@ class PostMainController extends BasePostController { ctx.parameters.id, parameters ? parameters.query : null ), - poolPostsNearby ]).then( (responses) => { - const [post, aroundResponse, poolPostsNearby] = responses; + const [post, aroundResponse] = responses; let aroundPool = null; // remove junk from query, but save it into history so that it can @@ -51,9 +45,9 @@ class PostMainController extends BasePostController { const found = item.match(/^pool:([0-9]+)/i); if (found) { const activePool = parseInt(found[1]); - poolPostsNearby.forEach((nearbyPosts) => { - if (nearbyPosts.pool.id == activePool) { - aroundPool = nearbyPosts + post.pools.map((pool) => { + if (pool.id == activePool) { + aroundPool = pool; } }); } @@ -63,7 +57,6 @@ class PostMainController extends BasePostController { this._post = post; this._view = new PostMainView({ post: post, - poolPostsNearby: poolPostsNearby, editMode: editMode, prevPostId: aroundPool ? (aroundPool.previousPost ? aroundPool.previousPost.id : null) diff --git a/client/js/controls/pool_navigator_control.js b/client/js/controls/pool_navigator_control.js index 6a52cd48..41b68d53 100644 --- a/client/js/controls/pool_navigator_control.js +++ b/client/js/controls/pool_navigator_control.js @@ -16,9 +16,9 @@ class PoolNavigatorControl extends events.EventTarget { views.replaceContent( this._hostNode, template({ - pool: poolPostNearby.pool, - parameters: { query: `pool:${poolPostNearby.pool.id}` }, - linkClass: misc.makeCssName(poolPostNearby.pool.category, "pool"), + pool: poolPostNearby, + parameters: { query: `pool:${poolPostNearby.id}` }, + linkClass: misc.makeCssName(poolPostNearby.category, "pool"), canViewPosts: api.hasPrivilege("posts:view"), canViewPools: api.hasPrivilege("pools:view"), firstPost: poolPostNearby.firstPost, diff --git a/client/js/controls/pool_navigator_list_control.js b/client/js/controls/pool_navigator_list_control.js index 6aa5302f..37d052f2 100644 --- a/client/js/controls/pool_navigator_list_control.js +++ b/client/js/controls/pool_navigator_list_control.js @@ -13,8 +13,8 @@ class PoolNavigatorListControl extends events.EventTarget { this._poolPostNearby = poolPostNearby; this._indexToNode = {}; - for (let [i, entry] of this._poolPostNearby.entries()) { - this._installPoolNavigatorNode(entry, i); + for (const entry of this._poolPostNearby) { + this._installPoolNavigatorNode(entry); } } @@ -22,7 +22,7 @@ class PoolNavigatorListControl extends events.EventTarget { return this._hostNode; } - _installPoolNavigatorNode(poolPostNearby, i) { + _installPoolNavigatorNode(poolPostNearby) { const poolListItemNode = document.createElement("div"); const poolControl = new PoolNavigatorControl( poolListItemNode, diff --git a/client/js/models/pool.js b/client/js/models/pool.js index 51fa8a05..24b89fe3 100644 --- a/client/js/models/pool.js +++ b/client/js/models/pool.js @@ -51,6 +51,22 @@ class Pool extends events.EventTarget { return this._lastEditTime; } + get firstPost() { + return this._firstPost; + } + + get lastPost() { + return this._lastPost; + } + + get previousPost() { + return this._previousPost; + } + + get nextPost() { + return this._nextPost; + } + set names(value) { this._names = value; } @@ -169,6 +185,10 @@ class Pool extends events.EventTarget { _creationTime: response.creationTime, _lastEditTime: response.lastEditTime, _postCount: response.postCount || 0, + _firstPost: response.firstPost || null, + _lastPost: response.lastPost || null, + _previousPost: response.previousPost || null, + _nextPost: response.nextPost || null, }; for (let obj of [this, this._orig]) { diff --git a/client/js/models/post_list.js b/client/js/models/post_list.js index 16df6eb8..8c2c9d4e 100644 --- a/client/js/models/post_list.js +++ b/client/js/models/post_list.js @@ -16,14 +16,6 @@ class PostList extends AbstractList { ); } - static getNearbyPoolPosts(id) { - return api.get( - uri.formatApiLink("post", id, "pools-nearby", { - fields: "id", - }) - ); - } - static search(text, offset, limit, fields) { return api .get( diff --git a/client/js/views/post_main_view.js b/client/js/views/post_main_view.js index 805a9e73..6ddd00cd 100644 --- a/client/js/views/post_main_view.js +++ b/client/js/views/post_main_view.js @@ -58,7 +58,7 @@ class PostMainView { this._installSidebar(ctx); this._installCommentForm(); this._installComments(ctx.post.comments); - this._installPoolNavigators(ctx.poolPostsNearby); + this._installPoolNavigators(ctx); const showPreviousImage = () => { if (ctx.prevPostId) { @@ -139,7 +139,7 @@ class PostMainView { } } - _installPoolNavigators(poolPostsNearby) { + _installPoolNavigators(ctx) { const poolNavigatorsContainerNode = document.querySelector( "#content-holder .pool-navigators-container" ); @@ -149,7 +149,7 @@ class PostMainView { this.poolNavigatorsControl = new PoolNavigatorListControl( poolNavigatorsContainerNode, - poolPostsNearby, + ctx.post.pools, ); } diff --git a/server/szurubooru/api/post_api.py b/server/szurubooru/api/post_api.py index 34a2136c..69239725 100644 --- a/server/szurubooru/api/post_api.py +++ b/server/szurubooru/api/post_api.py @@ -281,19 +281,6 @@ def get_posts_around( ctx, post_id, lambda post: _serialize_post(ctx, post) ) -@rest.routes.get("/post/(?P[^/]+)/pools-nearby/?") -def get_pools_around( - ctx: rest.Context, params: Dict[str, str] -) -> rest.Response: - auth.verify_privilege(ctx.user, "posts:list") - auth.verify_privilege(ctx.user, "posts:view") - auth.verify_privilege(ctx.user, "pools:list") - _search_executor_config.user = ctx.user - post = _get_post(params) - results = posts.get_pools_nearby(post) - return posts.serialize_pool_posts_nearby(results) - - @rest.routes.post("/posts/reverse-search/?") def get_posts_by_image( diff --git a/server/szurubooru/func/posts.py b/server/szurubooru/func/posts.py index 60a5d703..e3eaef41 100644 --- a/server/szurubooru/func/posts.py +++ b/server/szurubooru/func/posts.py @@ -9,6 +9,7 @@ import sqlalchemy as sa from szurubooru import config, db, errors, model, rest from szurubooru.func import ( + auth, comments, files, image_hash, @@ -345,8 +346,10 @@ class PostSerializer(serialization.BaseSerializer): ] def serialize_pools(self) -> List[Any]: + if not auth.has_privilege(self.auth_user, "pools:list"): + return [] return [ - pools.serialize_micro_pool(pool) + {**pools.serialize_micro_pool(pool), **get_pool_posts_nearby(self.post, pool)} if auth.has_privilege(self.auth_user, "pools:view") else pools.serialize_micro_pool(pool) for pool in sorted( self.post.pools, key=lambda pool: pool.creation_time ) @@ -977,46 +980,37 @@ def search_by_image(image_content: bytes) -> List[Tuple[float, model.Post]]: else: return [] -PoolPostsNearby = namedtuple('PoolPostsNearby', 'pool first_post prev_post next_post last_post') -def get_pools_nearby( - post: model.Post -) -> List[PoolPostsNearby]: - response = [] - pools = post.pools +def serialize_safe_post( + post: Optional[model.Post] +) -> rest.Response: + return {"id": getattr(post, "post_id", None)} if post else None - for pool in pools: - prev_post_id = None - next_post_id = None - first_post_id = pool.posts[0].post_id, - last_post_id = pool.posts[-1].post_id, - for previous_item, current_item, next_item in _get_nearby_iter(pool.posts): - if post.post_id == current_item.post_id: - if previous_item != None: - prev_post_id = previous_item.post_id - if next_item != None: - next_post_id = next_item.post_id - break +def serialize_id_post( + post_id: Optional[int] +) -> rest.Response: + return serialize_safe_post(try_get_post_by_id(post_id)) if post_id else None - resp_entry = PoolPostsNearby( - pool=pool, - first_post=first_post_id, - last_post=last_post_id, - prev_post=prev_post_id, - next_post=next_post_id, - ) - response.append(resp_entry) - return response -def serialize_pool_posts_nearby( - nearby: List[PoolPostsNearby] -) -> Optional[rest.Response]: - return [ - { - "pool": pools.serialize_micro_pool(entry.pool), - "firstPost": {"id": getattr(try_get_post_by_id(entry.first_post), "post_id", None)} if entry.first_post else None, - "lastPost": {"id": getattr(try_get_post_by_id(entry.last_post), "post_id", None)} if entry.last_post else None, - "previousPost": {"id": getattr(try_get_post_by_id(entry.prev_post), "post_id", None)} if entry.prev_post else None, - "nextPost": {"id": getattr(try_get_post_by_id(entry.next_post), "post_id", None)} if entry.next_post else None, - } for entry in nearby - ] +def get_pool_posts_nearby( + post: model.Post, pool: model.Pool +) -> rest.Response: + prev_post_id = None + next_post_id = None + first_post_id = pool.posts[0].post_id, + last_post_id = pool.posts[-1].post_id, + + for previous_item, current_item, next_item in _get_nearby_iter(pool.posts): + if post.post_id == current_item.post_id: + if previous_item != None: + prev_post_id = previous_item.post_id + if next_item != None: + next_post_id = next_item.post_id + break + + return { + "firstPost": serialize_id_post(first_post_id), + "lastPost": serialize_id_post(last_post_id), + "previousPost": serialize_id_post(prev_post_id), + "nextPost": serialize_id_post(next_post_id), + } From 374f4a5fa9a83c4b43a52bedaf8d6b02ee5f089e Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 2 Apr 2025 06:14:49 +0200 Subject: [PATCH 35/45] client/posts: split query by any whitespace --- client/js/controllers/post_main_controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js index 00db7a1c..d7395fc4 100644 --- a/client/js/controllers/post_main_controller.js +++ b/client/js/controllers/post_main_controller.js @@ -41,7 +41,7 @@ class PostMainController extends BasePostController { ) : uri.formatClientLink("post", ctx.parameters.id); router.replace(url, ctx.state, false); - parameters.query.split(" ").forEach((item) => { + misc.splitByWhitespace(parameters.query).forEach((item) => { const found = item.match(/^pool:([0-9]+)/i); if (found) { const activePool = parseInt(found[1]); From a5cf49a94ad593bf88f713dea9d5bb8a3b8b8f5a Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 2 Apr 2025 05:26:51 +0200 Subject: [PATCH 36/45] client/pools: remove broken selector --- client/js/views/pool_create_view.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/js/views/pool_create_view.js b/client/js/views/pool_create_view.js index fc75f452..c3282ca1 100644 --- a/client/js/views/pool_create_view.js +++ b/client/js/views/pool_create_view.js @@ -30,7 +30,7 @@ class PoolCreateView extends events.EventTarget { } for (let node of this._formNode.querySelectorAll( - "input, select, textarea, posts" + "input, select, textarea" )) { node.addEventListener("change", (e) => { this.dispatchEvent(new CustomEvent("change")); From 0acc522bfc27b753d0ff5eea2aba9a7a4f9a9acd Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 2 Apr 2025 06:11:04 +0200 Subject: [PATCH 37/45] server/pools: add field for retrieving only the first 3 posts --- server/szurubooru/func/pools.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/server/szurubooru/func/pools.py b/server/szurubooru/func/pools.py index c3ea9f0f..e1efe006 100644 --- a/server/szurubooru/func/pools.py +++ b/server/szurubooru/func/pools.py @@ -108,6 +108,7 @@ class PoolSerializer(serialization.BaseSerializer): "lastEditTime": self.serialize_last_edit_time, "postCount": self.serialize_post_count, "posts": self.serialize_posts, + "postsMicro": self.serialize_posts_micro, } def serialize_id(self) -> Any: @@ -143,6 +144,14 @@ class PoolSerializer(serialization.BaseSerializer): ] ] + def serialize_posts_micro(self) -> Any: + posts_micro = [] + for i, rel in enumerate(self.pool.posts): + posts_micro.append(posts.serialize_micro_post(rel, None)) + if i == 2: + break + return posts_micro + def serialize_pool( pool: model.Pool, options: List[str] = [] From ff788a5e30107a352cb0181c124e0ae31642e0e4 Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 2 Apr 2025 06:11:58 +0200 Subject: [PATCH 38/45] client/pools: use cheaper pool post listing for unprivileged users --- client/js/controllers/pool_list_controller.js | 5 +++-- client/js/models/pool.js | 5 +++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/js/controllers/pool_list_controller.js b/client/js/controllers/pool_list_controller.js index 37a45cfc..0590e9d9 100644 --- a/client/js/controllers/pool_list_controller.js +++ b/client/js/controllers/pool_list_controller.js @@ -14,7 +14,6 @@ const EmptyView = require("../views/empty_view.js"); const fields = [ "id", "names", - "posts", "creationTime", "postCount", "category", @@ -101,11 +100,13 @@ class PoolListController { return uri.formatClientLink("pools", parameters); }, requestPage: (offset, limit) => { + const canEditPosts = api.hasPrivilege("pools:edit") || api.hasPrivilege("pools:edit:posts"); + const effectiveFields = fields.concat([canEditPosts ? "posts": "postsMicro"]); return PoolList.search( this._ctx.parameters.query, offset, limit, - fields + effectiveFields ); }, pageRenderer: (pageCtx) => { diff --git a/client/js/models/pool.js b/client/js/models/pool.js index 24b89fe3..c61f9f28 100644 --- a/client/js/models/pool.js +++ b/client/js/models/pool.js @@ -36,7 +36,7 @@ class Pool extends events.EventTarget { } get posts() { - return this._posts; + return this._postsMicro || this._posts; } get postCount() { @@ -185,6 +185,7 @@ class Pool extends events.EventTarget { _creationTime: response.creationTime, _lastEditTime: response.lastEditTime, _postCount: response.postCount || 0, + _postsMicro: response.postsMicro, _firstPost: response.firstPost || null, _lastPost: response.lastPost || null, _previousPost: response.previousPost || null, @@ -192,7 +193,7 @@ class Pool extends events.EventTarget { }; for (let obj of [this, this._orig]) { - obj._posts.sync(response.posts); + obj._posts.sync(response.posts || []); } Object.assign(this, map); From ac64c1ca507afc3695930c6ec23a27fc8223727b Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 2 Apr 2025 06:25:46 +0200 Subject: [PATCH 39/45] client/css: add missing variable for pool categories page --- client/css/colors.styl | 1 + 1 file changed, 1 insertion(+) diff --git a/client/css/colors.styl b/client/css/colors.styl index cf7e7caf..aa1d6e03 100644 --- a/client/css/colors.styl +++ b/client/css/colors.styl @@ -36,6 +36,7 @@ $button-disabled-background-color = #CCC $post-thumbnail-border-color = $main-color $post-thumbnail-no-tags-border-color = #F44 $default-tag-category-background-color = $active-tab-background-color +$default-pool-category-background-color = $active-tab-background-color $new-tag-background-color = #DFC $new-tag-text-color = black $implied-tag-background-color = #FFC From 5244718e0f480e7e53525f6c1d68ba6cc1494624 Mon Sep 17 00:00:00 2001 From: Eva Date: Wed, 2 Apr 2025 06:32:05 +0200 Subject: [PATCH 40/45] client/css: pool thumbnail outline Same as posts in search results. --- client/css/pool-list-view.styl | 1 + 1 file changed, 1 insertion(+) diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index 0147c01a..af85dff0 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -47,6 +47,7 @@ background-position: 50% 30% position: absolute display: inline-block + box-shadow: 0 0 0 1px rgba(0,0,0,0.2) .thumbnail-1, .thumbnail.empty right: -4px From 98227e562e7a1e15c7ae400f3739e123a684102e Mon Sep 17 00:00:00 2001 From: Eva Date: Thu, 3 Apr 2025 01:21:49 +0200 Subject: [PATCH 41/45] server/tests: fix sort:pool test indentation --- .../search/configs/test_post_search_config.py | 74 +++++++++---------- 1 file changed, 37 insertions(+), 37 deletions(-) diff --git a/server/szurubooru/tests/search/configs/test_post_search_config.py b/server/szurubooru/tests/search/configs/test_post_search_config.py index c3fa8393..13ee16b0 100644 --- a/server/szurubooru/tests/search/configs/test_post_search_config.py +++ b/server/szurubooru/tests/search/configs/test_post_search_config.py @@ -866,43 +866,43 @@ def test_tumbleweed( verify_unpaged("-special:tumbleweed", [1, 2, 3]) - def test_sort_pool( - post_factory, pool_factory, pool_category_factory, verify_unpaged - ): - post1 = post_factory(id=1) - post2 = post_factory(id=2) - post3 = post_factory(id=3) - post4 = post_factory(id=4) - pool1 = pool_factory( - id=1, - names=["pool1"], - description="desc", - category=pool_category_factory("test-cat1"), - ) - pool1.posts = [post1, post4, post3] - pool2 = pool_factory( - id=2, - names=["pool2"], - description="desc", - category=pool_category_factory("test-cat2"), - ) - pool2.posts = [post3, post4, post2] - db.session.add_all( - [ - post1, - post2, - post3, - post4, - pool1, - pool2 - ] - ) - db.session.flush() - verify_unpaged("pool:1 sort:pool", [1, 4, 3]) - verify_unpaged("pool:2 sort:pool", [3, 4, 2]) - verify_unpaged("pool:1 pool:2 sort:pool", [4, 3]) - verify_unpaged("pool:2 pool:1 sort:pool", [3, 4]) - verify_unpaged("sort:pool", [1, 2, 3, 4]) +def test_sort_pool( + post_factory, pool_factory, pool_category_factory, verify_unpaged +): + post1 = post_factory(id=1) + post2 = post_factory(id=2) + post3 = post_factory(id=3) + post4 = post_factory(id=4) + pool1 = pool_factory( + id=1, + names=["pool1"], + description="desc", + category=pool_category_factory("test-cat1"), + ) + pool1.posts = [post1, post4, post3] + pool2 = pool_factory( + id=2, + names=["pool2"], + description="desc", + category=pool_category_factory("test-cat2"), + ) + pool2.posts = [post3, post4, post2] + db.session.add_all( + [ + post1, + post2, + post3, + post4, + pool1, + pool2 + ] + ) + db.session.flush() + verify_unpaged("pool:1 sort:pool", [1, 4, 3]) + verify_unpaged("pool:2 sort:pool", [3, 4, 2]) + verify_unpaged("pool:1 pool:2 sort:pool", [4, 3]) + verify_unpaged("pool:2 pool:1 sort:pool", [3, 4]) + verify_unpaged("sort:pool", [1, 2, 3, 4]) @pytest.mark.parametrize( From 27f94be56d2a0cfe941766589665a9d2a2cd7e20 Mon Sep 17 00:00:00 2001 From: Eva Date: Thu, 3 Apr 2025 01:59:27 +0200 Subject: [PATCH 42/45] server/tests: fix nearby pool posts tests --- .../tests/api/test_post_retrieving.py | 46 ++++--------------- 1 file changed, 8 insertions(+), 38 deletions(-) diff --git a/server/szurubooru/tests/api/test_post_retrieving.py b/server/szurubooru/tests/api/test_post_retrieving.py index aa70f77a..a149973a 100644 --- a/server/szurubooru/tests/api/test_post_retrieving.py +++ b/server/szurubooru/tests/api/test_post_retrieving.py @@ -127,7 +127,8 @@ def test_trying_to_retrieve_single_without_privileges( {"post_id": 999}, ) -def test_get_pool_post_around(user_factory, post_factory, pool_factory, pool_post_factory, context_factory): + +def test_get_pool_post_around(user_factory, post_factory, pool_factory, pool_post_factory): p1 = post_factory(id=1) p2 = post_factory(id=2) p3 = post_factory(id=3) @@ -139,42 +140,11 @@ def test_get_pool_post_around(user_factory, post_factory, pool_factory, pool_pos pool_posts = [pool_post_factory(pool=pool, post=p1), pool_post_factory(pool=pool, post=p2), pool_post_factory(pool=pool, post=p3)] db.session.add_all(pool_posts) - result = api.post_api.get_pools_around(context_factory(user=user_factory(rank=model.User.RANK_REGULAR)), {"post_id": 2}) - assert result[0]["previousPost"]["id"] == 1 and result[0]["nextPost"]["id"] == 3 + result = posts.get_pool_posts_nearby(p1, pool) + assert result["previousPost"] == None and result["nextPost"]["id"] == 2 -def test_get_pool_post_around_start(user_factory, post_factory, pool_factory, pool_post_factory, context_factory): - p1 = post_factory(id=1) - p2 = post_factory(id=2) - p3 = post_factory(id=3) - db.session.add_all([p1, p2, p3]) + result = posts.get_pool_posts_nearby(p2, pool) + assert result["previousPost"]["id"] == 1 and result["nextPost"]["id"] == 3 - pool = pool_factory(id=1) - db.session.add(pool) - - pool_posts = [pool_post_factory(pool=pool, post=p1), pool_post_factory(pool=pool, post=p2), pool_post_factory(pool=pool, post=p3)] - db.session.add_all(pool_posts) - - result = api.post_api.get_pools_around(context_factory(user=user_factory(rank=model.User.RANK_REGULAR)), {"post_id": 1}) - assert result[0]["previousPost"] == None and result[0]["nextPost"]["id"] == 2 - -def test_get_pool_post_around_end(user_factory, post_factory, pool_factory, pool_post_factory, context_factory): - p1 = post_factory(id=1) - p2 = post_factory(id=2) - p3 = post_factory(id=3) - db.session.add_all([p1, p2, p3]) - - pool = pool_factory(id=1) - db.session.add(pool) - - pool_posts = [pool_post_factory(pool=pool, post=p1), pool_post_factory(pool=pool, post=p2), pool_post_factory(pool=pool, post=p3)] - db.session.add_all(pool_posts) - - result = api.post_api.get_pools_around(context_factory(user=user_factory(rank=model.User.RANK_REGULAR)), {"post_id": 3}) - assert result[0]["previousPost"]["id"] == 2 and result[0]["nextPost"] == None - -def test_get_pool_post_around_no_pool(user_factory, post_factory, pool_factory, pool_post_factory, context_factory): - p1 = post_factory(id=1) - db.session.add(p1) - - result = api.post_api.get_pools_around(context_factory(user=user_factory(rank=model.User.RANK_REGULAR)), {"post_id": 1}) - assert result == [] + result = posts.get_pool_posts_nearby(p3, pool) + assert result["previousPost"]["id"] == 2 and result["nextPost"] == None From 09717456152de502d68f0d904029884a761d60ea Mon Sep 17 00:00:00 2001 From: Eva Date: Thu, 3 Apr 2025 02:07:08 +0200 Subject: [PATCH 43/45] server/tests: fix post serialization tests --- server/szurubooru/tests/func/test_posts.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/server/szurubooru/tests/func/test_posts.py b/server/szurubooru/tests/func/test_posts.py index fa1b3bb6..1417da13 100644 --- a/server/szurubooru/tests/func/test_posts.py +++ b/server/szurubooru/tests/func/test_posts.py @@ -105,7 +105,14 @@ def test_serialize_post( pool_category_factory, config_injector, ): - config_injector({"data_url": "http://example.com/", "secret": "test"}) + config_injector({ + "privileges": { + "pools:list": model.User.RANK_REGULAR, + "pools:view": model.User.RANK_REGULAR, + }, + "data_url": "http://example.com/", + "secret": "test" + }) with patch("szurubooru.func.comments.serialize_comment"), patch( "szurubooru.func.users.serialize_micro_user" ), patch("szurubooru.func.posts.files.has"): @@ -249,6 +256,10 @@ def test_serialize_post( "description": "desc", "category": "test-cat1", "postCount": 1, + "firstPost": {"id": 1}, + "lastPost": {"id": 1}, + "previousPost": None, + "nextPost": None, }, { "id": 2, @@ -256,6 +267,10 @@ def test_serialize_post( "description": "desc2", "category": "test-cat2", "postCount": 1, + "firstPost": {"id": 1}, + "lastPost": {"id": 1}, + "previousPost": None, + "nextPost": None, }, ], "user": "post author", From d7ffdb09972819482f7749b44037b946d4bc0e6e Mon Sep 17 00:00:00 2001 From: Eva Date: Thu, 3 Apr 2025 02:48:46 +0200 Subject: [PATCH 44/45] client/css: remove semicolons --- client/css/pool-list-view.styl | 12 ++++++------ client/css/pool-navigator-control.styl | 22 +++++++++++----------- 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/client/css/pool-list-view.styl b/client/css/pool-list-view.styl index af85dff0..bda6c8d9 100644 --- a/client/css/pool-list-view.styl +++ b/client/css/pool-list-view.styl @@ -31,12 +31,12 @@ outline: none border-right: 20px solid transparent &:before - content: ' '; - display: block; - position: relative; - width: 100%; - height: 20px; - bottom: 20px; + content: ' ' + display: block + position: relative + width: 100% + height: 20px + bottom: 20px .thumbnail width: 100% diff --git a/client/css/pool-navigator-control.styl b/client/css/pool-navigator-control.styl index f1ee14f8..1c4d636d 100644 --- a/client/css/pool-navigator-control.styl +++ b/client/css/pool-navigator-control.styl @@ -14,26 +14,26 @@ background: $top-navigation-color .pool-name - flex: 1 1; - text-align: center; - overflow: hidden; - white-space: nowrap; - -o-text-overflow: ellipsis; - text-overflow: ellipsis; + flex: 1 1 + text-align: center + overflow: hidden + white-space: nowrap + -o-text-overflow: ellipsis + text-overflow: ellipsis .first, .last - flex-basis: 1em; + flex-basis: 1em .first, .prev, .next, .last - flex: 0 1; - white-space: nowrap; + flex: 0 1 + white-space: nowrap >span padding-top: 2px padding-bottom: 2px - margin: 0 .25em; + margin: 0 .25em .darktheme .pool-navigator-container .pool-info-wrapper border: 1px solid $top-navigation-color-darktheme - background: $window-color-darktheme \ No newline at end of file + background: $window-color-darktheme From aafcfc33bb58f0bb900f9634340cc47a6decacc3 Mon Sep 17 00:00:00 2001 From: Eva Date: Thu, 3 Apr 2025 03:54:26 +0200 Subject: [PATCH 45/45] client/pools: prioritize loading of first thumbnail --- client/js/util/views.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/js/util/views.js b/client/js/util/views.js index cbaefc11..c9130a44 100644 --- a/client/js/util/views.js +++ b/client/js/util/views.js @@ -40,7 +40,7 @@ function makeRelativeTime(time) { ); } -function makeThumbnail(url, klass) { +function makeThumbnail(url, klass, extraProperties) { return makeElement( "span", url @@ -49,7 +49,7 @@ function makeThumbnail(url, klass) { style: `background-image: url(\'${url}\')`, } : { class: "thumbnail empty" }, - makeElement("img", { alt: "thumbnail", src: url }) + makeElement("img", Object.assign({ alt: "thumbnail", src: url }, extraProperties || {})) ); } @@ -64,7 +64,7 @@ function makePoolThumbnails(posts, postFlow) { let s = ""; for (let i = 0; i < Math.min(3, posts.length); i++) { - s += makeThumbnail(posts.at(i).thumbnailUrl, "thumbnail thumbnail-" + (i+1)); + s += makeThumbnail(posts.at(i).thumbnailUrl, "thumbnail thumbnail-" + (i+1), i === 0 ? {fetchPriority: "high"} : {}); } return s;