summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--client/css/comment-control.styl4
-rw-r--r--client/css/core-general.styl4
-rw-r--r--client/css/user-list-view.styl3
-rw-r--r--client/html/post_readonly_sidebar.tpl8
-rw-r--r--client/js/controllers/pool_list_controller.js2
-rw-r--r--client/js/util/views.js12
-rw-r--r--doc/API.md14
-rw-r--r--server/Dockerfile10
-rw-r--r--server/requirements.txt4
-rw-r--r--server/szurubooru/func/net.py2
-rw-r--r--server/szurubooru/search/configs/post_search_config.py29
-rw-r--r--server/szurubooru/tests/search/configs/test_post_search_config.py52
13 files changed, 117 insertions, 29 deletions
diff --git a/README.md b/README.md
index 9307055..6a38501 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@ scrubbing](https://sjp.pwn.pl/sjp/;2527372). It is pronounced as *shoorubooru*.
## Features
- Post content: images (JPG, PNG, GIF, animated GIF), videos (MP4, WEBM), Flash animations
-- Ability to retrieve web video content using [youtube-dl](https://github.com/ytdl-org/youtube-dl)
+- Ability to retrieve web video content using [yt-dlp](https://github.com/yt-dlp/yt-dlp)
- Post comments
- Post notes / annotations, including arbitrary polygons
- Rich JSON REST API ([see documentation](doc/API.md))
diff --git a/client/css/comment-control.styl b/client/css/comment-control.styl
index 0d5959b..21bbf72 100644
--- a/client/css/comment-control.styl
+++ b/client/css/comment-control.styl
@@ -127,6 +127,10 @@ $comment-border-color = #DDD
color: mix($main-color, $inactive-link-color-darktheme)
.comment-content
+ p
+ word-wrap: normal
+ word-break: break-all
+
ul, ol
list-style-position: inside
margin: 1em 0
diff --git a/client/css/core-general.styl b/client/css/core-general.styl
index 7e5883a..d25c5f6 100644
--- a/client/css/core-general.styl
+++ b/client/css/core-general.styl
@@ -300,10 +300,10 @@ a .access-key
background-size: 20px 20px
img
opacity: 0
- width: auto
+ width: 100%
height: 100%
video
- width: auto
+ width: 100%
height: 100%
.flexbox-dummy
diff --git a/client/css/user-list-view.styl b/client/css/user-list-view.styl
index f1569f8..64915b4 100644
--- a/client/css/user-list-view.styl
+++ b/client/css/user-list-view.styl
@@ -21,10 +21,11 @@
.details
font-size: 90%
line-height: 130%
+ .image
+ margin: 0.25em 0.6em 0.25em 0
.thumbnail
width: 3em
height: 3em
- margin: 0.25em 0.6em 0 0
.darktheme .user-list
ul li
diff --git a/client/html/post_readonly_sidebar.tpl b/client/html/post_readonly_sidebar.tpl
index 0f93ae3..60efc26 100644
--- a/client/html/post_readonly_sidebar.tpl
+++ b/client/html/post_readonly_sidebar.tpl
@@ -17,8 +17,8 @@
'video/mp4': 'MPEG-4',
'video/quicktime': 'MOV',
'application/x-shockwave-flash': 'SWF',
- }[ctx.post.mimeType] %>
- </a>
+ }[ctx.post.mimeType] %><!--
+ --></a>
(<%- ctx.post.canvasWidth %>x<%- ctx.post.canvasHeight %>)
<% if (ctx.post.flags.length) { %><!--
--><% if (ctx.post.flags.includes('loop')) { %><i class='fa fa-repeat'></i><% } %><!--
@@ -99,10 +99,10 @@
--><% if (ctx.canListPosts) { %><!--
--><a href='<%- ctx.formatClientLink('posts', {query: ctx.escapeTagName(tag.names[0])}) %>' class='<%= ctx.makeCssName(tag.category, 'tag') %>'><!--
--><% } %><!--
- --><%- ctx.getPrettyName(tag.names[0]) %>&#32;<!--
+ --><%- ctx.getPrettyName(tag.names[0]) %><!--
--><% if (ctx.canListPosts) { %><!--
--></a><!--
- --><% } %><!--
+ --><% } %>&#32;<!--
--><span class='tag-usages' data-pseudo-content='<%- tag.postCount %>'></span><!--
--></li><!--
--><% } %><!--
diff --git a/client/js/controllers/pool_list_controller.js b/client/js/controllers/pool_list_controller.js
index 91d655c..a66f816 100644
--- a/client/js/controllers/pool_list_controller.js
+++ b/client/js/controllers/pool_list_controller.js
@@ -43,6 +43,8 @@ class PoolListController {
this._headerView.addEventListener(
"submit",
(e) => this._evtSubmit(e),
+ );
+ this._headerView.addEventListener(
"navigate",
(e) => this._evtNavigate(e)
);
diff --git a/client/js/util/views.js b/client/js/util/views.js
index 38c98a1..f6280a1 100644
--- a/client/js/util/views.js
+++ b/client/js/util/views.js
@@ -209,13 +209,13 @@ function makePostLink(id, includeHash) {
}
function makeTagLink(name, includeHash, includeCount, tag) {
- const category = tag ? tag.category : "unknown";
+ const category = tag && tag.category ? tag.category : "unknown";
let text = misc.getPrettyName(name);
if (includeHash === true) {
text = "#" + text;
}
if (includeCount === true) {
- text += " (" + (tag ? tag.postCount : 0) + ")";
+ text += " (" + (tag && tag.postCount ? tag.postCount : 0) + ")";
}
return api.hasPrivilege("tags:view")
? makeElement(
@@ -234,15 +234,15 @@ function makeTagLink(name, includeHash, includeCount, tag) {
}
function makePoolLink(id, includeHash, includeCount, pool, name) {
- const category = pool ? pool.category : "unknown";
+ const category = pool && pool.category ? pool.category : "unknown";
let text = misc.getPrettyName(
- name ? name : pool ? pool.names[0] : "unknown"
+ name ? name : pool && pool.names ? pool.names[0] : "pool " + id
);
if (includeHash === true) {
text = "#" + text;
}
if (includeCount === true) {
- text += " (" + (pool ? pool.postCount : 0) + ")";
+ text += " (" + (pool && pool.postCount ? pool.postCount : 0) + ")";
}
return api.hasPrivilege("pools:view")
? makeElement(
@@ -264,7 +264,7 @@ function makeUserLink(user) {
let text = makeThumbnail(user ? user.avatarUrl : null);
text += user && user.name ? misc.escapeHtml(user.name) : "Anonymous";
const link =
- user && api.hasPrivilege("users:view")
+ user && user.name && api.hasPrivilege("users:view")
? makeElement(
"a",
{ href: uri.formatClientLink("user", user.name) },
diff --git a/doc/API.md b/doc/API.md
index 63a50a2..00ee75a 100644
--- a/doc/API.md
+++ b/doc/API.md
@@ -54,7 +54,7 @@
- [Deleting pool category](#deleting-pool-category)
- [Setting default pool category](#setting-default-pool-category)
- Pools
- - [Listing pools](#listing-pool)
+ - [Listing pools](#listing-pools)
- [Creating pool](#creating-pool)
- [Updating pool](#updating-pool)
- [Getting pool](#getting-pool)
@@ -165,9 +165,9 @@ way. The files, however, should be passed as regular fields appended with a
accepts a file named `content`, the client should pass
`{"contentUrl":"http://example.com/file.jpg"}` as a part of the JSON message
body. When creating or updating post content using this method, the server can
-also be configured to employ [youtube-dl](https://github.com/ytdl-org/youtube-dl)
-to download content from popular sites such as youtube, gfycat, etc. Access to
-youtube-dl can be configured with the `'uploads:use_downloader'` permission
+also be configured to employ [yt-dlp](https://github.com/yt-dlp/yt-dlp) to
+download content from popular sites such as youtube, gfycat, etc. Access to
+yt-dlp can be configured with the `'uploads:use_downloader'` permission
Finally, in some cases the user might want to reuse one file between the
requests to save the bandwidth (for example, reverse search + consecutive
@@ -323,7 +323,7 @@ data.
{
"name": <name>,
"color": <color>,
- "order": <order> // optional
+ "order": <order>
}
```
@@ -1389,7 +1389,7 @@ data.
## Creating pool
- **Request**
- `POST /pools/create`
+ `POST /pool`
- **Input**
@@ -2491,7 +2491,7 @@ One file together with its metadata posted to the site.
## Micro post
**Description**
-A [post resource](#post) stripped down to `name` and `thumbnailUrl` fields.
+A [post resource](#post) stripped down to `id` and `thumbnailUrl` fields.
## Note
**Description**
diff --git a/server/Dockerfile b/server/Dockerfile
index 487f192..3e4dadf 100644
--- a/server/Dockerfile
+++ b/server/Dockerfile
@@ -23,15 +23,15 @@ RUN apk --no-cache add \
py3-pillow \
py3-pynacl \
py3-tz \
- py3-pyrfc3339 \
- && pip3 install --no-cache-dir --disable-pip-version-check \
+ py3-pyrfc3339
+RUN pip3 install --no-cache-dir --disable-pip-version-check \
"alembic>=0.8.5" \
"coloredlogs==5.0" \
"pyheif==0.6.1" \
"heif-image-plugin>=0.3.2" \
- youtube_dl \
- "pillow-avif-plugin>=1.1.0" \
- && apk --no-cache del py3-pip
+ yt-dlp \
+ "pillow-avif-plugin~=1.1.0"
+RUN apk --no-cache del py3-pip
COPY ./ /opt/app/
RUN rm -rf /opt/app/szurubooru/tests
diff --git a/server/requirements.txt b/server/requirements.txt
index 16b29ff..ffe18f0 100644
--- a/server/requirements.txt
+++ b/server/requirements.txt
@@ -3,7 +3,7 @@ certifi>=2017.11.5
coloredlogs==5.0
heif-image-plugin==0.3.2
numpy>=1.8.2
-pillow-avif-plugin>=1.1.0
+pillow-avif-plugin~=1.1.0
pillow>=4.3.0
psycopg2-binary>=2.6.1
pyheif==0.6.1
@@ -12,4 +12,4 @@ pyRFC3339>=1.0
pytz>=2018.3
pyyaml>=3.11
SQLAlchemy>=1.0.12, <1.4
-youtube_dl
+yt-dlp
diff --git a/server/szurubooru/func/net.py b/server/szurubooru/func/net.py
index c53a62e..d6aa95e 100644
--- a/server/szurubooru/func/net.py
+++ b/server/szurubooru/func/net.py
@@ -64,7 +64,7 @@ def download(url: str, use_video_downloader: bool = False) -> bytes:
def _get_youtube_dl_content_url(url: str) -> str:
- cmd = ["youtube-dl", "--format", "best", "--no-playlist"]
+ cmd = ["yt-dlp", "--format", "best", "--no-playlist"]
if config.config["user_agent"]:
cmd.extend(["--user-agent", config.config["user_agent"]])
cmd.extend(["--get-url", url])
diff --git a/server/szurubooru/search/configs/post_search_config.py b/server/szurubooru/search/configs/post_search_config.py
index ddc003b..8d4672d 100644
--- a/server/szurubooru/search/configs/post_search_config.py
+++ b/server/szurubooru/search/configs/post_search_config.py
@@ -122,6 +122,34 @@ def _pool_filter(
)(query, criterion, negated)
+def _category_filter(
+ query: SaQuery, criterion: Optional[criteria.BaseCriterion], negated: bool
+) -> SaQuery:
+ assert criterion
+
+ # Step 1. find the id for the category
+ q1 = db.session.query(model.TagCategory.tag_category_id).filter(
+ model.TagCategory.name == criterion.value
+ )
+
+ # Step 2. find the tags with that category
+ q2 = db.session.query(model.Tag.tag_id).filter(
+ model.Tag.category_id.in_(q1)
+ )
+
+ # Step 3. find all posts that have at least one of those tags
+ q3 = db.session.query(model.PostTag.post_id).filter(
+ model.PostTag.tag_id.in_(q2)
+ )
+
+ # Step 4. profit
+ expr = model.Post.post_id.in_(q3)
+ if negated:
+ expr = ~expr
+
+ return query.filter(expr)
+
+
class PostSearchConfig(BaseSearchConfig):
def __init__(self) -> None:
self.user = None # type: Optional[model.User]
@@ -349,6 +377,7 @@ class PostSearchConfig(BaseSearchConfig):
),
),
(["pool"], _pool_filter),
+ (["category"], _category_filter),
]
)
diff --git a/server/szurubooru/tests/search/configs/test_post_search_config.py b/server/szurubooru/tests/search/configs/test_post_search_config.py
index 4fb8191..b86fa27 100644
--- a/server/szurubooru/tests/search/configs/test_post_search_config.py
+++ b/server/szurubooru/tests/search/configs/test_post_search_config.py
@@ -863,3 +863,55 @@ def test_tumbleweed(
db.session.flush()
verify_unpaged("special:tumbleweed", [4])
verify_unpaged("-special:tumbleweed", [1, 2, 3])
+
+
+@pytest.mark.parametrize(
+ "input,expected_post_ids",
+ [
+ ("category:cat1", [1, 2, 3]),
+ ("category:cat2", [3, 4]),
+ ],
+)
+def test_search_by_tag_category(
+ verify_unpaged,
+ post_factory,
+ tag_factory,
+ tag_category_factory,
+ input,
+ expected_post_ids,
+):
+ cat1 = tag_category_factory(name="cat1")
+ cat2 = tag_category_factory(name="cat2")
+ tag1 = tag_factory(names=["t1"], category=cat1)
+ tag2 = tag_factory(names=["t2"], category=cat1)
+ tag3 = tag_factory(names=["t3"], category=cat2)
+
+ post1 = post_factory(id=1)
+ post1.tags.append(tag1)
+
+ post2 = post_factory(id=2)
+ post2.tags.append(tag2)
+
+ post3 = post_factory(id=3)
+ post3.tags.append(tag1)
+ post3.tags.append(tag3)
+
+ post4 = post_factory(id=4)
+ post4.tags.append(tag3)
+
+ post5 = post_factory(id=5)
+
+ db.session.add_all(
+ [
+ tag1,
+ tag2,
+ tag3,
+ post1,
+ post2,
+ post3,
+ post4,
+ post5,
+ ]
+ )
+ db.session.flush()
+ verify_unpaged(input, expected_post_ids)