summaryrefslogtreecommitdiff
path: root/client/js
diff options
context:
space:
mode:
Diffstat (limited to 'client/js')
-rw-r--r--client/js/controllers/home_controller.js2
-rw-r--r--client/js/controllers/pool_controller.js8
-rw-r--r--client/js/controllers/pool_list_controller.js2
-rw-r--r--client/js/controllers/post_list_controller.js51
-rw-r--r--client/js/controllers/post_main_controller.js12
-rw-r--r--client/js/controllers/post_upload_controller.js68
-rw-r--r--client/js/controllers/tag_controller.js6
-rw-r--r--client/js/controllers/user_controller.js17
-rw-r--r--client/js/controls/expander_control.js5
-rw-r--r--client/js/controls/file_dropper_control.js22
-rw-r--r--client/js/controls/post_content_control.js24
-rw-r--r--client/js/controls/post_edit_sidebar_control.js7
-rw-r--r--client/js/controls/post_notes_overlay_control.js5
-rw-r--r--client/js/controls/tag_input_control.js6
-rw-r--r--client/js/main.js6
-rw-r--r--client/js/models/post.js2
-rw-r--r--client/js/util/markdown.js23
-rw-r--r--client/js/util/views.js12
-rw-r--r--client/js/views/post_main_view.js5
-rw-r--r--client/js/views/post_upload_view.js24
-rw-r--r--client/js/views/posts_header_view.js50
-rw-r--r--client/js/views/posts_page_view.js35
-rw-r--r--client/js/views/user_tokens_view.js10
23 files changed, 298 insertions, 104 deletions
diff --git a/client/js/controllers/home_controller.js b/client/js/controllers/home_controller.js
index ea2a411..528ce62 100644
--- a/client/js/controllers/home_controller.js
+++ b/client/js/controllers/home_controller.js
@@ -17,7 +17,7 @@ class HomeController {
buildDate: config.meta.buildDate,
canListSnapshots: api.hasPrivilege("snapshots:list"),
canListPosts: api.hasPrivilege("posts:list"),
- isDevelopmentMode: config.environment == "development"
+ isDevelopmentMode: config.environment == "development",
});
Info.get().then(
diff --git a/client/js/controllers/pool_controller.js b/client/js/controllers/pool_controller.js
index ad0b956..f49827f 100644
--- a/client/js/controllers/pool_controller.js
+++ b/client/js/controllers/pool_controller.js
@@ -91,16 +91,16 @@ class PoolController {
_evtUpdate(e) {
this._view.clearMessages();
this._view.disableForm();
- if (e.detail.names !== undefined) {
+ if (e.detail.names !== undefined && e.detail.names !== null) {
e.detail.pool.names = e.detail.names;
}
- if (e.detail.category !== undefined) {
+ if (e.detail.category !== undefined && e.detail.category !== null) {
e.detail.pool.category = e.detail.category;
}
- if (e.detail.description !== undefined) {
+ if (e.detail.description !== undefined && e.detail.description !== null) {
e.detail.pool.description = e.detail.description;
}
- if (e.detail.posts !== undefined) {
+ if (e.detail.posts !== undefined && e.detail.posts !== null) {
e.detail.pool.posts.clear();
for (let postId of e.detail.posts) {
e.detail.pool.posts.add(
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/controllers/post_list_controller.js b/client/js/controllers/post_list_controller.js
index 7a8ff9e..0146e9e 100644
--- a/client/js/controllers/post_list_controller.js
+++ b/client/js/controllers/post_list_controller.js
@@ -47,6 +47,7 @@ class PostListController {
canBulkEditTags: api.hasPrivilege("posts:bulk-edit:tags"),
canBulkEditSafety: api.hasPrivilege("posts:bulk-edit:safety"),
canViewMetrics: api.hasPrivilege("metrics:list"),
+ canBulkDelete: api.hasPrivilege("posts:bulk-edit:delete"),
bulkEdit: {
tags: this._bulkEditTags,
},
@@ -55,6 +56,16 @@ class PostListController {
this._evtNavigate(e)
);
+ if (this._headerView._bulkDeleteEditor) {
+ this._headerView._bulkDeleteEditor.addEventListener(
+ "deleteSelectedPosts",
+ (e) => {
+ this._evtDeleteSelectedPosts(e);
+ }
+ );
+ }
+
+ this._postsMarkedForDeletion = [];
this._syncPageController();
}
@@ -143,6 +154,38 @@ class PostListController {
this._ctx.parameters.relations = relations.join(" ") || " ";
}
+ _evtMarkForDeletion(e) {
+ const postId = e.detail;
+
+ // Add or remove post from delete list
+ if (e.detail.delete) {
+ this._postsMarkedForDeletion.push(e.detail.post);
+ } else {
+ this._postsMarkedForDeletion = this._postsMarkedForDeletion.filter(
+ (x) => x.id != e.detail.post.id
+ );
+ }
+ }
+
+ _evtDeleteSelectedPosts(e) {
+ if (this._postsMarkedForDeletion.length == 0) return;
+
+ if (
+ confirm(
+ `Are you sure you want to delete ${this._postsMarkedForDeletion.length} posts?`
+ )
+ ) {
+ Promise.all(
+ this._postsMarkedForDeletion.map((post) => post.delete())
+ )
+ .catch((error) => window.alert(error.message))
+ .then(() => {
+ this._postsMarkedForDeletion = [];
+ this._headerView._navigate();
+ });
+ }
+ }
+
_syncPageController() {
this._pageController.run({
parameters: this._ctx.parameters,
@@ -177,6 +220,11 @@ class PostListController {
tags: this._bulkEditTags,
relations: this._ctx.parameters.relations,
},
+ canBulkDelete: api.hasPrivilege("posts:bulk-edit:delete"),
+ bulkEdit: {
+ tags: this._bulkEditTags,
+ markedForDeletion: this._postsMarkedForDeletion,
+ },
postFlow: settings.get().postFlow,
});
const view = new PostsPageView(pageCtx);
@@ -191,6 +239,9 @@ class PostListController {
view.addEventListener("removeRelation", (e) =>
this._evtRemoveRelation(e)
);
+ view.addEventListener("markForDeletion", (e) =>
+ this._evtMarkForDeletion(e)
+ );
return view;
},
readPageFromCache: (rawPage) => PostList.fromResponse(rawPage),
diff --git a/client/js/controllers/post_main_controller.js b/client/js/controllers/post_main_controller.js
index d473788..7d6dcd3 100644
--- a/client/js/controllers/post_main_controller.js
+++ b/client/js/controllers/post_main_controller.js
@@ -160,22 +160,22 @@ class PostMainController extends BasePostController {
this._view.sidebarControl.disableForm();
this._view.sidebarControl.clearMessages();
const post = e.detail.post;
- if (e.detail.safety !== undefined) {
+ if (e.detail.safety !== undefined && e.detail.safety !== null) {
post.safety = e.detail.safety;
}
- if (e.detail.flags !== undefined) {
+ if (e.detail.flags !== undefined && e.detail.flags !== null) {
post.flags = e.detail.flags;
}
- if (e.detail.relations !== undefined) {
+ if (e.detail.relations !== undefined && e.detail.relations !== null) {
post.relations = e.detail.relations;
}
- if (e.detail.content !== undefined) {
+ if (e.detail.content !== undefined && e.detail.content !== null) {
post.newContent = e.detail.content;
}
- if (e.detail.thumbnail !== undefined) {
+ if (e.detail.thumbnail !== undefined && e.detail.thumbnail !== null) {
post.newThumbnail = e.detail.thumbnail;
}
- if (e.detail.source !== undefined) {
+ if (e.detail.source !== undefined && e.detail.source !== null) {
post.source = e.detail.source;
}
post.save().then(
diff --git a/client/js/controllers/post_upload_controller.js b/client/js/controllers/post_upload_controller.js
index 360dd4f..7ee63ac 100644
--- a/client/js/controllers/post_upload_controller.js
+++ b/client/js/controllers/post_upload_controller.js
@@ -13,7 +13,7 @@ const PostUploadView = require("../views/post_upload_view.js");
const EmptyView = require("../views/empty_view.js");
const genericErrorMessage =
- "One of the posts needs your attention; " +
+ "One or more posts needs your attention; " +
'click "resume upload" when you\'re ready.';
class PostUploadController {
@@ -58,6 +58,7 @@ class PostUploadController {
this._view.disableForm();
this._view.clearMessages();
const tagErrors = []; // to be displayed after all uploads
+ let anyFailures = false;
e.detail.uploadables
.reduce(
@@ -66,11 +67,45 @@ class PostUploadController {
this._uploadSinglePost(
uploadable,
e.detail.skipDuplicates,
- e.detail.copyTagsToOriginals
- )
+ e.detail.copyTagsToOriginals,
+ e.detail.alwaysUploadSimilar
+ ).catch((error) => {
+ anyFailures = true;
+ if (error.uploadable) {
+ if (error.similarPosts) {
+ error.uploadable.lookalikes =
+ error.similarPosts;
+ this._view.updateUploadable(
+ error.uploadable
+ );
+ this._view.showInfo(
+ error.message,
+ error.uploadable
+ );
+ } else {
+ this._view.showError(
+ error.message,
+ error.uploadable
+ );
+ }
+ } else {
+ this._view.showError(
+ error.message,
+ uploadable
+ );
+ }
+ if (e.detail.pauseRemainOnError) {
+ return Promise.reject();
+ }
+ })
),
Promise.resolve()
)
+ .then(() => {
+ if (anyFailures) {
+ return Promise.reject();
+ }
+ })
.then(
() => {
this._view.clearMessages();
@@ -82,31 +117,13 @@ class PostUploadController {
}
},
(error) => {
- if (error.uploadable) {
- if (error.similarPosts) {
- error.uploadable.lookalikes = error.similarPosts;
- this._view.updateUploadable(error.uploadable);
- this._view.showInfo(genericErrorMessage);
- this._view.showInfo(
- error.message,
- error.uploadable
- );
- } else {
- this._view.showError(genericErrorMessage);
- this._view.showError(
- error.message,
- error.uploadable
- );
- }
- } else {
- this._view.showError(error.message);
- }
+ this._view.showError(genericErrorMessage);
this._view.enableForm();
}
);
}
- _uploadSinglePost(uploadable, skipDuplicates, copyTagsToOriginals) {
+ _uploadSinglePost(uploadable, skipDuplicates, copyTagsToOriginals, alwaysUploadSimilar) {
progress.start();
let reverseSearchPromise = Promise.resolve();
if (!uploadable.lookalikesConfirmed) {
@@ -145,7 +162,10 @@ class PostUploadController {
}
// notify about similar posts
- if (searchResult.similarPosts.length) {
+ if (
+ searchResult.similarPosts.length &&
+ !alwaysUploadSimilar
+ ) {
let error = new Error(
`Found ${searchResult.similarPosts.length} similar ` +
"posts.\nYou can resume or discard this upload."
diff --git a/client/js/controllers/tag_controller.js b/client/js/controllers/tag_controller.js
index 326430b..b7e6ce3 100644
--- a/client/js/controllers/tag_controller.js
+++ b/client/js/controllers/tag_controller.js
@@ -105,13 +105,13 @@ class TagController {
_evtUpdate(e) {
this._view.clearMessages();
this._view.disableForm();
- if (e.detail.names !== undefined) {
+ if (e.detail.names !== undefined && e.detail.names !== null) {
e.detail.tag.names = e.detail.names;
}
- if (e.detail.category !== undefined) {
+ if (e.detail.category !== undefined && e.detail.category !== null) {
e.detail.tag.category = e.detail.category;
}
- if (e.detail.description !== undefined) {
+ if (e.detail.description !== undefined && e.detail.description !== null) {
e.detail.tag.description = e.detail.description;
}
e.detail.tag.save().then(
diff --git a/client/js/controllers/user_controller.js b/client/js/controllers/user_controller.js
index 326736b..8cf4658 100644
--- a/client/js/controllers/user_controller.js
+++ b/client/js/controllers/user_controller.js
@@ -31,9 +31,8 @@ class UserController {
userTokenPromise = UserToken.get(userName).then(
(userTokens) => {
return userTokens.map((token) => {
- token.isCurrentAuthToken = api.isCurrentAuthToken(
- token
- );
+ token.isCurrentAuthToken =
+ api.isCurrentAuthToken(token);
return token;
});
},
@@ -176,21 +175,21 @@ class UserController {
const isLoggedIn = api.isLoggedIn(e.detail.user);
const infix = isLoggedIn ? "self" : "any";
- if (e.detail.name !== undefined) {
+ if (e.detail.name !== undefined && e.detail.name !== null) {
e.detail.user.name = e.detail.name;
}
- if (e.detail.email !== undefined) {
+ if (e.detail.email !== undefined && e.detail.email !== null) {
e.detail.user.email = e.detail.email;
}
- if (e.detail.rank !== undefined) {
+ if (e.detail.rank !== undefined && e.detail.rank !== null) {
e.detail.user.rank = e.detail.rank;
}
- if (e.detail.password !== undefined) {
+ if (e.detail.password !== undefined && e.detail.password !== null) {
e.detail.user.password = e.detail.password;
}
- if (e.detail.avatarStyle !== undefined) {
+ if (e.detail.avatarStyle !== undefined && e.detail.avatarStyle !== null) {
e.detail.user.avatarStyle = e.detail.avatarStyle;
if (e.detail.avatarContent) {
e.detail.user.avatarContent = e.detail.avatarContent;
@@ -303,7 +302,7 @@ class UserController {
this._view.clearMessages();
this._view.disableForm();
- if (e.detail.note !== undefined) {
+ if (e.detail.note !== undefined && e.detail.note !== null) {
e.detail.userToken.note = e.detail.note;
}
diff --git a/client/js/controls/expander_control.js b/client/js/controls/expander_control.js
index c26bd03..f3105ca 100644
--- a/client/js/controls/expander_control.js
+++ b/client/js/controls/expander_control.js
@@ -49,9 +49,8 @@ class ExpanderControl {
// eslint-disable-next-line accessor-pairs
set title(newTitle) {
if (this._expanderNode) {
- this._expanderNode.querySelector(
- "header span"
- ).textContent = newTitle;
+ this._expanderNode.querySelector("header span").textContent =
+ newTitle;
}
}
diff --git a/client/js/controls/file_dropper_control.js b/client/js/controls/file_dropper_control.js
index 2dfb492..8d1fa16 100644
--- a/client/js/controls/file_dropper_control.js
+++ b/client/js/controls/file_dropper_control.js
@@ -48,6 +48,12 @@ class FileDropperControl extends events.EventTarget {
this._urlInputNode.addEventListener("keydown", (e) =>
this._evtUrlInputKeyDown(e)
);
+ this._urlInputNode.addEventListener("paste", (e) => {
+ // document.onpaste is used on the post-upload page.
+ // And this event is used on the post edit page.
+ if (document.getElementById("post-upload")) return;
+ this._evtPaste(e)
+ });
}
if (this._urlConfirmButtonNode) {
this._urlConfirmButtonNode.addEventListener("click", (e) =>
@@ -55,6 +61,11 @@ class FileDropperControl extends events.EventTarget {
);
}
+ document.onpaste = (e) => {
+ if (!document.getElementById("post-upload")) return;
+ this._evtPaste(e)
+ }
+
this._originalHtml = this._dropperNode.innerHTML;
views.replaceContent(target, source);
}
@@ -129,6 +140,17 @@ class FileDropperControl extends events.EventTarget {
this._emitFiles(e.dataTransfer.files);
}
+ _evtPaste(e) {
+ const items = (e.clipboardData || e.originalEvent.clipboardData).items;
+ const fileList = Array.from(items).map((x) => x.getAsFile()).filter(f => f);
+
+ if (!this._options.allowMultiple && fileList.length > 1) {
+ window.alert("Cannot select multiple files.");
+ } else if (fileList.length > 0) {
+ this._emitFiles(fileList);
+ }
+ }
+
_evtUrlInputKeyDown(e) {
if (e.which !== KEY_RETURN) {
return;
diff --git a/client/js/controls/post_content_control.js b/client/js/controls/post_content_control.js
index 0023854..ed5e9a4 100644
--- a/client/js/controls/post_content_control.js
+++ b/client/js/controls/post_content_control.js
@@ -115,6 +115,30 @@ class PostContentControl {
}
_refreshSize() {
+ if (window.innerWidth <= 800) {
+ const buttons = document.querySelector(".sidebar > .buttons");
+ if (buttons) {
+ const content = document.querySelector(".content");
+ content.insertBefore(buttons, content.querySelector(".post-container + *"));
+
+ const afterControls = document.querySelector(".content > .after-mobile-controls");
+ if (afterControls) {
+ afterControls.parentElement.parentElement.appendChild(afterControls);
+ }
+ }
+ } else {
+ const buttons = document.querySelector(".content > .buttons");
+ if (buttons) {
+ const sidebar = document.querySelector(".sidebar");
+ sidebar.insertBefore(buttons, sidebar.firstElementChild);
+ }
+
+ const afterControls = document.querySelector(".content + .after-mobile-controls");
+ if (afterControls) {
+ document.querySelector(".content").appendChild(afterControls);
+ }
+ }
+
this._currentFitFunction();
}
diff --git a/client/js/controls/post_edit_sidebar_control.js b/client/js/controls/post_edit_sidebar_control.js
index 053a317..a673495 100644
--- a/client/js/controls/post_edit_sidebar_control.js
+++ b/client/js/controls/post_edit_sidebar_control.js
@@ -221,9 +221,8 @@ class PostEditSidebarControl extends events.EventTarget {
);
if (this._formNode) {
- const inputNodes = this._formNode.querySelectorAll(
- "input, textarea"
- );
+ const inputNodes =
+ this._formNode.querySelectorAll("input, textarea");
for (let node of inputNodes) {
node.addEventListener("change", (e) =>
this.dispatchEvent(new CustomEvent("change"))
@@ -476,7 +475,7 @@ class PostEditSidebarControl extends events.EventTarget {
: undefined,
thumbnail:
- this._newPostThumbnail !== undefined
+ this._newPostThumbnail !== undefined && this._newPostThumbnail !== null
? this._newPostThumbnail
: undefined,
diff --git a/client/js/controls/post_notes_overlay_control.js b/client/js/controls/post_notes_overlay_control.js
index 030f7f2..e9aad04 100644
--- a/client/js/controls/post_notes_overlay_control.js
+++ b/client/js/controls/post_notes_overlay_control.js
@@ -727,9 +727,8 @@ class PostNotesOverlayControl extends events.EventTarget {
}
_showNoteText(note) {
- this._textNode.querySelector(
- ".wrapper"
- ).innerHTML = misc.formatMarkdown(note.text);
+ this._textNode.querySelector(".wrapper").innerHTML =
+ misc.formatMarkdown(note.text);
this._textNode.style.display = "block";
const bodyRect = document.body.getBoundingClientRect();
const noteRect = this._textNode.getBoundingClientRect();
diff --git a/client/js/controls/tag_input_control.js b/client/js/controls/tag_input_control.js
index 35bbe9f..0399d21 100644
--- a/client/js/controls/tag_input_control.js
+++ b/client/js/controls/tag_input_control.js
@@ -200,11 +200,9 @@ class TagInputControl extends events.EventTarget {
const listItemNode = this._createListItemNode(tag);
if (!tag.category) {
listItemNode.classList.add("new");
- }
- else if (source === SOURCE_IMPLICATION) {
+ } else if (source === SOURCE_IMPLICATION) {
listItemNode.classList.add("implication");
- }
- else {
+ } else {
listItemNode.classList.add("added");
}
this._tagListNode.prependChild(listItemNode);
diff --git a/client/js/main.js b/client/js/main.js
index 4f7436a..1d37865 100644
--- a/client/js/main.js
+++ b/client/js/main.js
@@ -4,12 +4,12 @@ const config = require("./config.js");
if (config.environment == "development") {
var ws = new WebSocket("ws://" + location.hostname + ":8080");
- ws.addEventListener('open', function (event) {
+ ws.addEventListener("open", function (event) {
console.log("Live-reloading websocket connected.");
});
- ws.addEventListener('message', (event) => {
+ ws.addEventListener("message", (event) => {
console.log(event);
- if (event.data == 'reload'){
+ if (event.data == "reload") {
location.reload();
}
});
diff --git a/client/js/models/post.js b/client/js/models/post.js
index 1adaf5a..3df3b2c 100644
--- a/client/js/models/post.js
+++ b/client/js/models/post.js
@@ -296,7 +296,7 @@ class Post extends events.EventTarget {
if (this._newContent) {
files.content = this._newContent;
}
- if (this._newThumbnail !== undefined) {
+ if (this._newThumbnail !== undefined && this._newThumbnail !== null) {
files.thumbnail = this._newThumbnail;
}
if (this._source !== this._orig._source) {
diff --git a/client/js/util/markdown.js b/client/js/util/markdown.js
index 22cdae5..e71e326 100644
--- a/client/js/util/markdown.js
+++ b/client/js/util/markdown.js
@@ -65,17 +65,6 @@ class TagPermalinkFixWrapper extends BaseMarkdownWrapper {
// post, user and tags permalinks
class EntityPermalinkWrapper extends BaseMarkdownWrapper {
preprocess(text) {
- // URL-based permalinks
- text = text.replace(new RegExp("\\b/post/(\\d+)/?\\b", "g"), "@$1");
- text = text.replace(
- new RegExp("\\b/tag/([a-zA-Z0-9_-]+?)/?", "g"),
- "#$1"
- );
- text = text.replace(
- new RegExp("\\b/user/([a-zA-Z0-9_-]+?)/?", "g"),
- "+$1"
- );
-
text = text.replace(
/(^|^\(|(?:[^\]])\(|[\s<>\[\]\)])([+#@][a-zA-Z0-9_-]+)/g,
"$1[$2]($2)"
@@ -136,12 +125,8 @@ function createRenderer() {
const renderer = new marked.Renderer();
renderer.image = (href, title, alt) => {
- let [
- _,
- url,
- width,
- height,
- ] = /^(.+?)(?:\s=\s*(\d*)\s*x\s*(\d*)\s*)?$/.exec(href);
+ let [_, url, width, height] =
+ /^(.+?)(?:\s=\s*(\d*)\s*x\s*(\d*)\s*)?$/.exec(href);
let res = '<img src="' + sanitize(url) + '" alt="' + sanitize(alt);
if (width) {
res += '" width="' + width;
@@ -174,7 +159,7 @@ function formatMarkdown(text) {
for (let wrapper of wrappers) {
text = wrapper.preprocess(text);
}
- text = marked(text, options);
+ text = marked.parse(text, options);
wrappers.reverse();
for (let wrapper of wrappers) {
text = wrapper.postprocess(text);
@@ -200,7 +185,7 @@ function formatInlineMarkdown(text) {
for (let wrapper of wrappers) {
text = wrapper.preprocess(text);
}
- text = marked.inlineLexer(text, [], options);
+ text = marked.parseInline(text, options);
wrappers.reverse();
for (let wrapper of wrappers) {
text = wrapper.postprocess(text);
diff --git a/client/js/util/views.js b/client/js/util/views.js
index cc0614b..9958c87 100644
--- a/client/js/util/views.js
+++ b/client/js/util/views.js
@@ -231,13 +231,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(
@@ -256,15 +256,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(
@@ -286,7 +286,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/client/js/views/post_main_view.js b/client/js/views/post_main_view.js
index 36e3f9a..c6b312b 100644
--- a/client/js/views/post_main_view.js
+++ b/client/js/views/post_main_view.js
@@ -25,9 +25,8 @@ class PostMainView {
views.replaceContent(this._hostNode, sourceNode);
views.syncScrollPosition();
- const topNavigationNode = document.body.querySelector(
- "#top-navigation"
- );
+ const topNavigationNode =
+ document.body.querySelector("#top-navigation");
this._postContentControl = new PostContentControl(
postContainerNode,
diff --git a/client/js/views/post_upload_view.js b/client/js/views/post_upload_view.js
index c83dd0a..83fdf12 100644
--- a/client/js/views/post_upload_view.js
+++ b/client/js/views/post_upload_view.js
@@ -1,6 +1,7 @@
"use strict";
const events = require("../events.js");
+const api = require("../api.js");
const views = require("../util/views.js");
const FileDropperControl = require("../controls/file_dropper_control.js");
const TagList = require("../models/tag_list.js");
@@ -27,6 +28,7 @@ function _mimeTypeToPostType(mimeType) {
"image/heic": "image",
"video/mp4": "video",
"video/webm": "video",
+ "video/quicktime": "video",
}[mimeType] || "unknown"
);
}
@@ -40,7 +42,8 @@ class Uploadable extends events.EventTarget {
this.flags = [];
this.tags = [];
this.relations = [];
- this.anonymous = false;
+ this.anonymous = !api.isLoggedIn();
+ this.forceAnonymous = !api.isLoggedIn();
}
destroy() {}
@@ -124,6 +127,7 @@ class Url extends Uploadable {
heif: "image/heif",
heic: "image/heic",
mp4: "video/mp4",
+ mov: "video/quicktime",
webm: "video/webm",
};
for (let extension of Object.keys(mime)) {
@@ -309,7 +313,7 @@ class PostUploadView extends events.EventTarget {
for (let uploadable of this._uploadables) {
this._updateUploadableFromDom(uploadable);
}
- this._submitButtonNode.value = "Resume upload";
+ this._submitButtonNode.value = "Resume";
this._emit("submit");
}
@@ -396,6 +400,10 @@ class PostUploadView extends events.EventTarget {
skipDuplicates: this._skipDuplicatesCheckboxNode.checked,
copyTagsToOriginals:
this._copyTagsToOriginalsCheckboxNode.checked,
+ alwaysUploadSimilar:
+ this._alwaysUploadSimilarCheckboxNode.checked,
+ pauseRemainOnError:
+ this._pauseRemainOnErrorCheckboxNode.checked,
},
})
);
@@ -466,6 +474,18 @@ class PostUploadView extends events.EventTarget {
get _copyTagsToOriginalsCheckboxNode() {
return this._hostNode.querySelector("form [name=copy-tags-to-originals]");
}
+
+ get _alwaysUploadSimilarCheckboxNode() {
+ return this._hostNode.querySelector(
+ "form [name=always-upload-similar]"
+ );
+ }
+
+ get _pauseRemainOnErrorCheckboxNode() {
+ return this._hostNode.querySelector(
+ "form [name=pause-remain-on-error]"
+ );
+ }
get _submitButtonNode() {
return this._hostNode.querySelector("form [type=submit]");
diff --git a/client/js/views/posts_header_view.js b/client/js/views/posts_header_view.js
index 7f5acac..7923752 100644
--- a/client/js/views/posts_header_view.js
+++ b/client/js/views/posts_header_view.js
@@ -160,6 +160,34 @@ class BulkAddRelationEditor extends BulkEditor {
}
}
+class BulkDeleteEditor extends BulkEditor {
+ constructor(hostNode) {
+ super(hostNode);
+ this._hostNode.addEventListener("submit", (e) =>
+ this._evtFormSubmit(e)
+ );
+ }
+
+ _evtFormSubmit(e) {
+ e.preventDefault();
+ this.dispatchEvent(
+ new CustomEvent("deleteSelectedPosts", { detail: {} })
+ );
+ }
+
+ _evtOpenLinkClick(e) {
+ e.preventDefault();
+ this.toggleOpen(true);
+ this.dispatchEvent(new CustomEvent("open", { detail: {} }));
+ }
+
+ _evtCloseLinkClick(e) {
+ e.preventDefault();
+ this.toggleOpen(false);
+ this.dispatchEvent(new CustomEvent("close", { detail: {} }));
+ }
+}
+
class PostsHeaderView extends events.EventTarget {
constructor(ctx) {
super();
@@ -247,6 +275,13 @@ class PostsHeaderView extends events.EventTarget {
this._evtCloseMetricsBtnClick(e)
);
}
+
+ if (this._bulkEditDeleteNode) {
+ this._bulkDeleteEditor = new BulkDeleteEditor(
+ this._bulkEditDeleteNode
+ );
+ this._bulkEditors.push(this._bulkDeleteEditor);
+ }
for (let editor of this._bulkEditors) {
editor.addEventListener("submit", (e) => {
@@ -268,6 +303,8 @@ class PostsHeaderView extends events.EventTarget {
this._openBulkEditor(this._bulkSafetyEditor);
} else if (ctx.parameters.relations && this._bulkAddRelationEditor) {
this._openBulkEditor(this._bulkAddRelationEditor);
+ } else if (ctx.parameters.delete && this._bulkDeleteEditor) {
+ this._openBulkEditor(this._bulkDeleteEditor);
}
if (ctx.parameters.metrics && this._metricsBlockNode) {
this._toggleMetricsBlock(true);
@@ -322,6 +359,10 @@ class PostsHeaderView extends events.EventTarget {
return this._hostNode.querySelector(".bulk-add-relation");
}
+ get _bulkEditDeleteNode() {
+ return this._hostNode.querySelector(".bulk-edit-delete");
+ }
+
get _metricsButtonHolderNode() {
return this._hostNode.querySelector(".metrics-btn-holder");
}
@@ -400,9 +441,8 @@ class PostsHeaderView extends events.EventTarget {
e.target.classList.toggle("disabled");
const safety = e.target.getAttribute("data-safety");
let browsingSettings = settings.get();
- browsingSettings.listPosts[safety] = !browsingSettings.listPosts[
- safety
- ];
+ browsingSettings.listPosts[safety] =
+ !browsingSettings.listPosts[safety];
settings.save(browsingSettings, true);
this.dispatchEvent(
new CustomEvent("navigate", {
@@ -498,6 +538,10 @@ class PostsHeaderView extends events.EventTarget {
this._bulkAddRelationEditor && this._bulkAddRelationEditor.opened
? this._ctx.parameters.relations || " "
: null;
+ parameters.delete =
+ this._bulkDeleteEditor && this._bulkDeleteEditor.opened
+ ? "1"
+ : null;
this.dispatchEvent(
new CustomEvent("navigate", { detail: { parameters: parameters } })
);
diff --git a/client/js/views/posts_page_view.js b/client/js/views/posts_page_view.js
index 9885602..463468a 100644
--- a/client/js/views/posts_page_view.js
+++ b/client/js/views/posts_page_view.js
@@ -47,6 +47,13 @@ class PostsPageView extends events.EventTarget {
);
}
}
+
+ const deleteFlipperNode = this._getDeleteFlipperNode(listItemNode);
+ if (deleteFlipperNode) {
+ deleteFlipperNode.addEventListener("click", (e) =>
+ this._evtBulkToggleDeleteClick(e, post)
+ );
+ }
}
this._syncBulkEditorsHighlights();
@@ -68,6 +75,10 @@ class PostsPageView extends events.EventTarget {
return listItemNode.querySelector(".relation-flipper");
}
+ _getDeleteFlipperNode(listItemNode) {
+ return listItemNode.querySelector(".delete-flipper");
+ }
+
_evtPostChange(e) {
const listItemNode = this._postIdToListItemNode[e.detail.post.id];
for (let node of listItemNode.querySelectorAll("[data-disabled]")) {
@@ -128,6 +139,20 @@ class PostsPageView extends events.EventTarget {
);
}
+ _evtBulkToggleDeleteClick(e, post) {
+ e.preventDefault();
+ const linkNode = e.target;
+ linkNode.classList.toggle("delete");
+ this.dispatchEvent(
+ new CustomEvent("markForDeletion", {
+ detail: {
+ post,
+ delete: linkNode.classList.contains("delete"),
+ },
+ })
+ );
+ }
+
_syncBulkEditorsHighlights() {
for (let listItemNode of this._listItemNodes) {
const postId = listItemNode.getAttribute("data-post-id");
@@ -159,6 +184,16 @@ class PostsPageView extends events.EventTarget {
let related = this._ctx.parameters.relations.includes(post.id);
relationFlipperNode.classList.toggle("related", related);
}
+
+ const deleteFlipperNode = this._getDeleteFlipperNode(listItemNode);
+ if (deleteFlipperNode) {
+ deleteFlipperNode.classList.toggle(
+ "delete",
+ this._ctx.bulkEdit.markedForDeletion.some(
+ (x) => x.id == postId
+ )
+ );
+ }
}
}
}
diff --git a/client/js/views/user_tokens_view.js b/client/js/views/user_tokens_view.js
index 68707f0..9ba9e7c 100644
--- a/client/js/views/user_tokens_view.js
+++ b/client/js/views/user_tokens_view.js
@@ -72,9 +72,8 @@ class UserTokenView extends events.EventTarget {
_evtDelete(e) {
e.preventDefault();
- const userToken = this._tokens[
- parseInt(e.target.getAttribute("data-token-id"))
- ];
+ const userToken =
+ this._tokens[parseInt(e.target.getAttribute("data-token-id"))];
this.dispatchEvent(
new CustomEvent("delete", {
detail: {
@@ -110,9 +109,8 @@ class UserTokenView extends events.EventTarget {
_evtChangeNoteClick(e) {
e.preventDefault();
- const userToken = this._tokens[
- parseInt(e.target.getAttribute("data-token-id"))
- ];
+ const userToken =
+ this._tokens[parseInt(e.target.getAttribute("data-token-id"))];
const text = window.prompt(
"Please enter the new name:",
userToken.note !== null ? userToken.note : undefined