mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Merge aafcfc33bb
into ee7e9ef2a3
This commit is contained in:
@ -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
|
||||
|
@ -1,47 +1,100 @@
|
||||
@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
|
||||
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: 2em 1.5em 2em 1.2em
|
||||
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: none
|
||||
border-right: 20px solid transparent
|
||||
&:before
|
||||
content: ' '
|
||||
display: block
|
||||
position: relative
|
||||
width: 100%
|
||||
height: 20px
|
||||
bottom: 20px
|
||||
|
||||
.thumbnail
|
||||
width: 100%
|
||||
height: 100%
|
||||
outline-offset: -2px
|
||||
background-size: cover
|
||||
transition: top .1s ease-in-out, right .1s ease-in-out
|
||||
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
|
||||
top: -4px
|
||||
z-index: 30
|
||||
|
||||
.thumbnail-2
|
||||
right: -10px
|
||||
top: -10px
|
||||
z-index: 20
|
||||
|
||||
.thumbnail-3
|
||||
right: -16px
|
||||
top: -16px
|
||||
z-index: 10
|
||||
|
||||
.pool-name
|
||||
color: black
|
||||
font-size: 1em
|
||||
text-align: center
|
||||
a
|
||||
width: 100%
|
||||
display: inline-block
|
||||
|
||||
a:active, a:focus
|
||||
.thumbnail
|
||||
outline: 2px solid $main-color !important
|
||||
|
||||
.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
|
||||
|
||||
.thumbnail-3
|
||||
right: -20px
|
||||
top: -20px
|
||||
|
||||
.pool-list-header
|
||||
label
|
||||
@ -61,3 +114,21 @@
|
||||
.darktheme .pool-list-header
|
||||
.append
|
||||
color: $inactive-link-color-darktheme
|
||||
|
||||
.post-flow
|
||||
ul
|
||||
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
|
||||
outline: 2px solid $post-thumbnail-no-tags-border-color
|
||||
&:hover a, a:active, a:focus
|
||||
.thumbnail
|
||||
outline: 2px solid $main-color !important
|
||||
|
39
client/css/pool-navigator-control.styl
Normal file
39
client/css/pool-navigator-control.styl
Normal file
@ -0,0 +1,39 @@
|
||||
@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 $line-color
|
||||
background: $top-navigation-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
|
||||
white-space: nowrap
|
||||
|
||||
>span
|
||||
padding-top: 2px
|
||||
padding-bottom: 2px
|
||||
margin: 0 .25em
|
||||
|
||||
|
||||
.darktheme .pool-navigator-container .pool-info-wrapper
|
||||
border: 1px solid $top-navigation-color-darktheme
|
||||
background: $window-color-darktheme
|
9
client/css/pool-navigator-list.styl
Normal file
9
client/css/pool-navigator-list.styl
Normal file
@ -0,0 +1,9 @@
|
||||
.pool-navigators>ul
|
||||
list-style-type: none
|
||||
margin: 0
|
||||
padding: 0
|
||||
|
||||
>li
|
||||
margin-bottom: 1em
|
||||
&:last-child
|
||||
margin-bottom: 0
|
@ -329,6 +329,10 @@
|
||||
<td><code>feature-time</code></td>
|
||||
<td>alias of <code>feature-time</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>pool</code></td>
|
||||
<td>pool order, requires pool named token</code></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div class='pool-delete'>
|
||||
<form>
|
||||
<p>This pool has <a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + ctx.pool.id}) %>'><%- ctx.pool.postCount %> post(s)</a>.</p>
|
||||
<p>This pool has <a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + ctx.pool.id + ' -sort:pool'}) %>'><%- ctx.pool.postCount %> post(s)</a>.</p>
|
||||
|
||||
<ul class='input'>
|
||||
<li>
|
||||
|
49
client/html/pool_navigator.tpl
Normal file
49
client/html/pool_navigator.tpl
Normal file
@ -0,0 +1,49 @@
|
||||
<div class='pool-navigator-container'>
|
||||
<div class='pool-info-wrapper'>
|
||||
<span class='first'>
|
||||
<% if (ctx.canViewPosts && ctx.previousPost && ctx.firstPost) { %>
|
||||
<a class='<%- ctx.linkClass %>' href='<%= ctx.getPostUrl(ctx.firstPost.id, ctx.parameters) %>'>
|
||||
<% } %>
|
||||
«
|
||||
<% if (ctx.canViewPosts && ctx.previousPost && ctx.firstPost) { %>
|
||||
</a>
|
||||
<% } %>
|
||||
</span>
|
||||
<span class='prev'>
|
||||
<% if (ctx.canViewPosts && ctx.previousPost) { %>
|
||||
<a class='<%- ctx.linkClass %>' href='<%= ctx.getPostUrl(ctx.previousPost.id, ctx.parameters) %>'>
|
||||
<% } %>
|
||||
‹ prev
|
||||
<% if (ctx.canViewPosts && ctx.previousPost) { %>
|
||||
</a>
|
||||
<% } %>
|
||||
</span>
|
||||
<span class='pool-name'>
|
||||
<% if (ctx.canViewPools) { %>
|
||||
<a class='<%- ctx.linkClass %>' href='<%= ctx.formatClientLink("pool", ctx.pool.id) %>'>
|
||||
<% } %>
|
||||
Pool: <%- ctx.getPrettyName(ctx.pool.names[0]) %>
|
||||
<% if (ctx.canViewPools) { %>
|
||||
</a>
|
||||
<% } %>
|
||||
</span>
|
||||
<span class='next'>
|
||||
<% if (ctx.canViewPosts && ctx.nextPost) { %>
|
||||
<a class='<%- ctx.linkClass %>' href='<%= ctx.getPostUrl(ctx.nextPost.id, ctx.parameters) %>'>
|
||||
<% } %>
|
||||
next ›
|
||||
<% if (ctx.canViewPosts && ctx.nextPost) { %>
|
||||
</a>
|
||||
<% } %>
|
||||
</span>
|
||||
<span class='last'>
|
||||
<% if (ctx.canViewPosts && ctx.nextPost && ctx.lastPost) { %>
|
||||
<a class='<%- ctx.linkClass %>' href='<%= ctx.getPostUrl(ctx.lastPost.id, ctx.parameters) %>'>
|
||||
<% } %>
|
||||
»
|
||||
<% if (ctx.canViewPosts && ctx.nextPost && ctx.lastPost) { %>
|
||||
</a>
|
||||
<% } %>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
4
client/html/pool_navigator_list.tpl
Normal file
4
client/html/pool_navigator_list.tpl
Normal file
@ -0,0 +1,4 @@
|
||||
<div class='pool-navigators'>
|
||||
<ul>
|
||||
</ul>
|
||||
</div>
|
@ -18,6 +18,6 @@
|
||||
<section class='description'>
|
||||
<hr/>
|
||||
<%= ctx.makeMarkdown(ctx.pool.description || 'This pool has no description yet.') %>
|
||||
<p>This pool has <a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + ctx.pool.id}) %>'><%- ctx.pool.postCount %> post(s)</a>.</p>
|
||||
<p>This pool has <a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + ctx.pool.id + ' -sort:pool'}) %>'><%- ctx.pool.postCount %> post(s)</a>.</p>
|
||||
</section>
|
||||
</div>
|
||||
|
@ -1,48 +1,19 @@
|
||||
<div class='pool-list table-wrap'>
|
||||
<% if (ctx.postFlow) { %><div class='pool-list post-flow'><% } else { %><div class='pool-list'><% } %>
|
||||
<% if (ctx.response.results.length) { %>
|
||||
<table>
|
||||
<thead>
|
||||
<th class='names'>
|
||||
<% if (ctx.parameters.query == 'sort:name' || !ctx.parameters.query) { %>
|
||||
<a href='<%- ctx.formatClientLink('pools', {query: '-sort:name'}) %>'>Pool name(s)</a>
|
||||
<% } else { %>
|
||||
<a href='<%- ctx.formatClientLink('pools', {query: 'sort:name'}) %>'>Pool name(s)</a>
|
||||
<ul>
|
||||
<% for (let pool of ctx.response.results) { %>
|
||||
<li data-pool-id='<%= pool.id %>'>
|
||||
<a class='thumbnail-wrapper' href='<%= ctx.canViewPools ? ctx.formatClientLink("pool", pool.id) : "" %>'>
|
||||
<% if (ctx.canViewPosts) { %>
|
||||
<%= ctx.makePoolThumbnails(pool.posts, ctx.postFlow) %>
|
||||
<% } %>
|
||||
</th>
|
||||
<th class='post-count'>
|
||||
<% if (ctx.parameters.query == 'sort:post-count') { %>
|
||||
<a href='<%- ctx.formatClientLink('pools', {query: '-sort:post-count'}) %>'>Post count</a>
|
||||
<% } else { %>
|
||||
<a href='<%- ctx.formatClientLink('pools', {query: 'sort:post-count'}) %>'>Post count</a>
|
||||
<% } %>
|
||||
</th>
|
||||
<th class='creation-time'>
|
||||
<% if (ctx.parameters.query == 'sort:creation-time') { %>
|
||||
<a href='<%- ctx.formatClientLink('pools', {query: '-sort:creation-time'}) %>'>Created on</a>
|
||||
<% } else { %>
|
||||
<a href='<%- ctx.formatClientLink('pools', {query: 'sort:creation-time'}) %>'>Created on</a>
|
||||
<% } %>
|
||||
</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% for (let pool of ctx.response.results) { %>
|
||||
<tr>
|
||||
<td class='names'>
|
||||
<ul>
|
||||
<% for (let name of pool.names) { %>
|
||||
<li><%= ctx.makePoolLink(pool.id, false, false, pool, name) %></li>
|
||||
<% } %>
|
||||
</ul>
|
||||
</td>
|
||||
<td class='post-count'>
|
||||
<a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + pool.id}) %>'><%- pool.postCount %></a>
|
||||
</td>
|
||||
<td class='creation-time'>
|
||||
<%= ctx.makeRelativeTime(pool.creationTime) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% } %>
|
||||
</tbody>
|
||||
</table>
|
||||
</a>
|
||||
<div class='pool-name'>
|
||||
<%= ctx.makePoolLink(pool.id, false, false, pool, name) %>
|
||||
</div>
|
||||
</li>
|
||||
<% } %>
|
||||
<%= ctx.makeFlexboxAlign() %>
|
||||
</ul>
|
||||
<% } %>
|
||||
</div>
|
||||
|
@ -52,6 +52,10 @@
|
||||
<div class='content'>
|
||||
<div class='post-container'></div>
|
||||
|
||||
<% if (ctx.canListPools && ctx.canViewPools) { %>
|
||||
<div class='pool-navigators-container'></div>
|
||||
<% } %>
|
||||
|
||||
<div class='after-mobile-controls'>
|
||||
<% if (ctx.canCreateComments) { %>
|
||||
<h2>Add comment</h2>
|
||||
|
@ -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");
|
||||
@ -13,7 +14,6 @@ const EmptyView = require("../views/empty_view.js");
|
||||
const fields = [
|
||||
"id",
|
||||
"names",
|
||||
"posts",
|
||||
"creationTime",
|
||||
"postCount",
|
||||
"category",
|
||||
@ -100,14 +100,21 @@ 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) => {
|
||||
Object.assign(pageCtx, {
|
||||
canViewPosts: api.hasPrivilege("posts:view"),
|
||||
canViewPools: api.hasPrivilege("pools:view"),
|
||||
postFlow: settings.get().postFlow,
|
||||
});
|
||||
return new PoolsPageView(pageCtx);
|
||||
},
|
||||
});
|
||||
|
@ -11,6 +11,7 @@ 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) {
|
||||
@ -26,6 +27,7 @@ class PostMainController extends BasePostController {
|
||||
]).then(
|
||||
(responses) => {
|
||||
const [post, aroundResponse] = responses;
|
||||
let aroundPool = null;
|
||||
|
||||
// remove junk from query, but save it into history so that it can
|
||||
// be still accessed after history navigation / page refresh
|
||||
@ -39,23 +41,36 @@ class PostMainController extends BasePostController {
|
||||
)
|
||||
: uri.formatClientLink("post", ctx.parameters.id);
|
||||
router.replace(url, ctx.state, false);
|
||||
misc.splitByWhitespace(parameters.query).forEach((item) => {
|
||||
const found = item.match(/^pool:([0-9]+)/i);
|
||||
if (found) {
|
||||
const activePool = parseInt(found[1]);
|
||||
post.pools.map((pool) => {
|
||||
if (pool.id == activePool) {
|
||||
aroundPool = pool;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this._post = post;
|
||||
this._view = new PostMainView({
|
||||
post: post,
|
||||
editMode: editMode,
|
||||
prevPostId: aroundResponse.prev
|
||||
? aroundResponse.prev.id
|
||||
: null,
|
||||
nextPostId: aroundResponse.next
|
||||
? aroundResponse.next.id
|
||||
: null,
|
||||
prevPostId: aroundPool
|
||||
? (aroundPool.previousPost ? aroundPool.previousPost.id : null)
|
||||
: (aroundResponse.prev ? aroundResponse.prev.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"),
|
||||
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,
|
||||
});
|
||||
|
||||
|
34
client/js/controls/pool_navigator_control.js
Normal file
34
client/js/controls/pool_navigator_control.js
Normal file
@ -0,0 +1,34 @@
|
||||
"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,
|
||||
parameters: { query: `pool:${poolPostNearby.id}` },
|
||||
linkClass: misc.makeCssName(poolPostNearby.category, "pool"),
|
||||
canViewPosts: api.hasPrivilege("posts:view"),
|
||||
canViewPools: api.hasPrivilege("pools:view"),
|
||||
firstPost: poolPostNearby.firstPost,
|
||||
previousPost: poolPostNearby.previousPost,
|
||||
nextPost: poolPostNearby.nextPost,
|
||||
lastPost: poolPostNearby.lastPost,
|
||||
getPrettyName: misc.getPrettyName,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PoolNavigatorControl;
|
49
client/js/controls/pool_navigator_list_control.js
Normal file
49
client/js/controls/pool_navigator_list_control.js
Normal file
@ -0,0 +1,49 @@
|
||||
"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 (const entry of this._poolPostNearby) {
|
||||
this._installPoolNavigatorNode(entry);
|
||||
}
|
||||
}
|
||||
|
||||
get _poolNavigatorListNode() {
|
||||
return this._hostNode;
|
||||
}
|
||||
|
||||
_installPoolNavigatorNode(poolPostNearby) {
|
||||
const poolListItemNode = document.createElement("div");
|
||||
const poolControl = new PoolNavigatorControl(
|
||||
poolListItemNode,
|
||||
poolPostNearby,
|
||||
);
|
||||
this._indexToNode[poolPostNearby.id] = poolListItemNode;
|
||||
this._poolNavigatorListNode.appendChild(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;
|
@ -36,7 +36,7 @@ class Pool extends events.EventTarget {
|
||||
}
|
||||
|
||||
get posts() {
|
||||
return this._posts;
|
||||
return this._postsMicro || this._posts;
|
||||
}
|
||||
|
||||
get postCount() {
|
||||
@ -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,10 +185,15 @@ 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,
|
||||
_nextPost: response.nextPost || null,
|
||||
};
|
||||
|
||||
for (let obj of [this, this._orig]) {
|
||||
obj._posts.sync(response.posts);
|
||||
obj._posts.sync(response.posts || []);
|
||||
}
|
||||
|
||||
Object.assign(this, map);
|
||||
|
@ -40,19 +40,36 @@ function makeRelativeTime(time) {
|
||||
);
|
||||
}
|
||||
|
||||
function makeThumbnail(url) {
|
||||
function makeThumbnail(url, klass, extraProperties) {
|
||||
return makeElement(
|
||||
"span",
|
||||
url
|
||||
? {
|
||||
class: "thumbnail",
|
||||
class: klass || "thumbnail",
|
||||
style: `background-image: url(\'${url}\')`,
|
||||
}
|
||||
: { class: "thumbnail empty" },
|
||||
makeElement("img", { alt: "thumbnail", src: url })
|
||||
makeElement("img", Object.assign({ alt: "thumbnail", src: url }, extraProperties || {}))
|
||||
);
|
||||
}
|
||||
|
||||
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), i === 0 ? {fetchPriority: "high"} : {});
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -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"));
|
||||
|
@ -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");
|
||||
|
||||
@ -57,6 +58,7 @@ class PostMainView {
|
||||
this._installSidebar(ctx);
|
||||
this._installCommentForm();
|
||||
this._installComments(ctx.post.comments);
|
||||
this._installPoolNavigators(ctx);
|
||||
|
||||
const showPreviousImage = () => {
|
||||
if (ctx.prevPostId) {
|
||||
@ -137,6 +139,20 @@ class PostMainView {
|
||||
}
|
||||
}
|
||||
|
||||
_installPoolNavigators(ctx) {
|
||||
const poolNavigatorsContainerNode = document.querySelector(
|
||||
"#content-holder .pool-navigators-container"
|
||||
);
|
||||
if (!poolNavigatorsContainerNode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.poolNavigatorsControl = new PoolNavigatorListControl(
|
||||
poolNavigatorsContainerNode,
|
||||
ctx.post.pools,
|
||||
);
|
||||
}
|
||||
|
||||
_installCommentForm() {
|
||||
const commentFormContainer = document.querySelector(
|
||||
"#content-holder .comment-form-container"
|
||||
|
264
client/package-lock.json
generated
264
client/package-lock.json
generated
@ -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",
|
||||
|
@ -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",
|
||||
|
37
doc/API.md
37
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/<id>/pools-nearby`
|
||||
|
||||
- **Output**
|
||||
|
||||
```json5
|
||||
[
|
||||
{
|
||||
"pool": <pool>,
|
||||
"firstPost": <first-post>,
|
||||
"lastPost": <last-post>,
|
||||
"nextPost": <next-post>,
|
||||
"previousPost": <previous-post>
|
||||
},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
- **Field meaning**
|
||||
|
||||
- `<pool>`: The associated [micro pool resource](#micro-pool).
|
||||
- `<first-post>`: A [micro post resource](#micro-post) that displays the first post in the pool.
|
||||
- `<last-post>`: A [micro post resource](#micro-post) that displays the last post in the pool.
|
||||
- `<next-post>`: A [micro post resource](#micro-post) that displays the next post in the pool.
|
||||
- `<previous-post>`: 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**
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
|
||||
|
@ -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] = []
|
||||
|
@ -1,12 +1,15 @@
|
||||
import hmac
|
||||
import logging
|
||||
from collections import namedtuple
|
||||
from datetime import datetime
|
||||
from itertools import tee, chain, islice
|
||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
from szurubooru import config, db, errors, model, rest
|
||||
from szurubooru.func import (
|
||||
auth,
|
||||
comments,
|
||||
files,
|
||||
image_hash,
|
||||
@ -15,7 +18,6 @@ from szurubooru.func import (
|
||||
pools,
|
||||
scores,
|
||||
serialization,
|
||||
snapshots,
|
||||
tags,
|
||||
users,
|
||||
util,
|
||||
@ -96,6 +98,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 zip(previous_item, current_item, next_item)
|
||||
|
||||
|
||||
def get_post_security_hash(id: int) -> str:
|
||||
return hmac.new(
|
||||
@ -337,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
|
||||
)
|
||||
@ -968,3 +979,38 @@ def search_by_image(image_content: bytes) -> List[Tuple[float, model.Post]]:
|
||||
]
|
||||
else:
|
||||
return []
|
||||
|
||||
def serialize_safe_post(
|
||||
post: Optional[model.Post]
|
||||
) -> rest.Response:
|
||||
return {"id": getattr(post, "post_id", None)} if post else None
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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),
|
||||
}
|
||||
|
@ -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,26 @@ 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], order: str
|
||||
) -> SaQuery:
|
||||
if pool_id is None:
|
||||
return query
|
||||
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(
|
||||
@ -153,6 +167,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 +192,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 +401,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 +463,10 @@ class PostSearchConfig(BaseSearchConfig):
|
||||
["feature-date", "feature-time"],
|
||||
(model.Post.last_feature_time, self.SORT_DESC),
|
||||
),
|
||||
(
|
||||
["pool"],
|
||||
lambda subquery, order: _pool_sort(subquery, self.pool_id, order)
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -181,14 +181,19 @@ 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):
|
||||
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)
|
||||
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
|
||||
|
@ -1,4 +1,4 @@
|
||||
from typing import Any, Callable
|
||||
from typing import Any, Callable, Union
|
||||
|
||||
SaColumn = Any
|
||||
SaQuery = Any
|
||||
|
@ -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,
|
||||
},
|
||||
}
|
||||
)
|
||||
@ -125,3 +126,25 @@ 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):
|
||||
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 = posts.get_pool_posts_nearby(p1, pool)
|
||||
assert result["previousPost"] == None and result["nextPost"]["id"] == 2
|
||||
|
||||
result = posts.get_pool_posts_nearby(p2, pool)
|
||||
assert result["previousPost"]["id"] == 1 and result["nextPost"]["id"] == 3
|
||||
|
||||
result = posts.get_pool_posts_nearby(p3, pool)
|
||||
assert result["previousPost"]["id"] == 2 and result["nextPost"] == None
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
[
|
||||
|
Reference in New Issue
Block a user