This commit is contained in:
Eva
2025-04-01 08:12:41 +00:00
committed by GitHub
43 changed files with 266 additions and 287 deletions

View File

@ -4,6 +4,9 @@ $comment-header-background-color-darktheme = $top-navigation-color-darktheme
$comment-border-color = #DDD
.comments-container
margin-top: 2em
.comment-container
padding: 0 0 0 60px
@ -16,7 +19,13 @@ $comment-border-color = #DDD
width: 40px
height: 40px
a
outline: none
display: inline-block
position: relative
top: -2px
border: 2px solid transparent
&:focus
border: 2px solid $main-color
nav:not(.active), .tab:not(.active)
display: none
@ -114,17 +123,30 @@ $comment-border-color = #DDD
.messages
margin: 1em 0
.darktheme .comment-container .comment header
background: $comment-header-background-color-darktheme
nav.edit
ul
li
&.active
background: $window-color-darktheme
border-bottom: 1px solid $window-color-darktheme
.edit, .delete, .score-container a, .nickname a
&:not(.inactive)
color: mix($main-color, $inactive-link-color-darktheme)
.darktheme .comment-container
.comment
border: 1px solid $comment-header-background-color-darktheme
header
background: $comment-header-background-color-darktheme
border-bottom: 1px solid $comment-header-background-color-darktheme
nav.edit
ul
li
&.active
background: $window-color-darktheme
border: 1px solid $window-color-darktheme
.edit, .delete, .score-container a, .nickname a
&:not(.inactive)
color: mix($main-color, $inactive-link-color-darktheme)
&:before
border-right: 0
&:after
border-right: 0.75em solid $comment-header-background-color-darktheme
.comment-content
p

View File

@ -125,6 +125,9 @@ input[type=radio]:checked + .radio:after,
input[type=checkbox]:checked + .checkbox:after
opacity: 1
input[type=radio]:disabled + .radio:after
background: $input-disabled-text-color
input[type=radio]:disabled + .radio:before,
input[type=checkbox]:disabled + .checkbox:before,
input[type=radio]:disabled + .radio:after,
@ -203,6 +206,7 @@ input[type=number]
background: $input-enabled-background-color
color: $input-enabled-text-color
box-shadow: none /* :-moz-submit-invalid on FF */
outline: none
transition: border-color 0.1s linear, background-color 0.1s linear
&:disabled
@ -211,7 +215,7 @@ input[type=number]
color: $input-disabled-text-color
&:focus
border-color: $main-color
border-color: $main-color !important
&[readonly]
border: 2px solid $input-disabled-border-color

View File

@ -106,7 +106,12 @@ form .fa-question-circle-o
background-color: $scrollbar-bg-color
&::-webkit-scrollbar-thumb
background-color: $scrollbar-thumb-color
>.content-wrapper:not(.transparent)
li[data-name=view]
background: $button-enabled-background-color
margin-right: 1em
a
color: $button-enabled-text-color
>.content-wrapper:not(.transparent-container)
background: $top-navigation-color
padding: 1.8em
@media (max-width: 1000px)
@ -117,7 +122,7 @@ form .fa-question-circle-o
margin-bottom: 0
.darktheme #content-holder
>.content-wrapper:not(.transparent)
>.content-wrapper:not(.transparent-container)
background: $top-navigation-color-darktheme
hr
@ -161,6 +166,8 @@ nav
li.active a
background: $active-tab-background-color
color: $active-tab-text-color
li.active:has(:focus)
background: $active-tab-background-color
:focus
background: $focused-tab-background-color
outline: 0
@ -236,6 +243,8 @@ nav
li.active a
background: $active-tab-background-color-darktheme
color: $active-tab-text-color-darktheme
li.active:has(:focus)
background: $active-tab-background-color-darktheme
:focus
background: $focused-tab-background-color-darktheme
&#top-navigation

View File

