diff options
Diffstat (limited to 'client/js')
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 |