From e62ebb05ba4808428cb4904631591a658bc90ea0 Mon Sep 17 00:00:00 2001 From: akuma06 Date: Fri, 2 Jun 2017 04:51:44 +0200 Subject: [PATCH] Mass edit mod api JS (done) (#868) * Mass Edit MOD api JS (WIP) In continuity with the mass edit mod api, this is the javascript use of it. ##What does it do? * Delete of multiple torrents on index/search * Category change of multiple torrents * Change of owner of multiple torrents * Lock & delete of multiple torrents ##How? * New toolbar only visible for mods * Checkboxes added only for mods * Selection and click on the button in toolbar * Nothing is submitted, you have to review the changes in a modal window listing them. * Then the ajax queries are initialized one at a time with a progression bar * You can always at any moment delete entries from the queuing list * Improved progress bar * Deleting part almost done Improved modal design All dom interactions should be done Prepared Query for only one callback Improved Modal to keep a link to the active modal * Finished =D Added some translation string * Forgot the refreshing of the page Just an option that can be disabled by making refreshTimeout to 0 --- public/css/main.css | 210 +++++++++++++++ public/js/modal.js | 84 ++++++ public/js/query.js | 27 +- public/js/torrentsMod.js | 364 ++++++++++++++++++++++++++ router/modpanel.go | 5 + templates/admin/paneltorrentedit.html | 1 - templates/home.html | 118 ++++++++- translations/en-us.all.json | 32 +++ 8 files changed, 832 insertions(+), 9 deletions(-) create mode 100644 public/js/modal.js create mode 100644 public/js/torrentsMod.js diff --git a/public/css/main.css b/public/css/main.css index d555d2d7..84904d5d 100644 --- a/public/css/main.css +++ b/public/css/main.css @@ -600,6 +600,12 @@ div.profile-content.box > nav > ul > li { height: 13px; width: 13px; } +.trash-icon { + width: 16px; + height: 16px; + display: inline-block; + background-image: url(data:image/svg+xml;utf8;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iaXNvLTg4NTktMSI/Pgo8IS0tIEdlbmVyYXRvcjogQWRvYmUgSWxsdXN0cmF0b3IgMTkuMC4wLCBTVkcgRXhwb3J0IFBsdWctSW4gLiBTVkcgVmVyc2lvbjogNi4wMCBCdWlsZCAwKSAgLS0+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeD0iMHB4IiB5PSIwcHgiIHZpZXdCb3g9IjAgMCA1MTIgNTEyIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCA1MTIgNTEyOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSIgd2lkdGg9IjE2cHgiIGhlaWdodD0iMTZweCI+CjxnPgoJPGc+CgkJPHBhdGggZD0iTTQ2NS40MjMsNDguMjQxaC0xMzcuNjFWMjMuOTU1QzMyNy44MTMsMTAuNzQ2LDMxNy4wODIsMCwzMDMuODkzLDBoLTk1Ljc4NWMtMTMuMTksMC0yMy45MiwxMC43NDYtMjMuOTIsMjMuOTU1VjQ4LjI0ICAgIEg0Ni41NzdjLTYuNjU1LDAtMTIuMDQ5LDUuMzk0LTEyLjA0OSwxMi4wNDljMCw2LjY1NSw1LjM5NCwxMi4wNDksMTIuMDQ5LDEyLjA0OWgyMi4zMzJsMTUuMjI4LDM5Ni4zOTYgICAgQzg1LjA2OSw0OTIuOTk1LDEwNC44MTgsNTEyLDEyOS4wOTksNTEyaDI1My44MDRjMjQuMjgxLDAsNDQuMDMtMTkuMDA2LDQ0Ljk2LTQzLjI2N2wxNS4yMjgtMzk2LjM5NmgyMi4zMzIgICAgYzYuNjUzLDAsMTIuMDQ5LTUuMzk0LDEyLjA0OS0xMi4wNDlDNDc3LjQ3Miw1My42MzUsNDcyLjA3OCw0OC4yNDEsNDY1LjQyMyw0OC4yNDF6IE0yMDguMjg1LDI0LjA5N2g5NS40M3YyNC4xNDNoLTk1LjQzVjI0LjA5N3ogICAgIE00MDMuNzg0LDQ2Ny44MDljLTAuNDMzLDExLjI2OC05LjYwNSwyMC4wOTQtMjAuODgyLDIwLjA5NEgxMjkuMDk5Yy0xMS4yNzYsMC0yMC40NDgtOC44MjctMjAuODgyLTIwLjA5NUw5My4wMjUsNzIuMzM4aDMyNS45NTIgICAgTDQwMy43ODQsNDY3LjgwOXoiIGZpbGw9IiMwMDAwMDAiLz4KCTwvZz4KPC9nPgo8Zz4KCTxnPgoJCTxwYXRoIGQ9Ik0xODIuNjMsMTgxLjU3MWMtMC4xMjctNi41NzUtNS40OTQtMTEuODE3LTEyLjA0Mi0xMS44MTdjLTAuMDc4LDAtMC4xNTgsMC0wLjIzNiwwLjAwMiAgICBjLTYuNjUyLDAuMTI4LTExLjk0Myw1LjYyNi0xMS44MTUsMTIuMjc4bDMuNzgxLDE5Ni42MzRjMC4xMjYsNi41NzUsNS40OTUsMTEuODE3LDEyLjA0MiwxMS44MTdjMC4wNzgsMCwwLjE1OCwwLDAuMjM2LTAuMDAyICAgIGM2LjY1My0wLjEyOCwxMS45NDMtNS42MjQsMTEuODE1LTEyLjI3OEwxODIuNjMsMTgxLjU3MXoiIGZpbGw9IiMwMDAwMDAiLz4KCTwvZz4KPC9nPgo8Zz4KCTxnPgoJCTxwYXRoIGQ9Ik0yNTUuOTk4LDE2OS43NTNjLTYuNjU0LDAtMTIuMDQ5LDUuMzk0LTEyLjA0OSwxMi4wNDl2MTk2LjYzNGMwLDYuNjU0LDUuMzk0LDEyLjA0OSwxMi4wNDksMTIuMDQ5ICAgIGM2LjY1NSwwLDEyLjA0OS01LjM5NCwxMi4wNDktMTIuMDQ5VjE4MS44MDJDMjY4LjA0NywxNzUuMTQ4LDI2Mi42NTMsMTY5Ljc1MywyNTUuOTk4LDE2OS43NTN6IiBmaWxsPSIjMDAwMDAwIi8+Cgk8L2c+CjwvZz4KPGc+Cgk8Zz4KCQk8cGF0aCBkPSJNMzQxLjY0NSwxNjkuNzU2Yy02LjYyOC0wLjE0Ny0xMi4xNTEsNS4xNjItMTIuMjc4LDExLjgxNWwtMy43ODEsMTk2LjYzNGMtMC4xMjksNi42NTMsNS4xNjIsMTIuMTUsMTEuODE1LDEyLjI3OCAgICBjMC4wNzgsMC4wMDEsMC4xNTgsMC4wMDIsMC4yMzYsMC4wMDJjNi41NDYsMCwxMS45MTYtNS4yNDQsMTIuMDQyLTExLjgxN2wzLjc4MS0xOTYuNjM0ICAgIEMzNTMuNTg4LDE3NS4zOCwzNDguMjk5LDE2OS44ODMsMzQxLjY0NSwxNjkuNzU2eiIgZmlsbD0iIzAwMDAwMCIvPgoJPC9nPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+CjxnPgo8L2c+Cjwvc3ZnPgo=) +} /* Filelist */ .filelist-control { cursor: pointer; @@ -712,6 +718,210 @@ input.filelist-checkbox:checked + table.table-filelist { .editor-preview-side { top: 110px; } + +/* Mod Tools */ +.modtools { + position: fixed; + top:60px; + padding: 1px 10px; + max-width: 1060px; + width:100%; + background: #111; + border: 1px solid #222; + height: 25px; +} +.tr-cb { + width: 20px; + text-align: left; + display: none; +} +.modtools .actions { + display: none; +} +.modtools span.btn-group { + margin-left: 20px; +} +.modtools .cb_action { + height: 100%; +} +/* Modal box */ +/* The Modal (background) */ +.modal { + display: none; /* Hidden by default */ + position: fixed; /* Stay in place */ + z-index: 4; /* Sit on top */ + left: 0; + top: 0; + width: 100%; /* Full width */ + height: 100%; /* Full height */ + overflow: auto; /* Enable scroll if needed */ + background-color: rgb(0,0,0); /* Fallback color */ + background-color: rgba(0,0,0,0.4); /* Black w/ opacity */ +} + +/* Modal Content/Box */ +/* Modal Header */ +.modal-header { + padding: 2px 16px; + background: #111; /* Old browsers */ + background: -moz-linear-gradient(bottom, #222 0%, #111 100%); + background: -webkit-linear-gradient(bottom, #222 0%, #111 100%); + background: linear-gradient(to top, #222 0%, #111 100%); + color: white; +} + +/* Modal Body */ +.modal-body {padding: 2px 16px;} + +/* Modal Footer */ +.modal-footer { + padding: 2px 16px; + background: #222; /* Old browsers */ + background: -moz-linear-gradient(bottom, #111 0%, #222 100%); + background: -webkit-linear-gradient(bottom, #111 0%, #222 100%); + background: linear-gradient(to top, #111 0%, #222 100%); + color: white; +} +.modal-footer span { + float: right; + margin-right: 20px; +} +.modal-footer span button { + margin-right: 5px; + padding: 1em 2em; + background: none; + border: 1px solid white; + border-radius: 3px; + color: white; + font-weight: bold; +} +.modal-footer span .close { + font-size: 1em; + float: none; + background: #E84C4C; +} +.modal-footer span #confirm_changes { + font-size: 1em; + background: #98D9A8; +} + +/* Modal Content */ +.modal-content { + position: relative; + background-color: #fefefe; + margin: auto; + padding: 0; + border: 1px solid #888; + width: 80%; + margin-top: 20%; + max-width: 1000px; + box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2),0 6px 20px 0 rgba(0,0,0,0.19); + -webkit-animation-name: animatetop; + -webkit-animation-duration: 0.4s; + animation-name: animatetop; + animation-duration: 0.4s +} +.modal .close { + float: right; + font-size: 2em; + cursor: pointer; +} + +.modal a.icon { + width: 16px; + height: 16px; +} + +.modal .edit_list { + background: #98D9A8; + padding: 0.3em; + margin-bottom: 3px; +} + +.modal .delete_list { + background: #E84C4C; + padding: 0.3em; + margin-bottom: 3px; +} + +.modal .delete_item { + font-weight: bold; + padding: 3px; +} + +.modal .edit_item { + font-weight: bold; + padding: 3px; +} +.delete_item a, .delete_item span.infos, .edit_item a, .edit_item span.infos { + float: right; +} +.delete_list span.infos, .edit_list span.infos { + float: right; + margin-top: 1em; +} +.modal-body div .title h3 { + cursor: pointer; +} +.modal .list { + border: 1px solid #222; + margin-bottom: 0.5em; + display: none; +} +.modal .list .delete_item:nth-child(even), .modal .list .edit_item:nth-child(even) {background: #CCC} +.modal .list .delete_item:nth-child(odd), .modal .list .edit_item:nth-child(odd) {background: #FFF} + +.modal-footer button { + cursor: pointer; +} + +.modal .close:hover, .modal-footer span button:hover, .modal a.icon:hover { + opacity: 0.7; +} + +/* Add Animation */ +@-webkit-keyframes animatetop { + from {top: -300px; opacity: 0} + to {top: 0; opacity: 1} +} + +@keyframes animatetop { + from {top: -300px; opacity: 0} + to {top: 0; opacity: 1} +} +.progress-bar { + height:1.5em; + width: 100%; + background: #333; + padding: 0; +} + +.progress-green { + height: 100%; + margin: 0; + background: #22A243; + text-align: right; + padding-right: 2px; + color: white; + padding-top: 0.25em; +} + +.logs_mess { + background: #ddd; + height: 100px; + overflow: auto; + padding: 3px; + font-style: italic; +} + +.logs_mess div.success { + color: #22A243; +} + +.logs_mess div.error { + color: #893636; + .error-text, .success-text { text-align: center; } + diff --git a/public/js/modal.js b/public/js/modal.js new file mode 100644 index 00000000..1ae89053 --- /dev/null +++ b/public/js/modal.js @@ -0,0 +1,84 @@ +// Get the modal +var Modal = { + active: 0, + Init: function (params) { + var elements = params.elements + var button = (params.button != undefined) ? params.button : false + if (elements.innerHTML != undefined) { + + } else { + var nbEl = elements.length + for (var i=0; i < nbEl; i++) { + var modal = elements[i]; + this.addModal(modal, button, i, params.before, params.after, params.close) + } + } + }, + addModal: function(modal, btn, i, before_callback, after_callback, close_callback) { + var isBtnArray = false; + // Get the button that opens the modal + if (!btn) { + btn = document.getElementById("modal_btn_"+modal.id) + } else if (btn.match(/^#/)) { + btn = document.getElementById(btn.substr(1)); + } else if (btn.match(/^\./)) { + btn = document.getElementsByClassName(btn.substr(1)); + isBtnArray = true; + } else { + console.error("Couldn't find the button") + return + } + if ((isBtnArray) && (i > 0) && (btn.length > 0) && (btn.length > i)) { + btn[i].addEventListener("click", function(e) { + if (before_callback != undefined) before_callback() + modal.style.display = "block"; + Modal.active = modal; + if (after_callback != undefined) after_callback() + e.preventDefault(); + }); + } else { + btn = (isBtnArray) ? btn[0] : btn; + // When the user clicks on the button, open the modal + btn.addEventListener("click", function(e) { + if (before_callback != undefined) before_callback() + modal.style.display = "block"; + Modal.active = modal; + if (after_callback != undefined) after_callback() + e.preventDefault(); + }); + } + // Get the element that closes the modal + var span = document.querySelectorAll("#"+modal.id+" .close")[0] + // When the user clicks on (x), close the modal + span.addEventListener("click", function(e) { + modal.style.display = "none"; + Modal.active = 0; + if (close_callback != undefined) close_callback() + e.preventDefault(); + }); + // When the user clicks anywhere outside of the modal, close it + window.addEventListener("click", function(event) { + if (event.target == modal) { + modal.style.display = "none"; + Modal.active = 0; + if (close_callback != undefined) close_callback() + } + }); + }, + CloseActive: function() { + if (this.active != 0) { + this.active.style.display= "none"; + this.active = 0; + } + }, + GetActive: function() { + return this.active; + }, + Open: function(q) { + var modal = document.querySelector(q); + if (modal != undefined) { + modal.style.display= "none"; + this.active = modal; + } + } +}; \ No newline at end of file diff --git a/public/js/query.js b/public/js/query.js index d3bd1cdf..20c19192 100644 --- a/public/js/query.js +++ b/public/js/query.js @@ -1,6 +1,6 @@ var Query = { Failed:0, - MaxConsecutingFailing:-1, + MaxFail: 10, Get: function(url, renderer, callback) { var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); @@ -9,15 +9,34 @@ var Query = { if (this.status == 200) { Query.Failed = 0; renderer(this.response); - callback(this.response); + if (callback != undefined) callback(this.response); } else { console.log("Error when refresh") Query.Failed++; console.log("Attempt to refresh "+Query.Failed+"..."); - if ((Query.MaxConsecutingFailing == -1) || (Query.Failed < Query.MaxConsecutingFailing)) Query.Get(url, renderer, callback); + if ((Query.MaxFail == -1) || (Query.Failed < Query.MaxFail)) Query.Get(url, renderer, callback); else console.error("Too many attempts, stopping...") } }; xhr.send(); + }, + Post: function(url, postArgs, callback) { + var xhr = new XMLHttpRequest(); + xhr.open('POST', url, true); + xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded"); + xhr.responseType = 'json'; + xhr.onload = function(e) { + if (this.status == 200) { + Query.Failed = 0; + if (callback != undefined) callback(this.response); + } else { + console.log("Error when refresh") + Query.Failed++; + console.log("Attempt to refresh "+Query.Failed+"..."); + if ((Query.MaxFail == -1) || (Query.Failed < Query.MaxFail)) Query.Post(url, postArgs, callback); + else console.error("Too many attempts, stopping...") + } + }; + xhr.send(postArgs); } -} \ No newline at end of file +}; \ No newline at end of file diff --git a/public/js/torrentsMod.js b/public/js/torrentsMod.js new file mode 100644 index 00000000..ac0e637d --- /dev/null +++ b/public/js/torrentsMod.js @@ -0,0 +1,364 @@ +var TorrentsMod = { + // Variables that can be modified to change the dom interactions + show_hide_button: "show_actions", + btn_class_action: "cb_action", + btn_class_submit: "cb_submit", + progress_bar_id: "progress_modtool", + status_input_name: "status_id", + owner_input_name: "owner_id", + category_input_name: "category_id", + delete_btn: "delete", + lock_delete_btn: "lock_delete", + edit_btn: "edit", + refreshTimeout: 3000, + // Internal variables used for processing the request + selected: [], + queued: [], + unique_id:1, + error_count:0, + progress_count: 0, + progress_max: 0, + pause: false, + + // Init method + Create: function() { + var sh_btn = document.getElementById(TorrentsMod.show_hide_button); + var btn_actions = document.getElementsByClassName(this.btn_class_action) + var btn_submit = document.getElementsByClassName(this.btn_class_submit) + btn_submit[0].disabled = true; + for (var i=0; i < btn_actions.length; i++) { + btn_actions[i].disabled = true; + switch (btn_actions[i].id) { + case this.delete_btn: + btn_actions[i].addEventListener("click", this.Delete) + break; + case this.lock_delete_btn: + btn_actions[i].addEventListener("click", this.LockDelete) + break; + case this.edit_btn: + btn_actions[i].addEventListener("click", this.Edit) + break; + default: + break; + } + } + for (var i=0; i < this.checkboxes.length; i++) { + checkbox = this.checkboxes[i]; + checkbox.addEventListener("change", this.checkboxEventHandler) + } + sh_btn.addEventListener("click", function(e) { + var display = "inline" + var divActions = this.nextElementSibling; + console.log(divActions) + if (divActions.style.display == "inline") { + display = "none"; + } + divActions.style.display = display; + var td_cbs = document.getElementsByClassName("tr-cb") + for (var i=0; i < td_cbs.length; i++) { + td_cb = td_cbs[i]; + td_cb.style.display = (display == "inline") ? "table-cell" : "none"; + } + var toggleText = this.dataset.toggleText; + this.dataset.toggleText = this.innerText; + this.innerText = toggleText; + }); + }, + // generate a unique id for a query + getId: function(){ + return this.unique_id++; + }, + + // UI Methods + selectAll: function(bool) { + var l = TorrentsMod.checkboxes.length; + for (var i = 0; i < l; i++) { + TorrentsMod.checkboxes[i].checked = bool; + TorrentsMod.checkboxEventHandlerFunc(TorrentsMod.checkboxes[i]); + } + }, + disableBtnActions: function() { + var btn_actions = document.getElementsByClassName(this.btn_class_action) + for (var i=0; i < btn_actions.length; i++) { + btn_actions[i].disabled = true; + } + }, + enableBtnActions: function() { + var btn_actions = document.getElementsByClassName(this.btn_class_action) + for (var i=0; i < btn_actions.length; i++) { + btn_actions[i].disabled = false; + } + }, + enableBtnSubmit: function() { + var btn_submit = document.getElementsByClassName(this.btn_class_submit) + btn_submit[0].disabled = false; + }, + enableApplyChangesBtn: function() { + var btn_apply_changes = document.getElementById("confirm_changes"); + btn_apply_changes.disabled=false; + }, + disableBtnSubmit: function() { + var btn_submit = document.getElementsByClassName(this.btn_class_submit) + btn_submit[0].disabled = true; + }, + disableApplyChangesBtn: function() { + var btn_apply_changes = document.getElementById("confirm_changes"); + btn_apply_changes.disabled=true; + }, + removeDivFromList: function(i) { + var queueAction = this.queued[i]; + var parentDiv = document.getElementById(queueAction.unique_id).parentNode; + parentDiv.removeChild(document.getElementById(queueAction.unique_id)); + }, + removeFromParent: function(el) { + var parentDiv = el.parentNode; + parentDiv.removeChild(el); + }, + generatingModal: function() { + listLength = this.queued.length; + var div = {"edit": "", "delete": ""}; + for (var i=0; i < listLength; i++) { + var listHTML = ""; + for(key in this.queued[i].selection) { + var selection = this.queued[i].selection[key]; + selection.key = i; + listHTML += Templates.Render("torrents."+this.queued[i].action+".item", selection); + } + this.queued[i].list = listHTML; + this.queued[i].key = i; + div[this.queued[i].action] += Templates.Render("torrents."+this.queued[i].action+".block", this.queued[i]); + } + this.progress_count = 0; + this.progress_max = listLength; + document.querySelector(".modal .edit_changes").innerHTML = div["edit"]; + document.querySelector(".modal .delete_changes").innerHTML = div["delete"]; + }, + toggleList: function(el) { + el.parentNode.nextSibling.style.display = (el.parentNode.nextSibling.style.display != "block") ? "block" : "none" + }, + addToLog: function(type, msg) { + var logDiv = document.querySelector(".modal .logs_mess"); + if (logDiv.style.display == "none") logDiv.style.display = "block" + logDiv.innerHTML += Templates.Render("torrents.logs."+type, msg); + }, + updateProgressBar: function() { + document.querySelector("#"+this.progress_bar_id).style.display = "block"; + var progress_green = document.querySelector("#"+this.progress_bar_id+" .progress-green"); + var perc = this.progress_count/this.progress_max*100; + progress_green.style.width=perc+"%"; + progress_green.innerText = this.progress_count+"/"+this.progress_max; + }, + resetModal: function() { + var logDiv = document.querySelector(".modal .logs_mess"); + logDiv.style.display = "none"; + document.querySelector("#"+this.progress_bar_id).style.display = "none"; + logDiv.innerHTML = ""; + document.querySelector(".modal .edit_changes").innerHTML = ""; + document.querySelector(".modal .delete_changes").innerHTML = ""; + this.enableApplyChangesBtn(); + }, + statusToClassName: function(status) { + var className = ["", "normal", "remake", "trusted", "aplus", ""] + return className[status]; + }, + + // Selection Management Methods + addToSelection: function (torrent) { + this.selected[torrent.id] = torrent; + }, + removeFromSelection: function(torrent) { + delete this.selected[torrent.id]; + for (t in this.selected) { + return + } + this.selected = []; + }, + + // Query Queue Management Methods + AddToQueue: function(QueueAction) { + QueueAction.unique_id = this.getId(); // used for DOM interaction + this.queued.push(QueueAction); + this.enableBtnSubmit() + }, + RemoveFromQueueAfterItems: function(i) { + for (t in this.queued[i].selection) { + this.RemoveItemFromQueue(i, t); + } + }, + RemoveFromQueue: function(i) { + this.progress_max = (this.progress_max - 1 >= 0) ? this.progress_max-1 : 0; + this.RemoveFromQueueAction(i) + return false; + }, + RemoveFromQueueAction: function(i) { + this.removeFromParent(document.getElementById("list_"+this.queued[i].unique_id)); + this.queued.splice(i, 1); + if (this.queued.length == 0) { + this.disableBtnSubmit(); + if (this.progress_max>0) { + this.disableApplyChangesBtn(); + } else { + Modal.CloseActive(); + } + } + }, + formatSelectionToQuery: function(selection) { + var format = ""; + for (s in selection) { + format += "&torrent_id="+selection[s].id + } + return (format != "") ? format.substr(1) : "" + }, + RemoveItemFromQueue: function(i, id) { + this.removeFromParent(document.getElementById("list_item_"+id)); + delete this.queued[i].selection[id]; + document.getElementById("torrent_cb_"+id).checked=false; + document.getElementById("torrent_"+id).style.display=""; + var test = 0; + for (t in this.queued[i].selection) { + test++ + break; + } + if (test == 0) this.RemoveFromQueue(i); + return false; + }, + newQueryAttempt: function(queryUrl, queryPost, callback) { + Query.Post(queryUrl, queryPost, function (response) { + if ((response.length == 0)||(!response.ok)) { // Query has failed + var errorMsg = response.errors.join("
"); + TorrentsMod.addToLog("error", errorMsg); + TorrentsMod.error_count++; + if (TorrentsMod.error_count < 2) { + TorrentsMod.addToLog("success", "Trying a new attempt..."); + TorrentsMod.newQueryAttempt(queryUrl, queryPost, callback) + } else { + TorrentsMod.addToLog("error", "The query ("+queryUrl+"?"+queryPost+") seems broken!"); + if (callback != undefined) { + TorrentsMod.addToLog("error", "Passing to the next query..."); + callback(response) // So we can query only one item + } + } + } else { + var succesMsg = (response.infos != null) ? response.infos.join("
") : "Query executed with success!"; + TorrentsMod.addToLog("success", succesMsg) + if (callback != undefined) callback(response) // So we can query only one item + } + }); + }, + QueryQueue: function(i, callback) { + if (this.queued.length > 0) { + var QueueAction = this.queued[i]; // we clone it so we can delete it safely + this.RemoveFromQueueAction(i); + var queryPost = ""; + var queryUrl = "/mod/api/torrents"; + QueueAction.queryPost = TorrentsMod.formatSelectionToQuery(QueueAction.selection) + if (QueueAction.action == "delete") { + queryPost="action="+QueueAction.action; + queryPost+="&withreport="+QueueAction.withReport; + queryPost += "&status="+((QueueAction.status != undefined) ? QueueAction.status : ""); + queryPost += "&"+QueueAction.queryPost; // we add torrent id + } else if (QueueAction.action == "edit") { + queryPost="action=multiple"; + queryPost += "&status="+QueueAction.status; + queryPost += "&owner="+QueueAction.owner; + queryPost += "&category="+QueueAction.category; + queryPost += "&"+QueueAction.queryPost; // we add torrent id + } + TorrentsMod.newQueryAttempt(queryUrl, queryPost, callback) + } else { + TorrentsMod.addToLog("success", "All operations are done!") + if (TorrentsMod.refreshTimeout > 0) { + TorrentsMod.addToLog("success", "Refreshing the page in 3 seconds...") + setTimeout(function(){ + window.location.reload() + }, TorrentsMod.refreshTimeout); + } + } + }, + QueryLoop: function() { + if (TorrentsMod.progress_count <= TorrentsMod.progress_max) { + TorrentsMod.updateProgressBar() + TorrentsMod.progress_count++; + if (TorrentsMod.progress_count > TorrentsMod.progress_max) TorrentsMod.progress_count = TorrentsMod.progress_max; + TorrentsMod.QueryQueue(0, TorrentsMod.QueryLoop); + } + }, + + // Event Handlers + checkboxEventHandler: function(e) { + var el = e.target; + TorrentsMod.checkboxEventHandlerFunc(el); + }, + checkboxEventHandlerFunc: function(el) { + var name = el.dataset.name; + var id = el.value; + if (el.checked) TorrentsMod.addToSelection({name:name, id:id}); + else TorrentsMod.removeFromSelection({name:name, id:id}); + if (TorrentsMod.selected.length > 0) TorrentsMod.enableBtnActions(); + else TorrentsMod.disableBtnActions(); + }, + + // Action Methods + DeleteHandler: function(locked) { + var withReport = confirm("Do you want to delete the reports along the selected torrents?") + var selection = TorrentsMod.selected; + if (locked) + TorrentsMod.AddToQueue({ action: "delete", + withReport: withReport, + selection: selection, + queryPost: "", + infos: "with lock"+ ((withReport) ? " and reports" : ""), + status: "5" }); + else TorrentsMod.AddToQueue({ + action: "delete", + withReport: withReport, + selection: selection, + infos: (withReport) ? "with reports" : "", + queryPost: ""}); + for (i in selection) document.getElementById("torrent_"+i).style.display="none"; + TorrentsMod.selected = [] + TorrentsMod.disableBtnActions(); + }, + Delete: function(e) { + TorrentsMod.DeleteHandler(false); + e.preventDefault(); + }, + LockDelete: function(e) { + TorrentsMod.DeleteHandler(true); + e.preventDefault(); + }, + Edit: function(e) { + var selection = TorrentsMod.selected; + var status = document.querySelector(".modtools *[name='"+TorrentsMod.status_input_name+"']").value; + var owner_id = document.querySelector(".modtools *[name='"+TorrentsMod.owner_input_name+"']").value; + var category = document.querySelector(".modtools *[name='"+TorrentsMod.category_input_name+"']").value; + var infos = ""; + infos += (status != "") ? "status: "+status : ""; + infos += (owner_id != "") ? " owner_id: "+owner_id : ""; + infos += (category != "") ? " category: "+category : ""; + TorrentsMod.AddToQueue({ + action: "edit", + selection: selection, + queryPost: "", // We don't format now, we wait until the query is sent + infos: (infos != "" ) ? "with "+infos : "No changes", + status: status, + category: category, + owner: owner_id }); + if (status != "") { + for (i in selection) document.getElementById("torrent_"+i).className="torrent-info "+TorrentsMod.statusToClassName(status); + } + TorrentsMod.selected = [] + TorrentsMod.disableBtnActions(); + e.preventDefault(); + }, + ApplyChanges: function() { + this.pause = false; + this.QueryLoop(); + } +}; + +// Load torrentMods when DOM is ready +document.addEventListener("DOMContentLoaded", function() { + TorrentsMod.checkboxes = document.querySelectorAll("input[name='torrent_id']"); + TorrentsMod.Create(); +}); \ No newline at end of file diff --git a/router/modpanel.go b/router/modpanel.go index 8ff1e8b8..80928001 100644 --- a/router/modpanel.go +++ b/router/modpanel.go @@ -609,6 +609,11 @@ func torrentManyAction(r *http.Request) { /* Changes are done, we save */ db.ORM.Unscoped().Model(&torrent).UpdateColumn(&torrent) } else if action == "delete" { + if status == model.TorrentStatusBlocked { // Then we should lock torrents before deleting them + torrent.Status = status + messages.AddInfoTf("infos", "torrent_moved", torrent.Name) + db.ORM.Unscoped().Model(&torrent).UpdateColumn(&torrent) // We must save it here and soft delete it after + } _, err = torrentService.DeleteTorrent(torrentID) if err != nil { messages.ImportFromError("errors", err) diff --git a/templates/admin/paneltorrentedit.html b/templates/admin/paneltorrentedit.html index 8c35371f..69721041 100644 --- a/templates/admin/paneltorrentedit.html +++ b/templates/admin/paneltorrentedit.html @@ -26,7 +26,6 @@ + {{end}} {{call $.T "category"}} {{call $.T "name"}}{{ genSortArrows .URL "1" }} @@ -39,10 +42,15 @@ Your browser does not support the audio element. {{ range .Models}} - + {{if eq .Status 4}}aplus{{end}}" id="torrent{{ .ID }}"> + {{ if HasAdmin $.User }} + + + + {{ end }} {{ if Sukebei }} @@ -88,11 +96,113 @@ Your browser does not support the audio element. + {{ if HasAdmin $.User }} +
+ + + + + + + + + + + + + + + + +
+ + + {{end}} {{end}} {{ define "footer_js"}} + +{{ if HasAdmin $.User }} + + +{{end}} {{end}} \ No newline at end of file diff --git a/translations/en-us.all.json b/translations/en-us.all.json index 21102c8e..67b3ce81 100644 --- a/translations/en-us.all.json +++ b/translations/en-us.all.json @@ -655,6 +655,10 @@ "id": "torrent_status_remake", "translation": "Remake" }, + { + "id": "torrent_status_blocked", + "translation": "Locked" + }, { "id": "profile_edit_page", "translation": "Edit %s's profile" @@ -935,6 +939,10 @@ "id": "edit", "translation": "Edit" }, + { + "id": "lock_delete", + "translation": "Lock & Delete" + }, { "id": "delete_definitely_torrent_warning", "translation": "You will not be able to recover the file, neither stop someone to reupload it!" @@ -1006,5 +1014,29 @@ { "id": "hide", "translation": "Hide" + }, + { + "id":"show_mod_tools", + "translation": "Show Mod Tools" + }, + { + "id":"hide_mod_tools", + "translation": "Hide Mod Tools" + }, + { + "id": "following_changes_applied", + "translation": "Following changes will be applied" + }, + { + "id": "changes_in_following_order", + "translation": "Changes will be made in the following order:" + }, + { + "id": "edit_changes", + "translation": "Edit Changes" + }, + { + "id": "delete_changes", + "translation": "Delete Changes" } ]