@ -61,7 +61,10 @@
word-spacing: 1.1em
background-repeat: no-repeat
background-position: 50% 50%
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12'><circle cx='6' cy='6' r='2' fill='%23000000'/></svg>")
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12'><circle cx='6' cy='6' r='2' fill='%23111111'/></svg>")
.thumbnail
margin-right: 0.4em
.darktheme #home footer ul .sep
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12'><circle cx='6' cy='6' r='2' fill='%23e6e6e6'/></svg>")

View File

@ -18,6 +18,8 @@
right: 0
top: 0
bottom: 0
&[data-state=read-only]
pointer-events: none
.notes-overlay
g

View File

@ -145,6 +145,7 @@ $cancel-button-color = tomato
clear: both
margin: 1em 0 0 0
padding-left: 7em
padding-bottom: 1em;
font-size: 90%
.thumbnail-wrapper

View File

@ -34,6 +34,16 @@ shortcuts:</p>
<td>Focus first post in post list</td>
</tr>
<tr>
<td><kbd>T</kbd></td>
<td>(In edit mode) Focus tag input</td>
</tr>
<tr>
<td><kbd>Command/Ctrl+S</kbd></td>
<td>(In edit mode) Save post</td>
</tr>
<tr>
<td><kbd>Delete</kbd></td>
<td>Delete post (while in edit mode)</td>

View File

@ -1,4 +1,4 @@
<div class='content-wrapper transparent' id='home'>
<div class='content-wrapper transparent-container' id='home'>
<div class='messages'></div>
<header>
<h1><%- ctx.name %></h1>

View File

