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-border-color = $main-color
|
||||||
$post-thumbnail-no-tags-border-color = #F44
|
$post-thumbnail-no-tags-border-color = #F44
|
||||||
$default-tag-category-background-color = $active-tab-background-color
|
$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-background-color = #DFC
|
||||||
$new-tag-text-color = black
|
$new-tag-text-color = black
|
||||||
$implied-tag-background-color = #FFC
|
$implied-tag-background-color = #FFC
|
||||||
|
@ -1,47 +1,100 @@
|
|||||||
@import colors
|
@import colors
|
||||||
|
|
||||||
.pool-list
|
.pool-list
|
||||||
table
|
ul
|
||||||
width: 100%
|
list-style-type: none
|
||||||
border-spacing: 0
|
padding: 0
|
||||||
text-align: left
|
display: flex
|
||||||
line-height: 1.3em
|
align-content: flex-end
|
||||||
tr:hover td
|
flex-wrap: wrap
|
||||||
background: $top-navigation-color
|
margin: 0 -0.25em
|
||||||
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
|
|
||||||
|
|
||||||
.darktheme .pool-list
|
li
|
||||||
table
|
position: relative
|
||||||
tr:hover td
|
flex-grow: 1
|
||||||
background: $top-navigation-color-darktheme
|
margin: 2em 1.5em 2em 1.2em
|
||||||
th
|
display: inline-block
|
||||||
background: $top-navigation-color-darktheme
|
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
|
.pool-list-header
|
||||||
label
|
label
|
||||||
@ -61,3 +114,21 @@
|
|||||||
.darktheme .pool-list-header
|
.darktheme .pool-list-header
|
||||||
.append
|
.append
|
||||||
color: $inactive-link-color-darktheme
|
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><code>feature-time</code></td>
|
||||||
<td>alias of <code>feature-time</code></td>
|
<td>alias of <code>feature-time</code></td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>pool</code></td>
|
||||||
|
<td>pool order, requires pool named token</code></td>
|
||||||
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<div class='pool-delete'>
|
<div class='pool-delete'>
|
||||||
<form>
|
<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'>
|
<ul class='input'>
|
||||||
<li>
|
<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'>
|
<section class='description'>
|
||||||
<hr/>
|
<hr/>
|
||||||
<%= ctx.makeMarkdown(ctx.pool.description || 'This pool has no description yet.') %>
|
<%= 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>
|
</section>
|
||||||
</div>
|
</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) { %>
|
<% if (ctx.response.results.length) { %>
|
||||||
<table>
|
<ul>
|
||||||
<thead>
|
<% for (let pool of ctx.response.results) { %>
|
||||||
<th class='names'>
|
<li data-pool-id='<%= pool.id %>'>
|
||||||
<% if (ctx.parameters.query == 'sort:name' || !ctx.parameters.query) { %>
|
<a class='thumbnail-wrapper' href='<%= ctx.canViewPools ? ctx.formatClientLink("pool", pool.id) : "" %>'>
|
||||||
<a href='<%- ctx.formatClientLink('pools', {query: '-sort:name'}) %>'>Pool name(s)</a>
|
<% if (ctx.canViewPosts) { %>
|
||||||
<% } else { %>
|
<%= ctx.makePoolThumbnails(pool.posts, ctx.postFlow) %>
|
||||||
<a href='<%- ctx.formatClientLink('pools', {query: 'sort:name'}) %>'>Pool name(s)</a>
|
|
||||||
<% } %>
|
<% } %>
|
||||||
</th>
|
</a>
|
||||||
<th class='post-count'>
|
<div class='pool-name'>
|
||||||
<% if (ctx.parameters.query == 'sort:post-count') { %>
|
<%= ctx.makePoolLink(pool.id, false, false, pool, name) %>
|
||||||
<a href='<%- ctx.formatClientLink('pools', {query: '-sort:post-count'}) %>'>Post count</a>
|
</div>
|
||||||
<% } else { %>
|
</li>
|
||||||
<a href='<%- ctx.formatClientLink('pools', {query: 'sort:post-count'}) %>'>Post count</a>
|
<% } %>
|
||||||
<% } %>
|
<%= ctx.makeFlexboxAlign() %>
|
||||||
</th>
|
</ul>
|
||||||
<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>
|
|
||||||
<% } %>
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,6 +52,10 @@
|
|||||||
<div class='content'>
|
<div class='content'>
|
||||||
<div class='post-container'></div>
|
<div class='post-container'></div>
|
||||||
|
|
||||||
|
<% if (ctx.canListPools && ctx.canViewPools) { %>
|
||||||
|
<div class='pool-navigators-container'></div>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
<div class='after-mobile-controls'>
|
<div class='after-mobile-controls'>
|
||||||
<% if (ctx.canCreateComments) { %>
|
<% if (ctx.canCreateComments) { %>
|
||||||
<h2>Add comment</h2>
|
<h2>Add comment</h2>
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
const router = require("../router.js");
|
const router = require("../router.js");
|
||||||
const api = require("../api.js");
|
const api = require("../api.js");
|
||||||
|
const settings = require("../models/settings.js");
|
||||||
const uri = require("../util/uri.js");
|
const uri = require("../util/uri.js");
|
||||||
const PoolList = require("../models/pool_list.js");
|
const PoolList = require("../models/pool_list.js");
|
||||||
const topNavigation = require("../models/top_navigation.js");
|
const topNavigation = require("../models/top_navigation.js");
|
||||||
@ -13,7 +14,6 @@ const EmptyView = require("../views/empty_view.js");
|
|||||||
const fields = [
|
const fields = [
|
||||||
"id",
|
"id",
|
||||||
"names",
|
"names",
|
||||||
"posts",
|
|
||||||
"creationTime",
|
"creationTime",
|
||||||
"postCount",
|
"postCount",
|
||||||
"category",
|
"category",
|
||||||
@ -100,14 +100,21 @@ class PoolListController {
|
|||||||
return uri.formatClientLink("pools", parameters);
|
return uri.formatClientLink("pools", parameters);
|
||||||
},
|
},
|
||||||
requestPage: (offset, limit) => {
|
requestPage: (offset, limit) => {
|
||||||
|
const canEditPosts = api.hasPrivilege("pools:edit") || api.hasPrivilege("pools:edit:posts");
|
||||||
|
const effectiveFields = fields.concat([canEditPosts ? "posts": "postsMicro"]);
|
||||||
return PoolList.search(
|
return PoolList.search(
|
||||||
this._ctx.parameters.query,
|
this._ctx.parameters.query,
|
||||||
offset,
|
offset,
|
||||||
limit,
|
limit,
|
||||||
fields
|
effectiveFields
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
pageRenderer: (pageCtx) => {
|
pageRenderer: (pageCtx) => {
|
||||||
|
Object.assign(pageCtx, {
|
||||||
|
canViewPosts: api.hasPrivilege("posts:view"),
|
||||||
|
canViewPools: api.hasPrivilege("pools:view"),
|
||||||
|
postFlow: settings.get().postFlow,
|
||||||
|
});
|
||||||
return new PoolsPageView(pageCtx);
|
return new PoolsPageView(pageCtx);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -11,6 +11,7 @@ const PostList = require("../models/post_list.js");
|
|||||||
const PostMainView = require("../views/post_main_view.js");
|
const PostMainView = require("../views/post_main_view.js");
|
||||||
const BasePostController = require("./base_post_controller.js");
|
const BasePostController = require("./base_post_controller.js");
|
||||||
const EmptyView = require("../views/empty_view.js");
|
const EmptyView = require("../views/empty_view.js");
|
||||||
|
const PoolNavigatorListControl = require("../controls/pool_navigator_list_control.js");
|
||||||
|
|
||||||
class PostMainController extends BasePostController {
|
class PostMainController extends BasePostController {
|
||||||
constructor(ctx, editMode) {
|
constructor(ctx, editMode) {
|
||||||
@ -26,6 +27,7 @@ class PostMainController extends BasePostController {
|
|||||||
]).then(
|
]).then(
|
||||||
(responses) => {
|
(responses) => {
|
||||||
const [post, aroundResponse] = responses;
|
const [post, aroundResponse] = responses;
|
||||||
|
let aroundPool = null;
|
||||||
|
|
||||||
// remove junk from query, but save it into history so that it can
|
// remove junk from query, but save it into history so that it can
|
||||||
// be still accessed after history navigation / page refresh
|
// be still accessed after history navigation / page refresh
|
||||||
@ -39,23 +41,36 @@ class PostMainController extends BasePostController {
|
|||||||
)
|
)
|
||||||
: uri.formatClientLink("post", ctx.parameters.id);
|
: uri.formatClientLink("post", ctx.parameters.id);
|
||||||
router.replace(url, ctx.state, false);
|
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._post = post;
|
||||||
this._view = new PostMainView({
|
this._view = new PostMainView({
|
||||||
post: post,
|
post: post,
|
||||||
editMode: editMode,
|
editMode: editMode,
|
||||||
prevPostId: aroundResponse.prev
|
prevPostId: aroundPool
|
||||||
? aroundResponse.prev.id
|
? (aroundPool.previousPost ? aroundPool.previousPost.id : null)
|
||||||
: null,
|
: (aroundResponse.prev ? aroundResponse.prev.id : null),
|
||||||
nextPostId: aroundResponse.next
|
nextPostId: aroundPool
|
||||||
? aroundResponse.next.id
|
? (aroundPool.nextPost ? aroundPool.nextPost.id : null)
|
||||||
: null,
|
: (aroundResponse.next ? aroundResponse.next.id : null),
|
||||||
canEditPosts: api.hasPrivilege("posts:edit"),
|
canEditPosts: api.hasPrivilege("posts:edit"),
|
||||||
canDeletePosts: api.hasPrivilege("posts:delete"),
|
canDeletePosts: api.hasPrivilege("posts:delete"),
|
||||||
canFeaturePosts: api.hasPrivilege("posts:feature"),
|
canFeaturePosts: api.hasPrivilege("posts:feature"),
|
||||||
canListComments: api.hasPrivilege("comments:list"),
|
canListComments: api.hasPrivilege("comments:list"),
|
||||||
canCreateComments: api.hasPrivilege("comments:create"),
|
canCreateComments: api.hasPrivilege("comments:create"),
|
||||||
|
canListPools: api.hasPrivilege("pools:list"),
|
||||||
|
canViewPools: api.hasPrivilege("pools:view"),
|
||||||
parameters: parameters,
|
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() {
|
get posts() {
|
||||||
return this._posts;
|
return this._postsMicro || this._posts;
|
||||||
}
|
}
|
||||||
|
|
||||||
get postCount() {
|
get postCount() {
|
||||||
@ -51,6 +51,22 @@ class Pool extends events.EventTarget {
|
|||||||
return this._lastEditTime;
|
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) {
|
set names(value) {
|
||||||
this._names = value;
|
this._names = value;
|
||||||
}
|
}
|
||||||
@ -169,10 +185,15 @@ class Pool extends events.EventTarget {
|
|||||||
_creationTime: response.creationTime,
|
_creationTime: response.creationTime,
|
||||||
_lastEditTime: response.lastEditTime,
|
_lastEditTime: response.lastEditTime,
|
||||||
_postCount: response.postCount || 0,
|
_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]) {
|
for (let obj of [this, this._orig]) {
|
||||||
obj._posts.sync(response.posts);
|
obj._posts.sync(response.posts || []);
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this, map);
|
Object.assign(this, map);
|
||||||
|
@ -40,19 +40,36 @@ function makeRelativeTime(time) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeThumbnail(url) {
|
function makeThumbnail(url, klass, extraProperties) {
|
||||||
return makeElement(
|
return makeElement(
|
||||||
"span",
|
"span",
|
||||||
url
|
url
|
||||||
? {
|
? {
|
||||||
class: "thumbnail",
|
class: klass || "thumbnail",
|
||||||
style: `background-image: url(\'${url}\')`,
|
style: `background-image: url(\'${url}\')`,
|
||||||
}
|
}
|
||||||
: { class: "thumbnail empty" },
|
: { 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) {
|
function makeRadio(options) {
|
||||||
_imbueId(options);
|
_imbueId(options);
|
||||||
return makeElement(
|
return makeElement(
|
||||||
@ -254,7 +271,7 @@ function makePoolLink(id, includeHash, includeCount, pool, name) {
|
|||||||
misc.escapeHtml(text)
|
misc.escapeHtml(text)
|
||||||
)
|
)
|
||||||
: makeElement(
|
: makeElement(
|
||||||
"span",
|
"div",
|
||||||
{ class: misc.makeCssName(category, "pool") },
|
{ class: misc.makeCssName(category, "pool") },
|
||||||
misc.escapeHtml(text)
|
misc.escapeHtml(text)
|
||||||
);
|
);
|
||||||
@ -436,6 +453,7 @@ function getTemplate(templatePath) {
|
|||||||
makeFileSize: makeFileSize,
|
makeFileSize: makeFileSize,
|
||||||
makeMarkdown: makeMarkdown,
|
makeMarkdown: makeMarkdown,
|
||||||
makeThumbnail: makeThumbnail,
|
makeThumbnail: makeThumbnail,
|
||||||
|
makePoolThumbnails: makePoolThumbnails,
|
||||||
makeRadio: makeRadio,
|
makeRadio: makeRadio,
|
||||||
makeCheckbox: makeCheckbox,
|
makeCheckbox: makeCheckbox,
|
||||||
makeSelect: makeSelect,
|
makeSelect: makeSelect,
|
||||||
|
@ -30,7 +30,7 @@ class PoolCreateView extends events.EventTarget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (let node of this._formNode.querySelectorAll(
|
for (let node of this._formNode.querySelectorAll(
|
||||||
"input, select, textarea, posts"
|
"input, select, textarea"
|
||||||
)) {
|
)) {
|
||||||
node.addEventListener("change", (e) => {
|
node.addEventListener("change", (e) => {
|
||||||
this.dispatchEvent(new CustomEvent("change"));
|
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 PostNotesOverlayControl = require("../controls/post_notes_overlay_control.js");
|
||||||
const PostReadonlySidebarControl = require("../controls/post_readonly_sidebar_control.js");
|
const PostReadonlySidebarControl = require("../controls/post_readonly_sidebar_control.js");
|
||||||
const PostEditSidebarControl = require("../controls/post_edit_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 CommentControl = require("../controls/comment_control.js");
|
||||||
const CommentListControl = require("../controls/comment_list_control.js");
|
const CommentListControl = require("../controls/comment_list_control.js");
|
||||||
|
|
||||||
@ -57,6 +58,7 @@ class PostMainView {
|
|||||||
this._installSidebar(ctx);
|
this._installSidebar(ctx);
|
||||||
this._installCommentForm();
|
this._installCommentForm();
|
||||||
this._installComments(ctx.post.comments);
|
this._installComments(ctx.post.comments);
|
||||||
|
this._installPoolNavigators(ctx);
|
||||||
|
|
||||||
const showPreviousImage = () => {
|
const showPreviousImage = () => {
|
||||||
if (ctx.prevPostId) {
|
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() {
|
_installCommentForm() {
|
||||||
const commentFormContainer = document.querySelector(
|
const commentFormContainer = document.querySelector(
|
||||||
"#content-holder .comment-form-container"
|
"#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",
|
"html-minifier": "^3.5.18",
|
||||||
"jimp": "^0.13.0",
|
"jimp": "^0.13.0",
|
||||||
"pretty-error": "^3.0.3",
|
"pretty-error": "^3.0.3",
|
||||||
"stylus": "^0.54.8",
|
"stylus": "^0.59.0",
|
||||||
"terser": "^4.8.1",
|
"terser": "^4.8.1",
|
||||||
"underscore": "^1.12.1",
|
"underscore": "^1.12.1",
|
||||||
"watchify": "^4.0.0",
|
"watchify": "^4.0.0",
|
||||||
"ws": "^7.4.6"
|
"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": {
|
"node_modules/@babel/runtime": {
|
||||||
"version": "7.10.3",
|
"version": "7.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
"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": {
|
"node_modules/available-typed-arrays": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz",
|
||||||
@ -1737,27 +1731,6 @@
|
|||||||
"node": "*"
|
"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": {
|
"node_modules/css-select": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
|
||||||
@ -1795,15 +1768,6 @@
|
|||||||
"url": "https://github.com/sponsors/fb55"
|
"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": {
|
"node_modules/csso": {
|
||||||
"version": "3.5.1",
|
"version": "3.5.1",
|
||||||
"resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz",
|
"resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz",
|
||||||
@ -1830,15 +1794,6 @@
|
|||||||
"ms": "2.0.0"
|
"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": {
|
"node_modules/define-properties": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||||
@ -3676,13 +3631,6 @@
|
|||||||
"url": "https://github.com/sponsors/ljharb"
|
"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": {
|
"node_modules/ripemd160": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
"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": {
|
"node_modules/sax": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||||
@ -3794,19 +3736,6 @@
|
|||||||
"node": ">=0.10.0"
|
"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": {
|
"node_modules/source-map-support": {
|
||||||
"version": "0.4.18",
|
"version": "0.4.18",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
|
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
|
||||||
@ -3816,12 +3745,6 @@
|
|||||||
"source-map": "^0.5.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": {
|
"node_modules/stream-browserify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||||
@ -3912,18 +3835,15 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/stylus": {
|
"node_modules/stylus": {
|
||||||
"version": "0.54.8",
|
"version": "0.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz",
|
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz",
|
||||||
"integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==",
|
"integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"css-parse": "~2.0.0",
|
"@adobe/css-tools": "^4.0.1",
|
||||||
"debug": "~3.1.0",
|
"debug": "^4.3.2",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
"mkdirp": "~1.0.4",
|
|
||||||
"safer-buffer": "^2.1.2",
|
|
||||||
"sax": "~1.2.4",
|
"sax": "~1.2.4",
|
||||||
"semver": "^6.3.0",
|
|
||||||
"source-map": "^0.7.3"
|
"source-map": "^0.7.3"
|
||||||
},
|
},
|
||||||
"bin": {
|
"bin": {
|
||||||
@ -3931,28 +3851,33 @@
|
|||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "*"
|
"node": "*"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://opencollective.com/stylus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/stylus/node_modules/mkdirp": {
|
"node_modules/stylus/node_modules/debug": {
|
||||||
"version": "1.0.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"dependencies": {
|
||||||
"mkdirp": "bin/cmd.js"
|
"ms": "2.1.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=10"
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/stylus/node_modules/semver": {
|
"node_modules/stylus/node_modules/ms": {
|
||||||
"version": "6.3.0",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
"dev": true,
|
"dev": true
|
||||||
"bin": {
|
|
||||||
"semver": "bin/semver.js"
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"node_modules/stylus/node_modules/source-map": {
|
"node_modules/stylus/node_modules/source-map": {
|
||||||
"version": "0.7.3",
|
"version": "0.7.3",
|
||||||
@ -4219,13 +4144,6 @@
|
|||||||
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
|
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
|
||||||
"dev": true
|
"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": {
|
"node_modules/url": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
||||||
@ -4602,6 +4520,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"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": {
|
"@babel/runtime": {
|
||||||
"version": "7.10.3",
|
"version": "7.10.3",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
"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": {
|
"available-typed-arrays": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz",
|
||||||
@ -6246,35 +6164,6 @@
|
|||||||
"randomfill": "^1.0.3"
|
"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": {
|
"css-select": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
|
||||||
@ -6326,12 +6215,6 @@
|
|||||||
"ms": "2.0.0"
|
"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": {
|
"define-properties": {
|
||||||
"version": "1.1.3",
|
"version": "1.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||||
@ -7829,12 +7712,6 @@
|
|||||||
"path-parse": "^1.0.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": {
|
"ripemd160": {
|
||||||
"version": "2.0.2",
|
"version": "2.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
"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",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
"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": {
|
"sax": {
|
||||||
"version": "1.2.4",
|
"version": "1.2.4",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||||
@ -7931,19 +7802,6 @@
|
|||||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||||
"dev": true
|
"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": {
|
"source-map-support": {
|
||||||
"version": "0.4.18",
|
"version": "0.4.18",
|
||||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
|
"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": "^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": {
|
"stream-browserify": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||||
@ -8040,31 +7892,31 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"stylus": {
|
"stylus": {
|
||||||
"version": "0.54.8",
|
"version": "0.59.0",
|
||||||
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz",
|
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz",
|
||||||
"integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==",
|
"integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"requires": {
|
"requires": {
|
||||||
"css-parse": "~2.0.0",
|
"@adobe/css-tools": "^4.0.1",
|
||||||
"debug": "~3.1.0",
|
"debug": "^4.3.2",
|
||||||
"glob": "^7.1.6",
|
"glob": "^7.1.6",
|
||||||
"mkdirp": "~1.0.4",
|
|
||||||
"safer-buffer": "^2.1.2",
|
|
||||||
"sax": "~1.2.4",
|
"sax": "~1.2.4",
|
||||||
"semver": "^6.3.0",
|
|
||||||
"source-map": "^0.7.3"
|
"source-map": "^0.7.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mkdirp": {
|
"debug": {
|
||||||
"version": "1.0.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||||
"dev": true
|
"dev": true,
|
||||||
|
"requires": {
|
||||||
|
"ms": "2.1.2"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"semver": {
|
"ms": {
|
||||||
"version": "6.3.0",
|
"version": "2.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
"source-map": {
|
"source-map": {
|
||||||
@ -8287,12 +8139,6 @@
|
|||||||
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
|
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
|
||||||
"dev": true
|
"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": {
|
"url": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
||||||
|
@ -28,7 +28,7 @@
|
|||||||
"html-minifier": "^3.5.18",
|
"html-minifier": "^3.5.18",
|
||||||
"jimp": "^0.13.0",
|
"jimp": "^0.13.0",
|
||||||
"pretty-error": "^3.0.3",
|
"pretty-error": "^3.0.3",
|
||||||
"stylus": "^0.54.8",
|
"stylus": "^0.59.0",
|
||||||
"terser": "^4.8.1",
|
"terser": "^4.8.1",
|
||||||
"underscore": "^1.12.1",
|
"underscore": "^1.12.1",
|
||||||
"watchify": "^4.0.0",
|
"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.
|
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
|
## Deleting post
|
||||||
- **Request**
|
- **Request**
|
||||||
|
|
||||||
|
@ -5,12 +5,10 @@ from szurubooru import db, errors, model, rest, search
|
|||||||
from szurubooru.func import (
|
from szurubooru.func import (
|
||||||
auth,
|
auth,
|
||||||
favorites,
|
favorites,
|
||||||
mime,
|
|
||||||
posts,
|
posts,
|
||||||
scores,
|
scores,
|
||||||
serialization,
|
serialization,
|
||||||
snapshots,
|
snapshots,
|
||||||
tags,
|
|
||||||
versions,
|
versions,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -108,6 +108,7 @@ class PoolSerializer(serialization.BaseSerializer):
|
|||||||
"lastEditTime": self.serialize_last_edit_time,
|
"lastEditTime": self.serialize_last_edit_time,
|
||||||
"postCount": self.serialize_post_count,
|
"postCount": self.serialize_post_count,
|
||||||
"posts": self.serialize_posts,
|
"posts": self.serialize_posts,
|
||||||
|
"postsMicro": self.serialize_posts_micro,
|
||||||
}
|
}
|
||||||
|
|
||||||
def serialize_id(self) -> Any:
|
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(
|
def serialize_pool(
|
||||||
pool: model.Pool, options: List[str] = []
|
pool: model.Pool, options: List[str] = []
|
||||||
|
@ -1,12 +1,15 @@
|
|||||||
import hmac
|
import hmac
|
||||||
import logging
|
import logging
|
||||||
|
from collections import namedtuple
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from itertools import tee, chain, islice
|
||||||
from typing import Any, Callable, Dict, List, Optional, Tuple
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import sqlalchemy as sa
|
import sqlalchemy as sa
|
||||||
|
|
||||||
from szurubooru import config, db, errors, model, rest
|
from szurubooru import config, db, errors, model, rest
|
||||||
from szurubooru.func import (
|
from szurubooru.func import (
|
||||||
|
auth,
|
||||||
comments,
|
comments,
|
||||||
files,
|
files,
|
||||||
image_hash,
|
image_hash,
|
||||||
@ -15,7 +18,6 @@ from szurubooru.func import (
|
|||||||
pools,
|
pools,
|
||||||
scores,
|
scores,
|
||||||
serialization,
|
serialization,
|
||||||
snapshots,
|
|
||||||
tags,
|
tags,
|
||||||
users,
|
users,
|
||||||
util,
|
util,
|
||||||
@ -96,6 +98,13 @@ FLAG_MAP = {
|
|||||||
model.Post.FLAG_SOUND: "sound",
|
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:
|
def get_post_security_hash(id: int) -> str:
|
||||||
return hmac.new(
|
return hmac.new(
|
||||||
@ -337,8 +346,10 @@ class PostSerializer(serialization.BaseSerializer):
|
|||||||
]
|
]
|
||||||
|
|
||||||
def serialize_pools(self) -> List[Any]:
|
def serialize_pools(self) -> List[Any]:
|
||||||
|
if not auth.has_privilege(self.auth_user, "pools:list"):
|
||||||
|
return []
|
||||||
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(
|
for pool in sorted(
|
||||||
self.post.pools, key=lambda pool: pool.creation_time
|
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:
|
else:
|
||||||
return []
|
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
|
import sqlalchemy as sa
|
||||||
|
|
||||||
@ -114,12 +114,26 @@ def _pool_filter(
|
|||||||
query: SaQuery, criterion: Optional[criteria.BaseCriterion], negated: bool
|
query: SaQuery, criterion: Optional[criteria.BaseCriterion], negated: bool
|
||||||
) -> SaQuery:
|
) -> SaQuery:
|
||||||
assert criterion
|
assert criterion
|
||||||
return search_util.create_subquery_filter(
|
from szurubooru.search.configs import util as search_util
|
||||||
model.Post.post_id,
|
subquery = db.session.query(model.PoolPost.post_id.label("foreign_id"))
|
||||||
model.PoolPost.post_id,
|
subquery = subquery.options(sa.orm.lazyload("*"))
|
||||||
model.PoolPost.pool_id,
|
subquery = search_util.create_num_filter(model.PoolPost.pool_id)(subquery, criterion, False)
|
||||||
search_util.create_num_filter,
|
subquery = subquery.subquery("t")
|
||||||
)(query, criterion, negated)
|
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(
|
def _category_filter(
|
||||||
@ -153,6 +167,7 @@ def _category_filter(
|
|||||||
class PostSearchConfig(BaseSearchConfig):
|
class PostSearchConfig(BaseSearchConfig):
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.user = None # type: Optional[model.User]
|
self.user = None # type: Optional[model.User]
|
||||||
|
self.pool_id = None # type: Optional[int]
|
||||||
|
|
||||||
def on_search_query_parsed(self, search_query: SearchQuery) -> SaQuery:
|
def on_search_query_parsed(self, search_query: SearchQuery) -> SaQuery:
|
||||||
new_special_tokens = []
|
new_special_tokens = []
|
||||||
@ -177,6 +192,10 @@ class PostSearchConfig(BaseSearchConfig):
|
|||||||
else:
|
else:
|
||||||
new_special_tokens.append(token)
|
new_special_tokens.append(token)
|
||||||
search_query.special_tokens = new_special_tokens
|
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:
|
def create_around_query(self) -> SaQuery:
|
||||||
return db.session.query(model.Post).options(sa.orm.lazyload("*"))
|
return db.session.query(model.Post).options(sa.orm.lazyload("*"))
|
||||||
@ -382,7 +401,7 @@ class PostSearchConfig(BaseSearchConfig):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@property
|
@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(
|
return util.unalias_dict(
|
||||||
[
|
[
|
||||||
(
|
(
|
||||||
@ -444,6 +463,10 @@ class PostSearchConfig(BaseSearchConfig):
|
|||||||
["feature-date", "feature-time"],
|
["feature-date", "feature-time"],
|
||||||
(model.Post.last_feature_time, self.SORT_DESC),
|
(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_column: SaColumn,
|
||||||
filter_factory: SaColumn,
|
filter_factory: SaColumn,
|
||||||
subquery_decorator: Callable[[SaQuery], None] = None,
|
subquery_decorator: Callable[[SaQuery], None] = None,
|
||||||
|
order: SaQuery = None,
|
||||||
) -> Filter:
|
) -> Filter:
|
||||||
filter_func = filter_factory(filter_column)
|
filter_func = filter_factory(filter_column)
|
||||||
|
|
||||||
|
@ -181,14 +181,19 @@ class Executor:
|
|||||||
_format_dict_keys(self.config.sort_columns),
|
_format_dict_keys(self.config.sort_columns),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
column, default_order = self.config.sort_columns[
|
entry = self.config.sort_columns[
|
||||||
sort_token.name
|
sort_token.name
|
||||||
]
|
]
|
||||||
order = _get_order(sort_token.order, default_order)
|
if callable(entry):
|
||||||
if order == sort_token.SORT_ASC:
|
order = _get_order(sort_token.order, sort_token.SORT_DESC)
|
||||||
db_query = db_query.order_by(column.asc())
|
db_query = entry(db_query, order)
|
||||||
elif order == sort_token.SORT_DESC:
|
else:
|
||||||
db_query = db_query.order_by(column.desc())
|
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)
|
db_query = self.config.finalize_query(db_query)
|
||||||
return db_query
|
return db_query
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
from typing import Any, Callable
|
from typing import Any, Callable, Union
|
||||||
|
|
||||||
SaColumn = Any
|
SaColumn = Any
|
||||||
SaQuery = Any
|
SaQuery = Any
|
||||||
|
@ -14,6 +14,7 @@ def inject_config(config_injector):
|
|||||||
"privileges": {
|
"privileges": {
|
||||||
"posts:list": model.User.RANK_REGULAR,
|
"posts:list": model.User.RANK_REGULAR,
|
||||||
"posts:view": 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)),
|
context_factory(user=user_factory(rank=model.User.RANK_ANONYMOUS)),
|
||||||
{"post_id": 999},
|
{"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,
|
pool_category_factory,
|
||||||
config_injector,
|
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(
|
with patch("szurubooru.func.comments.serialize_comment"), patch(
|
||||||
"szurubooru.func.users.serialize_micro_user"
|
"szurubooru.func.users.serialize_micro_user"
|
||||||
), patch("szurubooru.func.posts.files.has"):
|
), patch("szurubooru.func.posts.files.has"):
|
||||||
@ -249,6 +256,10 @@ def test_serialize_post(
|
|||||||
"description": "desc",
|
"description": "desc",
|
||||||
"category": "test-cat1",
|
"category": "test-cat1",
|
||||||
"postCount": 1,
|
"postCount": 1,
|
||||||
|
"firstPost": {"id": 1},
|
||||||
|
"lastPost": {"id": 1},
|
||||||
|
"previousPost": None,
|
||||||
|
"nextPost": None,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": 2,
|
"id": 2,
|
||||||
@ -256,6 +267,10 @@ def test_serialize_post(
|
|||||||
"description": "desc2",
|
"description": "desc2",
|
||||||
"category": "test-cat2",
|
"category": "test-cat2",
|
||||||
"postCount": 1,
|
"postCount": 1,
|
||||||
|
"firstPost": {"id": 1},
|
||||||
|
"lastPost": {"id": 1},
|
||||||
|
"previousPost": None,
|
||||||
|
"nextPost": None,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
"user": "post author",
|
"user": "post author",
|
||||||
|
@ -725,6 +725,7 @@ def test_filter_by_feature_date(
|
|||||||
"sort:fav-time",
|
"sort:fav-time",
|
||||||
"sort:feature-date",
|
"sort:feature-date",
|
||||||
"sort:feature-time",
|
"sort:feature-time",
|
||||||
|
"sort:pool",
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_sort_tokens(verify_unpaged, post_factory, input):
|
def test_sort_tokens(verify_unpaged, post_factory, input):
|
||||||
@ -865,6 +866,45 @@ def test_tumbleweed(
|
|||||||
verify_unpaged("-special:tumbleweed", [1, 2, 3])
|
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(
|
@pytest.mark.parametrize(
|
||||||
"input,expected_post_ids",
|
"input,expected_post_ids",
|
||||||
[
|
[
|
||||||
|
Reference in New Issue
Block a user