mirror of
https://github.com/rr-/szurubooru.git
synced 2025-07-17 08:26:24 +00:00
Compare commits
41 Commits
master
...
441bad03e2
Author | SHA1 | Date | |
---|---|---|---|
441bad03e2 | |||
2e1534f2bb | |||
a37ca56fa9 | |||
70d29da933 | |||
362a86712a | |||
f12e28855b | |||
e21cd61919 | |||
223bc9674a | |||
04f4558a3b | |||
9f533882bf | |||
6035278c7e | |||
acb1eddb53 | |||
b3fa26b3fb | |||
d3c2da4f91 | |||
8acd6ab776 | |||
2ed19e013a | |||
2b06e1cafa | |||
7f6211e0cc | |||
86bbd429f7 | |||
0b02826e6d | |||
ea215901af | |||
9884161297 | |||
b2501b7ee2 | |||
7f2a8b5b07 | |||
de9b8fdce3 | |||
ae85605531 | |||
ae72d75631 | |||
ce613c5ade | |||
e05252f67e | |||
20e03e1397 | |||
a9d870eaa8 | |||
08bd3bb890 | |||
b8b51ded15 | |||
f590dc6a41 | |||
486fc345fe | |||
ae9e596095 | |||
5d1dbb291c | |||
a8b6c143eb | |||
4c6d1a216b | |||
9588d11cb0 | |||
b08c6eca26 |
@ -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
|
||||
|
@ -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
|
||||
|
@ -8,11 +8,11 @@ $inactive-tab-text-color-darktheme = $inactive-link-color-darktheme
|
||||
|
||||
/* latin */
|
||||
@font-face
|
||||
font-family: 'Open Sans';
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
src: local('Open Sans'), local('OpenSans'), url(../fonts/open_sans.woff2) format('woff2');
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000;
|
||||
font-family: 'Open Sans'
|
||||
font-style: normal
|
||||
font-weight: 400
|
||||
src: local('Open Sans'), local('OpenSans'), url(../fonts/open_sans.woff2) format('woff2')
|
||||
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2212, U+2215, U+E0FF, U+EFFD, U+F000
|
||||
|
||||
/* make <body> cover entire viewport */
|
||||
html
|
||||
@ -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
|
||||
|
@ -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>")
|
@ -130,8 +130,8 @@
|
||||
background: rgba(255, 0, 0, 0.7)
|
||||
&:after
|
||||
color: white
|
||||
font-family: FontAwesome;
|
||||
content: "\f1f8"; // fa-trash
|
||||
font-family: FontAwesome
|
||||
content: "\f1f8" /* fa-trash */
|
||||
&:not(.delete)
|
||||
background: rgba(200, 200, 200, 0.7)
|
||||
&:after
|
||||
|
@ -18,6 +18,8 @@
|
||||
right: 0
|
||||
top: 0
|
||||
bottom: 0
|
||||
&[data-state=read-only]
|
||||
pointer-events: none
|
||||
|
||||
.notes-overlay
|
||||
g
|
||||
|
@ -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
|
||||
|
@ -44,30 +44,30 @@ $token-border-color = $active-tab-background-color
|
||||
|
||||
.token-flex-container
|
||||
width: 100%
|
||||
display: flex;
|
||||
flex-direction column;
|
||||
padding-bottom: 0.5em;
|
||||
display: flex
|
||||
flex-direction column
|
||||
padding-bottom: 0.5em
|
||||
|
||||
.full-width
|
||||
width: 100%
|
||||
|
||||
.token-flex-row
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
padding: 0.2em;
|
||||
display: flex
|
||||
flex-direction: row
|
||||
justify-content: space-between
|
||||
padding: 0.2em
|
||||
|
||||
.no-wrap
|
||||
white-space: nowrap;
|
||||
white-space: nowrap
|
||||
|
||||
.token-input
|
||||
min-height: 2em;
|
||||
line-height: 2em;
|
||||
text-align: center;
|
||||
min-height: 2em
|
||||
line-height: 2em
|
||||
text-align: center
|
||||
|
||||
.token-flex-column
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
display: flex
|
||||
flex-direction: column
|
||||
|
||||
.token-flex-labels
|
||||
padding-right: 0.5em
|
||||
@ -76,7 +76,7 @@ $token-border-color = $active-tab-background-color
|
||||
border-top: 3px solid $token-border-color
|
||||
|
||||
form
|
||||
width: 100%;
|
||||
width: 100%
|
||||
|
||||
#user-delete form
|
||||
width: 100%
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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><!--
|
||||
|
@ -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>
|
||||
|
@ -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') { %>
|
||||
|
||||
|
@ -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'>
|
||||
|
@ -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) %> <!--
|
||||
--><%- {
|
||||
|
@ -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>
|
||||
<% } %>
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -12,7 +12,7 @@
|
||||
|
||||
<%= item.operation %>
|
||||
|
||||
<%= ctx.makeResourceLink(item.type, item.id) %>
|
||||
<%= ctx.makeResourceLink(item.type, item.id, item.data) %>
|
||||
</div>
|
||||
|
||||
<div class='details'><!--
|
||||
|
@ -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><!--
|
||||
|
@ -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>
|
||||
|
@ -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) %>
|
||||
|
@ -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);
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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(
|
||||
|
@ -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() {
|
||||
|
@ -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(
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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,
|
||||
|
@ -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));
|
||||
};
|
||||
|
@ -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: {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
264
client/package-lock.json
generated
@ -27,13 +27,19 @@
|
||||
"html-minifier": "^3.5.18",
|
||||
"jimp": "^0.13.0",
|
||||
"pretty-error": "^3.0.3",
|
||||
"stylus": "^0.54.8",
|
||||
"stylus": "^0.59.0",
|
||||
"terser": "^4.8.1",
|
||||
"underscore": "^1.12.1",
|
||||
"watchify": "^4.0.0",
|
||||
"ws": "^7.4.6"
|
||||
}
|
||||
},
|
||||
"node_modules/@adobe/css-tools": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz",
|
||||
"integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@babel/runtime": {
|
||||
"version": "7.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz",
|
||||
@ -516,18 +522,6 @@
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"node_modules/atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"atob": "bin/atob.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.5.0"
|
||||
}
|
||||
},
|
||||
"node_modules/available-typed-arrays": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz",
|
||||
@ -1737,27 +1731,6 @@
|
||||
"node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/css": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
|
||||
"integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"inherits": "^2.0.3",
|
||||
"source-map": "^0.6.1",
|
||||
"source-map-resolve": "^0.5.2",
|
||||
"urix": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-parse": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz",
|
||||
"integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"css": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/css-select": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
|
||||
@ -1795,15 +1768,6 @@
|
||||
"url": "https://github.com/sponsors/fb55"
|
||||
}
|
||||
},
|
||||
"node_modules/css/node_modules/source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/csso": {
|
||||
"version": "3.5.1",
|
||||
"resolved": "https://registry.npmjs.org/csso/-/csso-3.5.1.tgz",
|
||||
@ -1830,15 +1794,6 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/decode-uri-component": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/define-properties": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||
@ -3676,13 +3631,6 @@
|
||||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/resolve-url": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
|
||||
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
|
||||
"deprecated": "https://github.com/lydell/resolve-url#deprecated",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/ripemd160": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
||||
@ -3698,12 +3646,6 @@
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"node_modules/safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
@ -3794,19 +3736,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-resolve": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
|
||||
"integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"atob": "^2.1.2",
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"resolve-url": "^0.2.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"urix": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-support": {
|
||||
"version": "0.4.18",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
|
||||
@ -3816,12 +3745,6 @@
|
||||
"source-map": "^0.5.6"
|
||||
}
|
||||
},
|
||||
"node_modules/source-map-url": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
|
||||
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/stream-browserify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||
@ -3912,18 +3835,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/stylus": {
|
||||
"version": "0.54.8",
|
||||
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz",
|
||||
"integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==",
|
||||
"version": "0.59.0",
|
||||
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz",
|
||||
"integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"css-parse": "~2.0.0",
|
||||
"debug": "~3.1.0",
|
||||
"@adobe/css-tools": "^4.0.1",
|
||||
"debug": "^4.3.2",
|
||||
"glob": "^7.1.6",
|
||||
"mkdirp": "~1.0.4",
|
||||
"safer-buffer": "^2.1.2",
|
||||
"sax": "~1.2.4",
|
||||
"semver": "^6.3.0",
|
||||
"source-map": "^0.7.3"
|
||||
},
|
||||
"bin": {
|
||||
@ -3931,28 +3851,33 @@
|
||||
},
|
||||
"engines": {
|
||||
"node": "*"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/stylus"
|
||||
}
|
||||
},
|
||||
"node_modules/stylus/node_modules/mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"node_modules/stylus/node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"mkdirp": "bin/cmd.js"
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/stylus/node_modules/semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
}
|
||||
"node_modules/stylus/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/stylus/node_modules/source-map": {
|
||||
"version": "0.7.3",
|
||||
@ -4219,13 +4144,6 @@
|
||||
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/urix": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
|
||||
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
|
||||
"deprecated": "Please see https://github.com/lydell/urix#deprecated",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/url": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
||||
@ -4602,6 +4520,12 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@adobe/css-tools": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.2.0.tgz",
|
||||
"integrity": "sha512-E09FiIft46CmH5Qnjb0wsW54/YQd69LsxeKUOWawmws1XWvyFGURnAChH0mlr7YPFR1ofwvUQfcL0J3lMxXqPA==",
|
||||
"dev": true
|
||||
},
|
||||
"@babel/runtime": {
|
||||
"version": "7.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.10.3.tgz",
|
||||
@ -5072,12 +4996,6 @@
|
||||
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
|
||||
},
|
||||
"atob": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz",
|
||||
"integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==",
|
||||
"dev": true
|
||||
},
|
||||
"available-typed-arrays": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.2.tgz",
|
||||
@ -6246,35 +6164,6 @@
|
||||
"randomfill": "^1.0.3"
|
||||
}
|
||||
},
|
||||
"css": {
|
||||
"version": "2.2.4",
|
||||
"resolved": "https://registry.npmjs.org/css/-/css-2.2.4.tgz",
|
||||
"integrity": "sha512-oUnjmWpy0niI3x/mPL8dVEI1l7MnG3+HHyRPHf+YFSbK+svOhXpmSOcDURUh2aOCgl2grzrOPt1nHLuCVFULLw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"inherits": "^2.0.3",
|
||||
"source-map": "^0.6.1",
|
||||
"source-map-resolve": "^0.5.2",
|
||||
"urix": "^0.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"source-map": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
|
||||
"integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"css-parse": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/css-parse/-/css-parse-2.0.0.tgz",
|
||||
"integrity": "sha1-pGjuZnwW2BzPBcWMONKpfHgNv9Q=",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"css": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"css-select": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/css-select/-/css-select-2.1.0.tgz",
|
||||
@ -6326,12 +6215,6 @@
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"decode-uri-component": {
|
||||
"version": "0.2.2",
|
||||
"resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz",
|
||||
"integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==",
|
||||
"dev": true
|
||||
},
|
||||
"define-properties": {
|
||||
"version": "1.1.3",
|
||||
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
|
||||
@ -7829,12 +7712,6 @@
|
||||
"path-parse": "^1.0.6"
|
||||
}
|
||||
},
|
||||
"resolve-url": {
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz",
|
||||
"integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=",
|
||||
"dev": true
|
||||
},
|
||||
"ripemd160": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz",
|
||||
@ -7850,12 +7727,6 @@
|
||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
|
||||
},
|
||||
"safer-buffer": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
|
||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||
"dev": true
|
||||
},
|
||||
"sax": {
|
||||
"version": "1.2.4",
|
||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
||||
@ -7931,19 +7802,6 @@
|
||||
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
|
||||
"dev": true
|
||||
},
|
||||
"source-map-resolve": {
|
||||
"version": "0.5.3",
|
||||
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
|
||||
"integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"atob": "^2.1.2",
|
||||
"decode-uri-component": "^0.2.0",
|
||||
"resolve-url": "^0.2.1",
|
||||
"source-map-url": "^0.4.0",
|
||||
"urix": "^0.1.0"
|
||||
}
|
||||
},
|
||||
"source-map-support": {
|
||||
"version": "0.4.18",
|
||||
"resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz",
|
||||
@ -7953,12 +7811,6 @@
|
||||
"source-map": "^0.5.6"
|
||||
}
|
||||
},
|
||||
"source-map-url": {
|
||||
"version": "0.4.1",
|
||||
"resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz",
|
||||
"integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==",
|
||||
"dev": true
|
||||
},
|
||||
"stream-browserify": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-2.0.1.tgz",
|
||||
@ -8040,31 +7892,31 @@
|
||||
}
|
||||
},
|
||||
"stylus": {
|
||||
"version": "0.54.8",
|
||||
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.54.8.tgz",
|
||||
"integrity": "sha512-vr54Or4BZ7pJafo2mpf0ZcwA74rpuYCZbxrHBsH8kbcXOwSfvBFwsRfpGO5OD5fhG5HDCFW737PKaawI7OqEAg==",
|
||||
"version": "0.59.0",
|
||||
"resolved": "https://registry.npmjs.org/stylus/-/stylus-0.59.0.tgz",
|
||||
"integrity": "sha512-lQ9w/XIOH5ZHVNuNbWW8D822r+/wBSO/d6XvtyHLF7LW4KaCIDeVbvn5DF8fGCJAUCwVhVi/h6J0NUcnylUEjg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"css-parse": "~2.0.0",
|
||||
"debug": "~3.1.0",
|
||||
"@adobe/css-tools": "^4.0.1",
|
||||
"debug": "^4.3.2",
|
||||
"glob": "^7.1.6",
|
||||
"mkdirp": "~1.0.4",
|
||||
"safer-buffer": "^2.1.2",
|
||||
"sax": "~1.2.4",
|
||||
"semver": "^6.3.0",
|
||||
"source-map": "^0.7.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
|
||||
"dev": true
|
||||
"debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "6.3.0",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
|
||||
"integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"dev": true
|
||||
},
|
||||
"source-map": {
|
||||
@ -8287,12 +8139,6 @@
|
||||
"integrity": "sha1-9rRQHC7EzdJrp4vnIilh3ndiFZg=",
|
||||
"dev": true
|
||||
},
|
||||
"urix": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz",
|
||||
"integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=",
|
||||
"dev": true
|
||||
},
|
||||
"url": {
|
||||
"version": "0.11.0",
|
||||
"resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz",
|
||||
|
@ -28,7 +28,7 @@
|
||||
"html-minifier": "^3.5.18",
|
||||
"jimp": "^0.13.0",
|
||||
"pretty-error": "^3.0.3",
|
||||
"stylus": "^0.54.8",
|
||||
"stylus": "^0.59.0",
|
||||
"terser": "^4.8.1",
|
||||
"underscore": "^1.12.1",
|
||||
"watchify": "^4.0.0",
|
||||
|
@ -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"))
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
[
|
||||
{
|
||||
|
@ -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)
|
||||
|
@ -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) + "%"
|
||||
|
||||
|
@ -70,7 +70,7 @@ def test_creating_minimal_posts(context_factory, post_factory, user_factory):
|
||||
posts.update_post_source.assert_called_once_with(post, "")
|
||||
posts.update_post_relations.assert_called_once_with(post, [])
|
||||
posts.update_post_notes.assert_called_once_with(post, [])
|
||||
posts.update_post_flags.assert_called_once_with(post, [])
|
||||
posts.update_post_flags.assert_called_once_with(post, ["tagme"])
|
||||
posts.update_post_thumbnail.assert_called_once_with(
|
||||
post, "post-thumbnail"
|
||||
)
|
||||
|
@ -93,7 +93,7 @@ def test_post_updating(
|
||||
post, ["note1", "note2"]
|
||||
)
|
||||
posts.update_post_flags.assert_called_once_with(
|
||||
post, ["flag1", "flag2"]
|
||||
post, ["flag1", "flag2"], remove=True
|
||||
)
|
||||
posts.serialize_post.assert_called_once_with(
|
||||
post, auth_user, options=[]
|
||||
|
Reference in New Issue
Block a user