@ -2,6 +2,7 @@
<h1><%- ctx.getPrettyName(ctx.pool.names[0]) %></h1>
<nav class='buttons'><!--
--><ul><!--
--><li data-name='view'><a href='<%- ctx.formatClientLink('posts', {query: 'pool:' + ctx.pool.id}) %>'>View</a></li><!--
--><li data-name='summary'><a href='<%- ctx.formatClientLink('pool', ctx.pool.id) %>'>Summary</a></li><!--
--><% if (ctx.canEditAnything) { %><!--
--><li data-name='edit'><a href='<%- ctx.formatClientLink('pool', ctx.pool.id, 'edit') %>'>Edit</a></li><!--

View File

@ -1,4 +1,10 @@
<div class='content-wrapper pool-summary'>
<section class='description'>
<%= 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>
<hr/>
</section>
<section class='details'>
<section>
Category:
@ -14,10 +20,4 @@
--></ul>
</section>
</section>
<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>
</section>
</div>

View File

@ -1,7 +1,7 @@
<div class='post-content post-type-<%- ctx.post.type %>'>
<% if (['image', 'animation'].includes(ctx.post.type)) { %>
<img class='resize-listener' alt='' src='<%- ctx.post.contentUrl %>'/>
<img class='resize-listener' alt='' src='<%- ctx.post.contentUrl %>' draggable='false'/>
<% } else if (ctx.post.type === 'flash') { %>

View File

@ -1,4 +1,4 @@
<div class='content-wrapper transparent post-view'>
<div class='content-wrapper transparent-container post-view'>
<aside class='sidebar'>
<nav class='buttons'>
<article class='previous-post'>

View File

@ -1,7 +1,7 @@
<div class='readonly-sidebar'>
<article class='details'>
<section class='download'>
<a rel='external' href='<%- ctx.post.contentUrl %>'>
<a href='<%- ctx.post.contentUrl %>' download>
<i class='fa fa-download'></i><!--
--><%= ctx.makeFileSize(ctx.post.fileSize) %> <!--
--><%- {

View File

@ -85,7 +85,7 @@
<div class='controls'>
<%= ctx.makeCheckbox({text: 'Copy tags', name: 'copy-tags'}) %>
<br/>
<%= ctx.makeCheckbox({text: 'Add relation', name: 'add-relation'}) %>
<%= ctx.makeCheckbox({text: 'Add relation', name: 'add-relation', checked: true}) %>
</div>
</li>
<% } %>

View File

@ -10,28 +10,27 @@
<span class='type' data-type='<%- post.type %>'>
<% if (post.type == 'video' || post.type == 'flash' || post.type == 'animation') { %>
<span class='icon'><i class='fa fa-film'></i></span>
<% } else { %>
<%- post.type %>
<% } %>
<%- post.type %>
</span>
<% if (post.score || post.favoriteCount || post.commentCount) { %>
<span class='stats'>
<% if (post.score) { %>
<span class='icon'>
<i class='fa fa-thumbs-up'></i>
<%- post.score %>
<span style="display: none">score:</span><%- post.score %>
</span>
<% } %>
<% if (post.favoriteCount) { %>
<span class='icon'>
<i class='fa fa-heart'></i>
<%- post.favoriteCount %>
<span style="display: none">favorites:</span><%- post.favoriteCount %>
</span>
<% } %>
<% if (post.commentCount) { %>
<span class='icon'>
<i class='fa fa-commenting'></i>
<%- post.commentCount %>
<span style="display: none">comments:</span><%- post.commentCount %>
</span>
<% } %>
</span>

View File

@ -28,7 +28,6 @@
name: 'dark-theme',
checked: ctx.browsingSettings.darkTheme,
}) %>
<p class='hint'>Changing this setting will require you to refresh the page for it to apply.</p>
</li>
<li>

View File

@ -12,7 +12,7 @@
<%= item.operation %>
<%= ctx.makeResourceLink(item.type, item.id) %>
<%= ctx.makeResourceLink(item.type, item.id, item.data) %>
</div>
<div class='details'><!--

View File

@ -2,6 +2,7 @@
<h1><%- ctx.getPrettyName(ctx.tag.names[0]) %></h1>
<nav class='buttons'><!--
--><ul><!--
--><li data-name='view'><a href='<%- ctx.formatClientLink('posts', {query: ctx.escapeTagName(ctx.tag.names[0])}) %>'>View</a></li><!--
--><li data-name='summary'><a href='<%- ctx.formatClientLink('tag', ctx.tag.names[0]) %>'>Summary</a></li><!--
--><% if (ctx.canEditAnything) { %><!--
--><li data-name='edit'><a href='<%- ctx.formatClientLink('tag', ctx.tag.names[0], 'edit') %>'>Edit</a></li><!--

View File

@ -1,4 +1,10 @@
<div class='content-wrapper tag-summary'>
<section class='description'>
<%= ctx.makeMarkdown(ctx.tag.description || 'This tag has no description yet.') %>
<p>This tag has <a href='<%- ctx.formatClientLink('posts', {query: ctx.escapeTagName(ctx.tag.names[0])}) %>'><%- ctx.tag.postCount %> usage(s)</a>.</p>
<hr/>
</section>
<section class='details'>
<section>
Category:
@ -32,10 +38,4 @@
--></ul>
</section>
</section>
<section class='description'>
<hr/>
<%= ctx.makeMarkdown(ctx.tag.description || 'This tag has no description yet.') %>
<p>This tag has <a href='<%- ctx.formatClientLink('posts', {query: ctx.escapeTagName(ctx.tag.names[0])}) %>'><%- ctx.tag.postCount %> usage(s)</a>.</p>
</section>
</div>

View File

@ -71,7 +71,7 @@
<% } %>
</td>
<td class='usages'>
<%- tag.postCount %>
<a href='<%- ctx.formatClientLink('posts', {query: ctx.escapeTagName(tag.names[0])}) %>'><%- tag.postCount %></a>
</td>
<td class='creation-time'>
<%= ctx.makeRelativeTime(tag.creationTime) %>

View File

@ -5,9 +5,11 @@ const request = require("superagent");
const events = require("./events.js");
const progress = require("./util/progress.js");
const uri = require("./util/uri.js");
const Info = require("../models/info.js");
let fileTokens = {};
let remoteConfig = null;
let remoteConfigPromise = null;
class Api extends events.EventTarget {
constructor() {
@ -68,9 +70,13 @@ class Api extends events.EventTarget {
fetchConfig() {
if (remoteConfig === null) {
return this.get(uri.formatApiLink("info")).then((response) => {
if (remoteConfigPromise !== null) {
return Promise.resolve(remoteConfigPromise);
}
remoteConfigPromise = Info.get().then((response) => {
remoteConfig = response.config;
});
return remoteConfigPromise;
} else {
return Promise.resolve();
}
@ -398,7 +404,7 @@ class Api extends events.EventTarget {
if (data) {
if (files && Object.keys(files).length) {
req.attach("metadata", new Blob([JSON.stringify(data)]));
req.attach("metadata", new Blob([JSON.stringify(data)], { type: "application/json" }));
} else {
req.set("Content-Type", "application/json");
req.send(data);

View File

@ -2,7 +2,6 @@
const api = require("../api.js");
const config = require("../config.js");
const Info = require("../models/info.js");
const topNavigation = require("../models/top_navigation.js");
const HomeView = require("../views/home_view.js");
@ -20,7 +19,7 @@ class HomeController {
isDevelopmentMode: config.environment == "development",
});
Info.get().then(
api.fetchConfig().then(
(info) => {
this._homeView.setStats({
diskUsage: info.diskUsage,

View File

@ -281,7 +281,16 @@ module.exports = (router) => {
if (ctx.state.parameters) {
Object.assign(ctx.parameters, ctx.state.parameters);
}
ctx.controller = new PostMainController(ctx, true);
const canEditPosts = api.hasPrivilege("posts:edit");
if (canEditPosts) {
ctx.controller = new PostMainController(ctx, true);
} else {
router.show(uri.formatClientLink(
"post",
ctx.parameters.id,
ctx.parameters ? { query: ctx.parameters.query } : {}
));
}
});
router.enter(["post", ":id"], (ctx, next) => {
// restore parameters from history state

View File

@ -4,6 +4,7 @@ const api = require("../api.js");
const events = require("../events.js");
const misc = require("../util/misc.js");
const views = require("../util/views.js");
const keyboard = require("../util/keyboard.js");
const Note = require("../models/note.js");
const Point = require("../models/point.js");
const TagInputControl = require("./tag_input_control.js");
@ -224,10 +225,12 @@ class PostEditSidebarControl extends events.EventTarget {
});
}
this._tagControl.addEventListener("change", (e) => {
this.dispatchEvent(new CustomEvent("change"));
this._syncExpanderTitles();
});
if (this._tagControl) {
this._tagControl.addEventListener("change", (e) => {
this.dispatchEvent(new CustomEvent("change"));
this._syncExpanderTitles();
});
}
if (this._noteTextareaNode) {
this._noteTextareaNode.addEventListener("change", (e) =>
@ -241,6 +244,16 @@ class PostEditSidebarControl extends events.EventTarget {
this._syncExpanderTitles();
});
}
keyboard.bind(["command+s", "ctrl+s"], (e) => this._evtSubmit(e));
if (this._tagInputNode) {
const realTagInput = this._formNode.querySelector(".tag-input input");
keyboard.bindElement(realTagInput, ["command+s", "ctrl+s"], (e) => this._evtSubmit(e));
keyboard.bind("t", (e) => {
e.preventDefault();
realTagInput.focus();
});
}
}
_syncExpanderTitles() {

View File

@ -42,6 +42,10 @@ const pools = require("./pools.js");
const api = require("./api.js");
const settings = require("./models/settings.js");
if (settings.get().darkTheme) {
document.body.classList.add("darktheme");
}
Promise.resolve()
.then(() => api.fetchConfig())
.then(

View File

@ -75,7 +75,7 @@ class Post extends events.EventTarget {
}
get sourceSplit() {
return this._source.split("\n");
return this._source.split("\n").filter((s) => s);
}
get canvasWidth() {

View File

@ -40,6 +40,11 @@ class Settings extends events.EventTarget {
save(newSettings, silent) {
newSettings = Object.assign(this.cache, newSettings);
localStorage.setItem("settings", JSON.stringify(newSettings));
if (newSettings.darkTheme) {
document.body.classList.add("darktheme");
} else {
document.body.classList.remove("darktheme");
}
this.cache = this._getFromLocalStorage();
if (silent !== true) {
this.dispatchEvent(

View File

@ -22,12 +22,21 @@ function bind(hotkey, func) {
return false;
}
function bindElement(element, hotkey, func) {
if (settings.get().keyboardShortcuts) {
mousetrap(element).bind(hotkey, func);
return true;
}
return false;
}
function unbind(hotkey) {
mousetrap.unbind(hotkey);
}
module.exports = {
bind: bind,
bindElement: bindElement,
unbind: unbind,
pause: () => {
paused = true;

View File

@ -54,7 +54,7 @@ class TildeWrapper extends BaseMarkdownWrapper {
// prevent ^#... from being treated as headers, due to tag permalinks
class TagPermalinkFixWrapper extends BaseMarkdownWrapper {
preprocess(text) {
return text.replace(/^#/g, "%%%#");
return text.replace(/^#(?=[a-zA-Z0-9_-])/g, "%%%#");
}
postprocess(text) {

View File

@ -5,7 +5,8 @@ const keyboard = require("../util/keyboard.js");
const views = require("./views.js");
function searchInputNodeFocusHelper(inputNode) {
keyboard.bind("q", () => {
keyboard.bind("q", (e) => {
e.preventDefault();
inputNode.focus();
inputNode.setSelectionRange(
inputNode.value.length,

View File

@ -111,7 +111,7 @@ function makeSelect(options) {
}
function makeInput(options) {
options.value = options.value || "";
options.value = options.value === 0 ? 0 : options.value || "";
return _makeLabel(options) + makeElement("input", options);
}
@ -261,7 +261,7 @@ function makePoolLink(id, includeHash, includeCount, pool, name) {
}
function makeUserLink(user) {
let text = makeThumbnail(user ? user.avatarUrl : null);
let text = makeThumbnail(user ? user.avatarUrl : "img/favicon.png");
text += user && user.name ? misc.escapeHtml(user.name) : "Anonymous";
const link =
user && user.name && api.hasPrivilege("users:view")
@ -300,6 +300,8 @@ function _serializeElement(name, attributes) {
attributes[key] === undefined
) {
return "";
} else if (attributes[key] === 0) {
return `${key}="0"`;
}
const attribute = misc.escapeHtml(attributes[key] || "");
return `${key}="${attribute}"`;
@ -321,6 +323,7 @@ function emptyContent(target) {
}
function replaceContent(target, source) {
if (!target) return;
emptyContent(target);
if (source instanceof NodeList) {
for (let child of [...source]) {
@ -457,6 +460,7 @@ function getTemplate(templatePath) {
makeCssName: misc.makeCssName,
makeNumericInput: makeNumericInput,
formatClientLink: uri.formatClientLink,
escapeTagName: uri.escapeTagName,
});
return htmlToDom(templateFactory(ctx));
};

View File

@ -58,6 +58,11 @@ class PoolMergeView extends events.EventTarget {
_evtSubmit(e) {
e.preventDefault();
if (!this._targetPoolId) {
this.clearMessages();
this.showError("You must select a pool name from autocomplete.");
return;
}
this.dispatchEvent(
new CustomEvent("submit", {
detail: {

View File

@ -60,12 +60,14 @@ class BulkSafetyEditor extends BulkEditor {
e.preventDefault();
this.toggleOpen(true);
this.dispatchEvent(new CustomEvent("open", { detail: {} }));
misc.enableExitConfirmation();
}
_evtCloseLinkClick(e) {
e.preventDefault();
this.toggleOpen(false);
this.dispatchEvent(new CustomEvent("close", { detail: {} }));
misc.disableExitConfirmation();
}
}
@ -130,6 +132,7 @@ class BulkTagEditor extends BulkEditor {
this.toggleOpen(true);
this.focus();
this.dispatchEvent(new CustomEvent("open", { detail: {} }));
misc.enableExitConfirmation();
}
_evtCloseLinkClick(e) {
@ -138,6 +141,7 @@ class BulkTagEditor extends BulkEditor {
this.toggleOpen(false);
this.blur();
this.dispatchEvent(new CustomEvent("close", { detail: {} }));
misc.disableExitConfirmation();
}
}
@ -160,12 +164,14 @@ class BulkDeleteEditor extends BulkEditor {
e.preventDefault();
this.toggleOpen(true);
this.dispatchEvent(new CustomEvent("open", { detail: {} }));
misc.enableExitConfirmation();
}
_evtCloseLinkClick(e) {
e.preventDefault();
this.toggleOpen(false);
this.dispatchEvent(new CustomEvent("close", { detail: {} }));
misc.disableExitConfirmation();
}
}

View File

@ -29,7 +29,7 @@ function _formatBasicChange(diff, text) {
return lines;
}
function _makeResourceLink(type, id) {
function _makeResourceLink(type, id, data) {
if (type === "post") {
return views.makePostLink(id, true);
} else if (type === "tag") {
@ -37,7 +37,7 @@ function _makeResourceLink(type, id) {
} else if (type === "tag_category") {
return 'category "' + id + '"';
} else if (type === "pool") {
return views.makePoolLink(id, true);
return views.makePoolLink(id, false, false, data);
}
}

264
client/package-lock.json generated
View File

@ -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",

View File

@ -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",

View File

@ -21,7 +21,7 @@ _search_executor = search.Executor(_search_executor_config)
def _get_post_id(params: Dict[str, str]) -> int:
try:
return int(params["post_id"])
except TypeError:
except (TypeError, ValueError):
raise posts.InvalidPostIdError(
"Invalid post ID: %r." % params["post_id"]
)
@ -161,7 +161,9 @@ def update_post(ctx: rest.Context, params: Dict[str, str]) -> rest.Response:
posts.update_post_notes(post, ctx.get_param_as_list("notes"))
if ctx.has_param("flags"):
auth.verify_privilege(ctx.user, "posts:edit:flags")
posts.update_post_flags(post, ctx.get_param_as_string_list("flags"))
posts.update_post_flags(post, ctx.get_param_as_string_list("flags"), remove=True)
else:
posts.update_post_flags(post, post.flags, remove=True)
if ctx.has_file("thumbnail"):
auth.verify_privilege(ctx.user, "posts:edit:thumbnail")
posts.update_post_thumbnail(post, ctx.get_file("thumbnail"))

View File

@ -30,7 +30,7 @@ def has_favorited(entity: model.Base, user: model.User) -> bool:
return _get_fav_entity(entity, user) is not None
def unset_favorite(entity: model.Base, user: Optional[model.User]) -> None:
def unset_favorite(entity: model.Base, user: model.User) -> None:
assert entity
assert user
fav_entity = _get_fav_entity(entity, user)
@ -38,7 +38,7 @@ def unset_favorite(entity: model.Base, user: Optional[model.User]) -> None:
db.session.delete(fav_entity)
def set_favorite(entity: model.Base, user: Optional[model.User]) -> None:
def set_favorite(entity: model.Base, user: model.User) -> None:
from szurubooru.func import scores
assert entity

View File

@ -24,6 +24,11 @@ def convert_heif_to_png(content: bytes) -> bytes:
return img_byte_arr.getvalue()
def check_for_loop(content: bytes) -> bytes:
img = PILImage.open(BytesIO(content))
return "loop" in img.info
class Image:
def __init__(self, content: bytes) -> None:
self.content = content

View File

@ -94,6 +94,7 @@ TYPE_MAP = {
FLAG_MAP = {
model.Post.FLAG_LOOP: "loop",
model.Post.FLAG_SOUND: "sound",
model.Post.FLAG_TAGME: "tagme",
}
@ -264,8 +265,8 @@ class PostSerializer(serialization.BaseSerializer):
{
post["id"]: post
for post in [
serialize_micro_post(rel, self.auth_user)
for rel in self.post.relations
serialize_micro_post(try_get_post_by_id(rel.child_id), self.auth_user)
for rel in get_post_relations(self.post.post_id)
]
}.values(),
key=lambda post: post["id"],
@ -281,16 +282,14 @@ class PostSerializer(serialization.BaseSerializer):
return scores.get_score(self.post, self.auth_user)
def serialize_own_favorite(self) -> Any:
return (
len(
[
user
for user in self.post.favorited_by
if user.user_id == self.auth_user.user_id
]
)
> 0
)
if self.auth_user.user_id is None:
return False
for user in self.post.favorited_by:
if user.user_id == self.auth_user.user_id:
return True
return False
def serialize_tag_count(self) -> Any:
return self.post.tag_count
@ -365,6 +364,10 @@ def get_post_count() -> int:
return db.session.query(sa.func.count(model.Post.post_id)).one()[0]
def get_post_relations(post_id: int) -> List[model.Post]:
return db.session.query(model.PostRelation).filter(model.PostRelation.parent_id == post_id).all()
def try_get_post_by_id(post_id: int) -> Optional[model.Post]:
return (
db.session.query(model.Post)
@ -531,10 +534,13 @@ def generate_alternate_formats(
def get_default_flags(content: bytes) -> List[str]:
assert content
ret = []
if mime.is_video(mime.get_mime_type(content)):
ret.append(model.Post.FLAG_LOOP)
if mime.is_animated_gif(content):
if images.check_for_loop(content):
ret.append(model.Post.FLAG_LOOP)
elif mime.is_video(mime.get_mime_type(content)):
if images.Image(content).check_for_sound():
ret.append(model.Post.FLAG_SOUND)
ret.append(model.Post.FLAG_TAGME)
return ret
@ -779,10 +785,12 @@ def update_post_notes(post: model.Post, notes: Any) -> None:
)
def update_post_flags(post: model.Post, flags: List[str]) -> None:
def update_post_flags(post: model.Post, flags: List[str], remove: bool = False) -> None:
assert post
target_flags = []
for flag in flags:
if remove and flag == model.Post.FLAG_TAGME:
continue
flag = util.flip(FLAG_MAP).get(flag, None)
if not flag:
raise InvalidPostFlagError(

View File

@ -4,7 +4,7 @@ from typing import Any, Callable, Dict, Optional
import sqlalchemy as sa
from szurubooru import db, model
from szurubooru.func import diff, net, users
from szurubooru.func import diff, net, users, posts
def get_tag_category_snapshot(category: model.TagCategory) -> Dict[str, Any]:
@ -53,7 +53,7 @@ def get_post_snapshot(post: model.Post) -> Dict[str, Any]:
"flags": post.flags,
"featured": post.is_featured,
"tags": sorted([tag.first_name for tag in post.tags]),
"relations": sorted([rel.post_id for rel in post.relations]),
"relations": sorted([rel.child_id for rel in posts.get_post_relations(post.post_id)]),
"notes": sorted(
[
{

View File

@ -197,6 +197,7 @@ class Post(Base):
FLAG_LOOP = "loop"
FLAG_SOUND = "sound"
FLAG_TAGME = "tagme"
# basic meta
post_id = sa.Column("id", sa.Integer, primary_key=True)

View File

@ -32,9 +32,13 @@ def _type_transformer(value: str) -> str:
def _safety_transformer(value: str) -> str:
available_values = {
"safe": model.Post.SAFETY_SAFE,
"s": model.Post.SAFETY_SAFE,
"sketchy": model.Post.SAFETY_SKETCHY,
"questionable": model.Post.SAFETY_SKETCHY,
"q": model.Post.SAFETY_SKETCHY,
"unsafe": model.Post.SAFETY_UNSAFE,
"explicit": model.Post.SAFETY_UNSAFE,
"e": model.Post.SAFETY_UNSAFE,
}
return search_util.enum_transformer(available_values, value)
@ -43,6 +47,7 @@ def _flag_transformer(value: str) -> str:
available_values = {
"loop": model.Post.FLAG_LOOP,
"sound": model.Post.FLAG_SOUND,
"tagme": model.Post.FLAG_TAGME,
}
return "%" + search_util.enum_transformer(available_values, value) + "%"