From 7c5db5975d11fa6888e2300ea8dd7e9c2c19e1a3 Mon Sep 17 00:00:00 2001 From: Carve Date: Sun, 7 Jan 2024 17:04:03 -0500 Subject: [PATCH] Fixes #41 and #45 --- private/css/Window.css | 1 + private/css/palette.css | 49 + private/css/vanillaSelectBox.css | 231 +++++ private/download.html | 8 + private/index.html | 11 + private/newcategory.html | 5 + private/rename_files.html | 491 ++++++++++ private/scripts/client.js | 28 +- private/scripts/contextmenu.js | 1263 ++++++++++++------------- private/scripts/dynamicTable.js | 28 +- private/scripts/lib/mocha-0.9.6.js | 3 +- private/scripts/mocha-init.js | 18 +- private/scripts/prop-files.js | 2 +- private/scripts/prop-webseeds.js | 3 +- private/shareratio.html | 58 +- private/upload.html | 27 +- private/views/filters.html | 4 + private/views/log.html | 6 +- private/views/preferences.html | 604 ++++++++---- private/views/preferencesToolbar.html | 7 +- private/views/rssDownloader.html | 22 + public/scripts/login.js | 4 +- 22 files changed, 1976 insertions(+), 897 deletions(-) create mode 100644 private/css/palette.css create mode 100644 private/css/vanillaSelectBox.css create mode 100644 private/rename_files.html diff --git a/private/css/Window.css b/private/css/Window.css index 0132958..2e50757 100644 --- a/private/css/Window.css +++ b/private/css/Window.css @@ -29,6 +29,7 @@ Required by: left: 0; position: absolute; /* This is also set in theme.js in order to make theme transitions smoother */ top: 0; + height: auto !important; /* fixes issues with modal height */ } /* diff --git a/private/css/palette.css b/private/css/palette.css new file mode 100644 index 0000000..10fcd99 --- /dev/null +++ b/private/css/palette.css @@ -0,0 +1,49 @@ +/* Adaptive color palette */ + +/* Default rules */ +* { + --color-accent-blue: hsl(210deg 65% 55%); + --color-text-blue: hsl(210deg 100% 55%); + --color-text-orange: hsl(26deg 100% 45%); + --color-text-red: hsl(0deg 100% 65%); + --color-text-green: hsl(110deg 94% 27%); + --color-text-white: hsl(0deg 0% 100%); + --color-text-disabled: hsl(0deg 0% 60%); + --color-text-default: hsl(0deg 0% 33%); + --color-background-blue: hsl(210deg 65% 55%); + --color-background-popup: hsl(0deg 0% 100%); + --color-background-default: hsl(0deg 0% 94%); + --color-background-hover: hsl(26deg 80% 60%); + --color-border-blue: hsl(210deg 42% 48%); + --color-border-default: hsl(0deg 0% 85%); +} + +:root { + color-scheme: light dark; +} + +/* Light corrections */ +@media (prefers-color-scheme: light) { + :root { + color-scheme: light; + } +} + +/* Dark corrections */ +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + } + + * { + --color-accent-blue: hsl(210deg 42% 48%); + --color-text-blue: hsl(210deg 88.1% 73.5%); + --color-text-orange: hsl(26deg 65% 70%); + --color-text-default: hsl(0deg 0% 90%); + --color-background-blue: hsl(210deg 42% 48%); + --color-background-popup: hsl(0deg 0% 20%); + --color-background-default: hsl(0deg 0% 25%); + --color-background-hover: hsl(26deg 50% 55%); + --color-border-default: hsl(0deg 0% 33%); + } +} diff --git a/private/css/vanillaSelectBox.css b/private/css/vanillaSelectBox.css new file mode 100644 index 0000000..80727e8 --- /dev/null +++ b/private/css/vanillaSelectBox.css @@ -0,0 +1,231 @@ +@import url("palette.css"); + +.hidden-search { + display: none !important; +} + +li[data-parent].closed { + display: none !important; +} + +li[data-parent].open:not(.hidden-search) { + display: block !important; +} + +.vsb-menu { + background-clip: padding-box; + background-color: var(--color-background-default); + border: 1px solid var(--color-border-default); + cursor: pointer; + display: block; + font-size: 11px; + position: absolute; + visibility: hidden; + z-index: 1000; /*Don't change*/ +} + +.vsb-js-search-zone { + min-height: 1.8em; + padding: 2px; + position: absolute; + width: 80%; + z-index: 1001; /*Don't change*/ +} + +.vsb-js-search-zone input { + border-radius: 4px; + height: 25px !important; + margin-left: 2px; + width: 96%; +} + +.vsb-main { + display: inline-block; + position: relative; + text-align: left; + vertical-align: top; /*Don't change*/ +} + +.vsb-menu ul { + cursor: pointer; + list-style: none; + margin: 0; + overflow-y: auto; + padding: 0; + user-select: none; + white-space: nowrap; +} + +li.disabled { + background-color: #999; + cursor: not-allowed; + opacity: 0.3; +} + +li.overflow { + background-color: #999; + cursor: not-allowed; + opacity: 0.3; +} + +li.short { + overflow: hidden; + text-overflow: ellipsis; +} + +.vsb-main button { + border: 1px solid var(--color-border-default); + border-radius: 4px; + min-width: 120px; + padding: 6px 12px; + text-align: left; + width: 100%; + z-index: 1; +} + +.vsb-main button.disabled { + cursor: not-allowed; + opacity: 0.65; +} + +.vsb-main .title { + margin-right: 6px; + user-select: none; +} + +.vsb-main ul { + white-space: nowrap; +} + +.vsb-menu li { + font-size: 12px; + padding: 4px 26px; +} + +.vsb-menu li:hover { + background-color: var(--color-background-hover); + color: var(--color-text-white); +} + +.vsb-menu li.grouped-option b { + display: inline-block; + margin-left: 10px; + transform: translate(-18px); +} + +.vsb-menu li.grouped-option.open span { + border-radius: 2px; + display: inline-block; + font-size: inherit; + height: 8px; + margin-top: -2px; + transform: translate(-38px) rotate(45deg); + width: 8px; +} + +.vsb-menu li.grouped-option.closed span { + border-radius: 2px; + display: inline-block; + font-size: inherit; + height: 8px; + transform: translate(-38px) rotate(-45deg); + width: 8px; +} + +.vsb-menu li.grouped-option i { + border: 1px solid; + border-radius: 3px; + display: inline-block; + float: left; + font-size: inherit; + font-weight: bold; + height: 11px; + margin-left: 22px; + margin-right: 2px; + margin-top: 0px; + padding: 1px 3px 2px; + width: 8px; +} + +.vsb-menu li.grouped-option.checked i::after { + content: ""; + display: inline-block; + float: left; + font-size: inherit; + height: 8px; + margin-left: 0px; + transform: rotate(45deg); + width: 5px; +} + +.vsb-menu :not(.multi) li.active { + margin-left: 7px; +} + +.vsb-menu :not(.multi) li.active::before { + border-bottom: 3px solid var(--color-border-blue); + border-radius: 2px; + border-right: 3px solid var(--color-border-blue); + content: ""; + display: inline-block; + font-size: inherit; + height: 10px; + margin-left: -18px; + transform: rotate(45deg); + width: 5px; +} + +.vsb-menu .multi li.grouped-option { + padding-left: 5px; +} + +.vsb-menu .multi li.grouped-option:hover { + color: rgb(52 31 112); + font-weight: bold; + text-decoration: underline; +} + +.vsb-menu .multi li:not(.grouped-option)::before { + background: var(--color-background-popup); + border: 1px solid; + border-radius: 3px; + content: ""; + display: inline-block; + float: left; + font-size: inherit; + font-weight: bold; + margin-left: -22px; + margin-right: 2px; + margin-top: 0px; + padding: 7px; +} + +.vsb-menu .multi li:not(.grouped-option).active::after { + border-bottom: 3px solid var(--color-border-blue); + border-right: 3px solid var(--color-border-blue); + content: ""; + display: inline-block; + float: left; + font-size: inherit; + height: 8px; + margin-left: -18px; + margin-top: 1px; + transform: rotate(45deg); + width: 5px; +} + +.caret { + border-left: 4px solid transparent; + border-right: 4px solid transparent; + border-top: 4px dashed; + border-top: 4px solid; + display: inline-block; + height: 0; + margin-left: 2px; + vertical-align: middle; + width: 0; +} + +li[data-parent] { + padding-left: 50px !important; +} diff --git a/private/download.html b/private/download.html index 064a309..630135a 100644 --- a/private/download.html +++ b/private/download.html @@ -80,6 +80,14 @@ + + + + + + + + diff --git a/private/index.html b/private/index.html index 510185b..21c7b84 100644 --- a/private/index.html +++ b/private/index.html @@ -30,6 +30,7 @@ + @@ -136,8 +137,13 @@
  • Remove Remove
  • Set location... Set location... +
  • +
  • Rename... Rename...
  • +
  • + Rename Files... Rename Files... +
  • Category Category @@ -174,6 +180,7 @@
  • Info hash v2 Info hash v2
  • Magnet link Magnet link
  • Torrent ID Torrent ID
  • +
  • Comment Comment
  • @@ -182,6 +189,7 @@
  • +
    diff --git a/private/newcategory.html b/private/newcategory.html index f2770b4..5c8a30a 100644 --- a/private/newcategory.html +++ b/private/newcategory.html @@ -45,6 +45,10 @@ $('savePath').set('value', window.qBittorrent.Misc.escapeHtml(uriSavePath)); $('savePath').focus(); } + else if (uriAction === "createSubcategory") { + $('categoryName').set('value', window.qBittorrent.Misc.escapeHtml(uriCategoryName)); + $('categoryName').focus(); + } else { $('categoryName').focus(); } @@ -96,6 +100,7 @@ }).send(); break; case "create": + case "createSubcategory": if (!verifyCategoryName(categoryName)) return; diff --git a/private/rename_files.html b/private/rename_files.html new file mode 100644 index 0000000..042c68c --- /dev/null +++ b/private/rename_files.html @@ -0,0 +1,491 @@ + + + + + + Renaming + + + + + + + + + + + +
    +
    +
    + + +
    +
    + +
    + + +
    +
    + + +
    +
    + + +
    +
    + + +
    + + +
    +
    + + +
    +
    + + +
    +
    +
    +
    + +
    +
    +
    + + +
    + +
    +
    +
    + + + + +
    +
    +
    + + + + + +
    +
    +
    +
    + + + diff --git a/private/scripts/client.js b/private/scripts/client.js index 37da5cb..50a9410 100644 --- a/private/scripts/client.js +++ b/private/scripts/client.js @@ -273,6 +273,7 @@ window.addEvent('load', function() { $("stalled_uploading_filter").removeClass("selectedFilter"); $("stalled_downloading_filter").removeClass("selectedFilter"); $("checking_filter").removeClass("selectedFilter"); + $("moving_filter").removeClass("selectedFilter"); $("errored_filter").removeClass("selectedFilter"); $(f + "_filter").addClass("selectedFilter"); selected_filter = f; @@ -446,6 +447,7 @@ window.addEvent('load', function() { updateFilter('stalled_uploading', 'Stalled Uploading (%1)'); updateFilter('stalled_downloading', 'Stalled Downloading (%1)'); updateFilter('checking', 'Checking (%1)'); + updateFilter('moving', 'Moving (%1)'); updateFilter('errored', 'Errored (%1)'); }; @@ -453,7 +455,7 @@ window.addEvent('load', function() { const categoryList = $('categoryFilterList'); if (!categoryList) return; - categoryList.empty(); + categoryList.getChildren().each(c => c.destroy()); const create_link = function(hash, text, count) { let display_name = text; @@ -488,7 +490,19 @@ window.addEvent('load', function() { Object.each(category_list, function(category) { sortedCategories.push(category.name); }); - sortedCategories.sort(); + sortedCategories.sort((leftCategory, rightCategory) => { + const leftSegments = leftCategory.split('/'); + const rightSegments = rightCategory.split('/'); + + for (let i = 0, iMax = Math.min(leftSegments.length, rightSegments.length); i < iMax; ++i) { + const compareResult = window.qBittorrent.Misc.naturalSortCollator.compare( + leftSegments[i], rightSegments[i]); + if (compareResult !== 0) + return compareResult; + } + + return leftSegments.length - rightSegments.length; + }); for (let i = 0; i < sortedCategories.length; ++i) { const categoryName = sortedCategories[i]; @@ -526,8 +540,7 @@ window.addEvent('load', function() { if (tagFilterList === null) return; - while (tagFilterList.firstChild !== null) - tagFilterList.removeChild(tagFilterList.firstChild); + tagFilterList.getChildren().each(c => c.destroy()); const createLink = function(hash, text, count) { const html = '' @@ -580,8 +593,7 @@ window.addEvent('load', function() { if (trackerFilterList === null) return; - while (trackerFilterList.firstChild !== null) - trackerFilterList.removeChild(trackerFilterList.firstChild); + trackerFilterList.getChildren().each(c => c.destroy()); const createLink = function(hash, text, count) { const html = '' @@ -1200,7 +1212,7 @@ window.addEvent('load', function() { loadMethod: 'xhr', contentURL: 'views/log.html', require: { - css: ['css/lib/vanillaSelectBox.css'], + css: ['css/vanillaSelectBox.css'], js: ['scripts/lib/vanillaSelectBox.js'], }, tabsURL: 'views/logTabs.html', @@ -1512,6 +1524,8 @@ function setupCopyEventHandler() { return copyMagnetLinkFN(); case "copyID": return copyIdFN(); + case "copyComment": + return copyCommentFN(); default: return ""; } diff --git a/private/scripts/contextmenu.js b/private/scripts/contextmenu.js index 71f1380..8fe40be 100644 --- a/private/scripts/contextmenu.js +++ b/private/scripts/contextmenu.js @@ -26,741 +26,668 @@ * exception statement from your version. */ -'use strict' +'use strict'; if (window.qBittorrent === undefined) { - window.qBittorrent = {} + window.qBittorrent = {}; } -window.qBittorrent.ContextMenu = (function () { - const exports = function () { - return { - ContextMenu: ContextMenu, - TorrentsTableContextMenu: TorrentsTableContextMenu, - CategoriesFilterContextMenu: CategoriesFilterContextMenu, - TagsFilterContextMenu: TagsFilterContextMenu, - SearchPluginsTableContextMenu: SearchPluginsTableContextMenu, - RssFeedContextMenu: RssFeedContextMenu, - RssArticleContextMenu: RssArticleContextMenu, - RssDownloaderRuleContextMenu: RssDownloaderRuleContextMenu, - } - } +window.qBittorrent.ContextMenu = (function() { + const exports = function() { + return { + ContextMenu: ContextMenu, + TorrentsTableContextMenu: TorrentsTableContextMenu, + CategoriesFilterContextMenu: CategoriesFilterContextMenu, + TagsFilterContextMenu: TagsFilterContextMenu, + SearchPluginsTableContextMenu: SearchPluginsTableContextMenu, + RssFeedContextMenu: RssFeedContextMenu, + RssArticleContextMenu: RssArticleContextMenu, + RssDownloaderRuleContextMenu: RssDownloaderRuleContextMenu + }; + }; - let lastShownContextMenu = null - const ContextMenu = new Class({ - //implements - Implements: [Options, Events], + let lastShownContextMenu = null; + const ContextMenu = new Class({ + //implements + Implements: [Options, Events], - //options - options: { - actions: {}, - menu: 'menu_id', - stopEvent: true, - targets: 'body', - offsets: { - x: 0, - y: 0, - }, - onShow: $empty, - onHide: $empty, - onClick: $empty, - fadeSpeed: 200, - touchTimer: 600, - }, + //options + options: { + actions: {}, + menu: 'menu_id', + stopEvent: true, + targets: 'body', + offsets: { + x: 0, + y: 0 + }, + onShow: () => {}, + onHide: () => {}, + onClick: () => {}, + fadeSpeed: 200, + touchTimer: 600 + }, - //initialization - initialize: function (options) { - //set options - this.setOptions(options) + //initialization + initialize: function(options) { + //set options + this.setOptions(options); - //option diffs menu - this.menu = $(this.options.menu) - this.targets = $$(this.options.targets) + //option diffs menu + this.menu = $(this.options.menu); + this.targets = $$(this.options.targets); - //fx - this.fx = new Fx.Tween(this.menu, { - property: 'opacity', - duration: this.options.fadeSpeed, - onComplete: function () { - if (this.getStyle('opacity')) { - this.setStyle('visibility', 'visible') - } else { - this.setStyle('visibility', 'hidden') - } - }.bind(this.menu), - }) + //fx + this.fx = new Fx.Tween(this.menu, { + property: 'opacity', + duration: this.options.fadeSpeed, + onComplete: function() { + if (this.getStyle('opacity')) { + this.setStyle('visibility', 'visible'); + } + else { + this.setStyle('visibility', 'hidden'); + } + }.bind(this.menu) + }); - //hide and begin the listener - this.hide().startListener() + //hide and begin the listener + this.hide().startListener(); - //hide the menu - this.menu.setStyles({ - position: 'absolute', - top: '-900000px', - display: 'block', - }) - }, + //hide the menu + this.menu.setStyles({ + 'position': 'absolute', + 'top': '-900000px', + 'display': 'block' + }); + }, - adjustMenuPosition: function (e) { - this.updateMenuItems() + adjustMenuPosition: function(e) { + this.updateMenuItems(); - const scrollableMenuMaxHeight = - document.documentElement.clientHeight * 0.75 + const scrollableMenuMaxHeight = document.documentElement.clientHeight * 0.75; - if (this.menu.hasClass('scrollableMenu')) - this.menu.setStyle('max-height', scrollableMenuMaxHeight) + if (this.menu.hasClass('scrollableMenu')) + this.menu.setStyle('max-height', scrollableMenuMaxHeight); - // draw the menu off-screen to know the menu dimensions - this.menu.setStyles({ - left: '-999em', - top: '-999em', - }) + // draw the menu off-screen to know the menu dimensions + this.menu.setStyles({ + left: '-999em', + top: '-999em' + }); - // position the menu - let xPosMenu = e.page.x + this.options.offsets.x - let yPosMenu = e.page.y + this.options.offsets.y - if ( - xPosMenu + this.menu.offsetWidth > - document.documentElement.clientWidth - ) - xPosMenu -= this.menu.offsetWidth - if ( - yPosMenu + this.menu.offsetHeight > - document.documentElement.clientHeight - ) - yPosMenu = - document.documentElement.clientHeight - this.menu.offsetHeight - if (xPosMenu < 0) xPosMenu = 0 - if (yPosMenu < 0) yPosMenu = 0 - this.menu.setStyles({ - left: xPosMenu, - top: yPosMenu, - position: 'absolute', - 'z-index': '2000', - }) + // position the menu + let xPosMenu = e.page.x + this.options.offsets.x; + let yPosMenu = e.page.y + this.options.offsets.y; + if (xPosMenu + this.menu.offsetWidth > document.documentElement.clientWidth) + xPosMenu -= this.menu.offsetWidth; + if (yPosMenu + this.menu.offsetHeight > document.documentElement.clientHeight) + yPosMenu = document.documentElement.clientHeight - this.menu.offsetHeight; + if (xPosMenu < 0) + xPosMenu = 0; + if (yPosMenu < 0) + yPosMenu = 0; + this.menu.setStyles({ + left: xPosMenu, + top: yPosMenu, + position: 'absolute', + 'z-index': '2000' + }); - // position the sub-menu - const uls = this.menu.getElementsByTagName('ul') - for (let i = 0; i < uls.length; ++i) { - const ul = uls[i] - if (ul.hasClass('scrollableMenu')) - ul.setStyle('max-height', scrollableMenuMaxHeight) - const rectParent = ul.parentNode.getBoundingClientRect() - const xPosOrigin = rectParent.left - const yPosOrigin = rectParent.bottom - let xPos = xPosOrigin + rectParent.width - 1 - let yPos = yPosOrigin - rectParent.height - 1 - if (xPos + ul.offsetWidth > document.documentElement.clientWidth) - xPos -= ul.offsetWidth + rectParent.width - 2 - if (yPos + ul.offsetHeight > document.documentElement.clientHeight) - yPos = document.documentElement.clientHeight - ul.offsetHeight - if (xPos < 0) xPos = 0 - if (yPos < 0) yPos = 0 - ul.setStyles({ - 'margin-left': xPos - xPosOrigin, - 'margin-top': yPos - yPosOrigin, - }) - } - }, - - setupEventListeners: function (elem) { - elem.addEvent( - 'contextmenu', - function (e) { - this.triggerMenu(e, elem) - }.bind(this) - ) - elem.addEvent( - 'click', - function (e) { - this.hide() - }.bind(this) - ) - - elem.addEvent( - 'touchstart', - function (e) { - e.preventDefault() - clearTimeout(this.touchstartTimer) - this.hide() - - const touchstartEvent = e - this.touchstartTimer = setTimeout( - function () { - this.triggerMenu(touchstartEvent, elem) - }.bind(this), - this.options.touchTimer - ) - }.bind(this) - ) - elem.addEvent( - 'touchend', - function (e) { - e.preventDefault() - clearTimeout(this.touchstartTimer) - }.bind(this) - ) - }, - - addTarget: function (t) { - this.targets[this.targets.length] = t - this.setupEventListeners(t) - }, - - triggerMenu: function (e, el) { - if (this.options.disabled) return - - //prevent default, if told to - if (this.options.stopEvent) { - e.stop() - } - //record this as the trigger - this.options.element = $(el) - this.adjustMenuPosition(e) - //show the menu - this.show() - }, - - //get things started - startListener: function () { - /* all elements */ - this.targets.each( - function (el) { - this.setupEventListeners(el) - }.bind(this), - this - ) - - /* menu items */ - this.menu.getElements('a').each(function (item) { - item.addEvent( - 'click', - function (e) { - e.preventDefault() - if (!item.hasClass('disabled')) { - this.execute( - item.get('href').split('#')[1], - $(this.options.element) - ) - this.fireEvent('click', [item, e]) + // position the sub-menu + const uls = this.menu.getElementsByTagName('ul'); + for (let i = 0; i < uls.length; ++i) { + const ul = uls[i]; + if (ul.hasClass('scrollableMenu')) + ul.setStyle('max-height', scrollableMenuMaxHeight); + const rectParent = ul.parentNode.getBoundingClientRect(); + const xPosOrigin = rectParent.left; + const yPosOrigin = rectParent.bottom; + let xPos = xPosOrigin + rectParent.width - 1; + let yPos = yPosOrigin - rectParent.height - 1; + if (xPos + ul.offsetWidth > document.documentElement.clientWidth) + xPos -= (ul.offsetWidth + rectParent.width - 2); + if (yPos + ul.offsetHeight > document.documentElement.clientHeight) + yPos = document.documentElement.clientHeight - ul.offsetHeight; + if (xPos < 0) + xPos = 0; + if (yPos < 0) + yPos = 0; + ul.setStyles({ + 'margin-left': xPos - xPosOrigin, + 'margin-top': yPos - yPosOrigin + }); } - }.bind(this) - ) - }, this) + }, - //hide on body click - $(document.body).addEvent( - 'click', - function () { - this.hide() - }.bind(this) - ) - }, + setupEventListeners: function(elem) { + elem.addEvent('contextmenu', function(e) { + this.triggerMenu(e, elem); + }.bind(this)); + elem.addEvent('click', function(e) { + this.hide(); + }.bind(this)); - updateMenuItems: function () {}, + elem.addEvent('touchstart', function(e) { + e.preventDefault(); + clearTimeout(this.touchstartTimer); + this.hide(); - //show menu - show: function (trigger) { - if (lastShownContextMenu && lastShownContextMenu != this) - lastShownContextMenu.hide() - this.fx.start(1) - this.fireEvent('show') - this.shown = true - lastShownContextMenu = this - return this - }, + const touchstartEvent = e; + this.touchstartTimer = setTimeout(function() { + this.triggerMenu(touchstartEvent, elem); + }.bind(this), this.options.touchTimer); + }.bind(this)); + elem.addEvent('touchend', function(e) { + e.preventDefault(); + clearTimeout(this.touchstartTimer); + }.bind(this)); + }, - //hide the menu - hide: function (trigger) { - if (this.shown) { - this.fx.start(0) - //this.menu.fade('out'); - this.fireEvent('hide') - this.shown = false - } - return this - }, + addTarget: function(t) { + this.targets[this.targets.length] = t; + this.setupEventListeners(t); + }, - setItemChecked: function (item, checked) { - if (this.menu.getElement('a[href$=' + item + ']')) { - this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity = - checked ? '1' : '0' - } - return this - }, + triggerMenu: function(e, el) { + if (this.options.disabled) + return; - getItemChecked: function (item) { - if (this.menu.getElement('a[href$=' + item + ']')) { - return ( - '0' != - this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity - ) - } else { - return '0' - } - }, + //prevent default, if told to + if (this.options.stopEvent) { + e.stop(); + } + //record this as the trigger + this.options.element = $(el); + this.adjustMenuPosition(e); + //show the menu + this.show(); + }, - //hide an item - hideItem: function (item) { - if (this.menu.getElement('a[href$=' + item + ']')) { - this.menu - .getElement('a[href$=' + item + ']') - .parentNode.addClass('invisible') - } - return this - }, + //get things started + startListener: function() { + /* all elements */ + this.targets.each(function(el) { + this.setupEventListeners(el); + }.bind(this), this); - //show an item - showItem: function (item) { - if (this.menu.getElement('a[href$=' + item + ']')) { - this.menu - .getElement('a[href$=' + item + ']') - .parentNode.removeClass('invisible') - } - return this - }, + /* menu items */ + this.menu.getElements('a').each(function(item) { + item.addEvent('click', function(e) { + e.preventDefault(); + if (!item.hasClass('disabled')) { + this.execute(item.get('href').split('#')[1], $(this.options.element)); + this.fireEvent('click', [item, e]); + } + }.bind(this)); + }, this); - //disable the entire menu - disable: function () { - this.options.disabled = true - return this - }, + //hide on body click + $(document.body).addEvent('click', function() { + this.hide(); + }.bind(this)); + }, - //enable the entire menu - enable: function () { - this.options.disabled = false - return this - }, + updateMenuItems: function() {}, - //execute an action - execute: function (action, element) { - if (this.options.actions[action]) { - this.options.actions[action](element, this, action) - } - return this - }, - }) + //show menu + show: function(trigger) { + if (lastShownContextMenu && lastShownContextMenu != this) + lastShownContextMenu.hide(); + this.fx.start(1); + this.fireEvent('show'); + this.shown = true; + lastShownContextMenu = this; + return this; + }, - const TorrentsTableContextMenu = new Class({ - Extends: ContextMenu, + //hide the menu + hide: function(trigger) { + if (this.shown) { + this.fx.start(0); + //this.menu.fade('out'); + this.fireEvent('hide'); + this.shown = false; + } + return this; + }, - updateMenuItems: function () { - let all_are_seq_dl = true - let there_are_seq_dl = false - let all_are_f_l_piece_prio = true - let there_are_f_l_piece_prio = false - let all_are_downloaded = true - let all_are_paused = true - let there_are_paused = false - let all_are_force_start = true - let there_are_force_start = false - let all_are_super_seeding = true - let all_are_auto_tmm = true - let there_are_auto_tmm = false - const tagsSelectionState = Object.clone(tagList) + setItemChecked: function(item, checked) { + this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity = + checked ? '1' : '0'; + return this; + }, - const h = torrentsTable.selectedRowsIds() - h.each(function (item, index) { - const data = torrentsTable.rows.get(item).full_data + getItemChecked: function(item) { + return '0' != this.menu.getElement('a[href$=' + item + ']').firstChild.style.opacity; + }, - if (data['seq_dl'] !== true) all_are_seq_dl = false - else there_are_seq_dl = true + //hide an item + hideItem: function(item) { + this.menu.getElement('a[href$=' + item + ']').parentNode.addClass('invisible'); + return this; + }, - if (data['f_l_piece_prio'] !== true) all_are_f_l_piece_prio = false - else there_are_f_l_piece_prio = true + //show an item + showItem: function(item) { + this.menu.getElement('a[href$=' + item + ']').parentNode.removeClass('invisible'); + return this; + }, - if (data['progress'] != 1.0) - // not downloaded - all_are_downloaded = false - else if (data['super_seeding'] !== true) all_are_super_seeding = false + //disable the entire menu + disable: function() { + this.options.disabled = true; + return this; + }, - if (data['state'] != 'pausedUP' && data['state'] != 'pausedDL') - all_are_paused = false - else there_are_paused = true + //enable the entire menu + enable: function() { + this.options.disabled = false; + return this; + }, - if (data['force_start'] !== true) all_are_force_start = false - else there_are_force_start = true - - if (data['auto_tmm'] === true) there_are_auto_tmm = true - else all_are_auto_tmm = false - - const torrentTags = data['tags'].split(', ') - for (const key in tagsSelectionState) { - const tag = tagsSelectionState[key] - const tagExists = torrentTags.contains(tag.name) - if (tag.checked !== undefined && tag.checked != tagExists) - tag.indeterminate = true - if (tag.checked === undefined) tag.checked = tagExists - else tag.checked = tag.checked && tagExists + //execute an action + execute: function(action, element) { + if (this.options.actions[action]) { + this.options.actions[action](element, this, action); + } + return this; } - }) + }); - let show_seq_dl = true + const TorrentsTableContextMenu = new Class({ + Extends: ContextMenu, - // hide renameFiles when more than 1 torrent is selected - if (h.length == 1) { - const data = torrentsTable.rows.get(h[0]).full_data - let metadata_downloaded = !( - data['state'] == 'metaDL' || - data['state'] == 'forcedMetaDL' || - data['total_size'] == -1 - ) + updateMenuItems: function() { + let all_are_seq_dl = true; + let there_are_seq_dl = false; + let all_are_f_l_piece_prio = true; + let there_are_f_l_piece_prio = false; + let all_are_downloaded = true; + let all_are_paused = true; + let there_are_paused = false; + let all_are_force_start = true; + let there_are_force_start = false; + let all_are_super_seeding = true; + let all_are_auto_tmm = true; + let there_are_auto_tmm = false; + const tagsSelectionState = Object.clone(tagList); - // hide renameFiles when metadata hasn't been downloaded yet - metadata_downloaded - ? this.showItem('renameFiles') - : this.hideItem('renameFiles') - } else this.hideItem('renameFiles') + const h = torrentsTable.selectedRowsIds(); + h.each(function(item, index) { + const data = torrentsTable.rows.get(item).full_data; - if (!all_are_seq_dl && there_are_seq_dl) show_seq_dl = false + if (data['seq_dl'] !== true) + all_are_seq_dl = false; + else + there_are_seq_dl = true; - let show_f_l_piece_prio = true + if (data['f_l_piece_prio'] !== true) + all_are_f_l_piece_prio = false; + else + there_are_f_l_piece_prio = true; - if (!all_are_f_l_piece_prio && there_are_f_l_piece_prio) - show_f_l_piece_prio = false + if (data['progress'] != 1.0) // not downloaded + all_are_downloaded = false; + else if (data['super_seeding'] !== true) + all_are_super_seeding = false; - if (all_are_downloaded) { - this.hideItem('downloadLimit') - this.menu - .getElement('a[href$=uploadLimit]') - .parentNode.addClass('separator') - this.hideItem('sequentialDownload') - this.hideItem('firstLastPiecePrio') - this.showItem('superSeeding') - this.setItemChecked('superSeeding', all_are_super_seeding) - } else { - if (!show_seq_dl && show_f_l_piece_prio) - this.menu - .getElement('a[href$=firstLastPiecePrio]') - .parentNode.addClass('separator') - else - this.menu - .getElement('a[href$=firstLastPiecePrio]') - .parentNode.removeClass('separator') + if (data['state'] != 'pausedUP' && data['state'] != 'pausedDL') + all_are_paused = false; + else + there_are_paused = true; - if (show_seq_dl) this.showItem('sequentialDownload') - else this.hideItem('sequentialDownload') + if (data['force_start'] !== true) + all_are_force_start = false; + else + there_are_force_start = true; - if (show_f_l_piece_prio) this.showItem('firstLastPiecePrio') - else this.hideItem('firstLastPiecePrio') + if (data['auto_tmm'] === true) + there_are_auto_tmm = true; + else + all_are_auto_tmm = false; - this.setItemChecked('sequentialDownload', all_are_seq_dl) - this.setItemChecked('firstLastPiecePrio', all_are_f_l_piece_prio) + const torrentTags = data['tags'].split(', '); + for (const key in tagsSelectionState) { + const tag = tagsSelectionState[key]; + const tagExists = torrentTags.contains(tag.name); + if ((tag.checked !== undefined) && (tag.checked != tagExists)) + tag.indeterminate = true; + if (tag.checked === undefined) + tag.checked = tagExists; + else + tag.checked = tag.checked && tagExists; + } + }); - this.showItem('downloadLimit') - this.menu - .getElement('a[href$=uploadLimit]') - .parentNode.removeClass('separator') - this.hideItem('superSeeding') - } + let show_seq_dl = true; - this.showItem('start') - this.showItem('pause') - this.showItem('forceStart') - if (all_are_paused) this.hideItem('pause') - else if (all_are_force_start) this.hideItem('forceStart') - else if (!there_are_paused && !there_are_force_start) - this.hideItem('start') + // hide renameFiles when more than 1 torrent is selected + if (h.length == 1) { + const data = torrentsTable.rows.get(h[0]).full_data; + let metadata_downloaded = !(data['state'] == 'metaDL' || data['state'] == 'forcedMetaDL' || data['total_size'] == -1); - if (!all_are_auto_tmm && there_are_auto_tmm) { - this.hideItem('autoTorrentManagement') - } else { - this.showItem('autoTorrentManagement') - this.setItemChecked('autoTorrentManagement', all_are_auto_tmm) - } + // hide renameFiles when metadata hasn't been downloaded yet + metadata_downloaded + ? this.showItem('renameFiles') + : this.hideItem('renameFiles'); + } + else + this.hideItem('renameFiles'); - const contextTagList = $('contextTagList') - for (const tagHash in tagList) { - const checkbox = contextTagList.getElement( - 'a[href=#Tag/' + tagHash + '] input[type=checkbox]' - ) - const checkboxState = tagsSelectionState[tagHash] - checkbox.indeterminate = checkboxState.indeterminate - checkbox.checked = checkboxState.checked - } - }, + if (!all_are_seq_dl && there_are_seq_dl) + show_seq_dl = false; - updateCategoriesSubMenu: function (category_list) { - const categoryList = $('contextCategoryList') - categoryList.empty() - categoryList.appendChild( - new Element('li', { - html: 'New...New...', - }) - ) - categoryList.appendChild( - new Element('li', { - html: 'ResetReset', - }) - ) + let show_f_l_piece_prio = true; - const sortedCategories = [] - Object.each(category_list, function (category) { - sortedCategories.push(category.name) - }) - sortedCategories.sort() + if (!all_are_f_l_piece_prio && there_are_f_l_piece_prio) + show_f_l_piece_prio = false; - let first = true - Object.each(sortedCategories, function (categoryName) { - const categoryHash = genHash(categoryName) - const el = new Element('li', { - html: - ' ' + - window.qBittorrent.Misc.escapeHtml(categoryName) + - '', - }) - if (first) { - el.addClass('separator') - first = false + if (all_are_downloaded) { + this.hideItem('downloadLimit'); + this.menu.getElement('a[href$=uploadLimit]').parentNode.addClass('separator'); + this.hideItem('sequentialDownload'); + this.hideItem('firstLastPiecePrio'); + this.showItem('superSeeding'); + this.setItemChecked('superSeeding', all_are_super_seeding); + } + else { + if (!show_seq_dl && show_f_l_piece_prio) + this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.addClass('separator'); + else + this.menu.getElement('a[href$=firstLastPiecePrio]').parentNode.removeClass('separator'); + + if (show_seq_dl) + this.showItem('sequentialDownload'); + else + this.hideItem('sequentialDownload'); + + if (show_f_l_piece_prio) + this.showItem('firstLastPiecePrio'); + else + this.hideItem('firstLastPiecePrio'); + + this.setItemChecked('sequentialDownload', all_are_seq_dl); + this.setItemChecked('firstLastPiecePrio', all_are_f_l_piece_prio); + + this.showItem('downloadLimit'); + this.menu.getElement('a[href$=uploadLimit]').parentNode.removeClass('separator'); + this.hideItem('superSeeding'); + } + + this.showItem('start'); + this.showItem('pause'); + this.showItem('forceStart'); + if (all_are_paused) + this.hideItem('pause'); + else if (all_are_force_start) + this.hideItem('forceStart'); + else if (!there_are_paused && !there_are_force_start) + this.hideItem('start'); + + if (!all_are_auto_tmm && there_are_auto_tmm) { + this.hideItem('autoTorrentManagement'); + } + else { + this.showItem('autoTorrentManagement'); + this.setItemChecked('autoTorrentManagement', all_are_auto_tmm); + } + + const contextTagList = $('contextTagList'); + for (const tagHash in tagList) { + const checkbox = contextTagList.getElement('a[href=#Tag/' + tagHash + '] input[type=checkbox]'); + const checkboxState = tagsSelectionState[tagHash]; + checkbox.indeterminate = checkboxState.indeterminate; + checkbox.checked = checkboxState.checked; + } + }, + + updateCategoriesSubMenu: function(category_list) { + const categoryList = $('contextCategoryList'); + categoryList.getChildren().each(c => c.destroy()); + categoryList.appendChild(new Element('li', { + html: 'New...New...' + })); + categoryList.appendChild(new Element('li', { + html: 'ResetReset' + })); + + const sortedCategories = []; + Object.each(category_list, function(category) { + sortedCategories.push(category.name); + }); + sortedCategories.sort(); + + let first = true; + Object.each(sortedCategories, function(categoryName) { + const categoryHash = genHash(categoryName); + const el = new Element('li', { + html: ' ' + window.qBittorrent.Misc.escapeHtml(categoryName) + '' + }); + if (first) { + el.addClass('separator'); + first = false; + } + categoryList.appendChild(el); + }); + }, + + updateTagsSubMenu: function(tagList) { + const contextTagList = $('contextTagList'); + while (contextTagList.firstChild !== null) + contextTagList.removeChild(contextTagList.firstChild); + + contextTagList.appendChild(new Element('li', { + html: '' + + 'Add...' + + ' Add...' + + '' + })); + contextTagList.appendChild(new Element('li', { + html: '' + + 'Remove All' + + ' Remove All' + + '' + })); + + const sortedTags = []; + for (const key in tagList) + sortedTags.push(tagList[key].name); + sortedTags.sort(); + + for (let i = 0; i < sortedTags.length; ++i) { + const tagName = sortedTags[i]; + const tagHash = genHash(tagName); + const el = new Element('li', { + html: '' + + ' ' + window.qBittorrent.Misc.escapeHtml(tagName) + + '' + }); + if (i === 0) + el.addClass('separator'); + contextTagList.appendChild(el); + } } - categoryList.appendChild(el) - }) - }, + }); - updateTagsSubMenu: function (tagList) { - const contextTagList = $('contextTagList') - while (contextTagList.firstChild !== null) - contextTagList.removeChild(contextTagList.firstChild) - - contextTagList.appendChild( - new Element('li', { - html: - '' + - 'Add...' + - ' Add...' + - '', - }) - ) - contextTagList.appendChild( - new Element('li', { - html: - '' + - 'Remove All' + - ' Remove All' + - '', - }) - ) - - const sortedTags = [] - for (const key in tagList) sortedTags.push(tagList[key].name) - sortedTags.sort() - - for (let i = 0; i < sortedTags.length; ++i) { - const tagName = sortedTags[i] - const tagHash = genHash(tagName) - const el = new Element('li', { - html: - '" + - ' ' + - window.qBittorrent.Misc.escapeHtml(tagName) + - '', - }) - if (i === 0) el.addClass('separator') - contextTagList.appendChild(el) - } - }, - }) - - const CategoriesFilterContextMenu = new Class({ - Extends: ContextMenu, - updateMenuItems: function () { - const id = this.options.element.id - if (id != CATEGORIES_ALL && id != CATEGORIES_UNCATEGORIZED) { - this.showItem('editCategory') - this.showItem('deleteCategory') - if (useSubcategories) { - this.showItem('createSubcategory') - } else { - this.hideItem('createSubcategory') + const CategoriesFilterContextMenu = new Class({ + Extends: ContextMenu, + updateMenuItems: function() { + const id = this.options.element.id; + if ((id != CATEGORIES_ALL) && (id != CATEGORIES_UNCATEGORIZED)) { + this.showItem('editCategory'); + this.showItem('deleteCategory'); + if (useSubcategories) { + this.showItem('createSubcategory'); + } + else { + this.hideItem('createSubcategory'); + } + } + else { + this.hideItem('editCategory'); + this.hideItem('deleteCategory'); + this.hideItem('createSubcategory'); + } } - } else { - this.hideItem('editCategory') - this.hideItem('deleteCategory') - this.hideItem('createSubcategory') - } - }, - }) + }); - const TagsFilterContextMenu = new Class({ - Extends: ContextMenu, - updateMenuItems: function () { - const id = this.options.element.id - if (id !== TAGS_ALL.toString() && id !== TAGS_UNTAGGED.toString()) - this.showItem('deleteTag') - else this.hideItem('deleteTag') - }, - }) + const TagsFilterContextMenu = new Class({ + Extends: ContextMenu, + updateMenuItems: function() { + const id = this.options.element.id; + if ((id !== TAGS_ALL.toString()) && (id !== TAGS_UNTAGGED.toString())) + this.showItem('deleteTag'); + else + this.hideItem('deleteTag'); + } + }); - const SearchPluginsTableContextMenu = new Class({ - Extends: ContextMenu, + const SearchPluginsTableContextMenu = new Class({ + Extends: ContextMenu, - updateMenuItems: function () { - const enabledColumnIndex = function (text) { - const columns = $('searchPluginsTableFixedHeaderRow').getChildren('th') - for (let i = 0; i < columns.length; ++i) - if (columns[i].get('html') === 'Enabled') return i - } + updateMenuItems: function() { + const enabledColumnIndex = function(text) { + const columns = $("searchPluginsTableFixedHeaderRow").getChildren("th"); + for (let i = 0; i < columns.length; ++i) + if (columns[i].get("html") === "Enabled") + return i; + }; - this.showItem('Enabled') - this.setItemChecked( - 'Enabled', - this.options.element - .getChildren('td') - [enabledColumnIndex()].get('html') === 'Yes' - ) + this.showItem('Enabled'); + this.setItemChecked('Enabled', this.options.element.getChildren("td")[enabledColumnIndex()].get("html") === "Yes"); - this.showItem('Uninstall') - }, - }) + this.showItem('Uninstall'); + } + }); - const RssFeedContextMenu = new Class({ - Extends: ContextMenu, - updateMenuItems: function () { - let selectedRows = window.qBittorrent.Rss.rssFeedTable.selectedRowsIds() - this.menu - .getElement('a[href$=newSubscription]') - .parentNode.addClass('separator') - switch (selectedRows.length) { - case 0: - // remove separator on top of newSubscription entry to avoid double line - this.menu - .getElement('a[href$=newSubscription]') - .parentNode.removeClass('separator') - // menu when nothing selected - this.hideItem('update') - this.hideItem('markRead') - this.hideItem('rename') - this.hideItem('delete') - this.showItem('newSubscription') - this.showItem('newFolder') - this.showItem('updateAll') - this.hideItem('copyFeedURL') - break - case 1: - if (selectedRows[0] === 0) { - // menu when "unread" feed selected - this.showItem('update') - this.showItem('markRead') - this.hideItem('rename') - this.hideItem('delete') - this.showItem('newSubscription') - this.hideItem('newFolder') - this.hideItem('updateAll') - this.hideItem('copyFeedURL') - } else if ( - window.qBittorrent.Rss.rssFeedTable.rows[selectedRows[0]].full_data - .dataUid === '' - ) { - // menu when single folder selected - this.showItem('update') - this.showItem('markRead') - this.showItem('rename') - this.showItem('delete') - this.showItem('newSubscription') - this.showItem('newFolder') - this.hideItem('updateAll') - this.hideItem('copyFeedURL') - } else { - // menu when single feed selected - this.showItem('update') - this.showItem('markRead') - this.showItem('rename') - this.showItem('delete') - this.showItem('newSubscription') - this.hideItem('newFolder') - this.hideItem('updateAll') - this.showItem('copyFeedURL') - } - break - default: - // menu when multiple items selected - this.showItem('update') - this.showItem('markRead') - this.hideItem('rename') - this.showItem('delete') - this.hideItem('newSubscription') - this.hideItem('newFolder') - this.hideItem('updateAll') - this.showItem('copyFeedURL') - break - } - }, - }) + const RssFeedContextMenu = new Class({ + Extends: ContextMenu, + updateMenuItems: function() { + let selectedRows = window.qBittorrent.Rss.rssFeedTable.selectedRowsIds(); + this.menu.getElement('a[href$=newSubscription]').parentNode.addClass('separator'); + switch (selectedRows.length) { + case 0: + // remove separator on top of newSubscription entry to avoid double line + this.menu.getElement('a[href$=newSubscription]').parentNode.removeClass('separator'); + // menu when nothing selected + this.hideItem('update'); + this.hideItem('markRead'); + this.hideItem('rename'); + this.hideItem('delete'); + this.showItem('newSubscription'); + this.showItem('newFolder'); + this.showItem('updateAll'); + this.hideItem('copyFeedURL'); + break; + case 1: + if (selectedRows[0] === 0) { + // menu when "unread" feed selected + this.showItem('update'); + this.showItem('markRead'); + this.hideItem('rename'); + this.hideItem('delete'); + this.showItem('newSubscription'); + this.hideItem('newFolder'); + this.hideItem('updateAll'); + this.hideItem('copyFeedURL'); + } + else if (window.qBittorrent.Rss.rssFeedTable.rows[selectedRows[0]].full_data.dataUid === '') { + // menu when single folder selected + this.showItem('update'); + this.showItem('markRead'); + this.showItem('rename'); + this.showItem('delete'); + this.showItem('newSubscription'); + this.showItem('newFolder'); + this.hideItem('updateAll'); + this.hideItem('copyFeedURL'); + } + else { + // menu when single feed selected + this.showItem('update'); + this.showItem('markRead'); + this.showItem('rename'); + this.showItem('delete'); + this.showItem('newSubscription'); + this.hideItem('newFolder'); + this.hideItem('updateAll'); + this.showItem('copyFeedURL'); + } + break; + default: + // menu when multiple items selected + this.showItem('update'); + this.showItem('markRead'); + this.hideItem('rename'); + this.showItem('delete'); + this.hideItem('newSubscription'); + this.hideItem('newFolder'); + this.hideItem('updateAll'); + this.showItem('copyFeedURL'); + break; + } + } + }); - const RssArticleContextMenu = new Class({ - Extends: ContextMenu, - }) + const RssArticleContextMenu = new Class({ + Extends: ContextMenu + }); - const RssDownloaderRuleContextMenu = new Class({ - Extends: ContextMenu, - adjustMenuPosition: function (e) { - this.updateMenuItems() + const RssDownloaderRuleContextMenu = new Class({ + Extends: ContextMenu, + adjustMenuPosition: function(e) { + this.updateMenuItems(); - // draw the menu off-screen to know the menu dimensions - this.menu.setStyles({ - left: '-999em', - top: '-999em', - }) - // position the menu - let xPosMenu = - e.page.x + this.options.offsets.x - $('rssdownloaderpage').offsetLeft - let yPosMenu = - e.page.y + this.options.offsets.y - $('rssdownloaderpage').offsetTop - if ( - xPosMenu + this.menu.offsetWidth > - document.documentElement.clientWidth - ) - xPosMenu -= this.menu.offsetWidth - if ( - yPosMenu + this.menu.offsetHeight > - document.documentElement.clientHeight - ) - yPosMenu = - document.documentElement.clientHeight - this.menu.offsetHeight - xPosMenu = Math.max(xPosMenu, 0) - yPosMenu = Math.max(yPosMenu, 0) + // draw the menu off-screen to know the menu dimensions + this.menu.setStyles({ + left: '-999em', + top: '-999em' + }); + // position the menu + let xPosMenu = e.page.x + this.options.offsets.x - $('rssdownloaderpage').offsetLeft; + let yPosMenu = e.page.y + this.options.offsets.y - $('rssdownloaderpage').offsetTop; + if ((xPosMenu + this.menu.offsetWidth) > document.documentElement.clientWidth) + xPosMenu -= this.menu.offsetWidth; + if ((yPosMenu + this.menu.offsetHeight) > document.documentElement.clientHeight) + yPosMenu = document.documentElement.clientHeight - this.menu.offsetHeight; + xPosMenu = Math.max(xPosMenu, 0); + yPosMenu = Math.max(yPosMenu, 0); - this.menu.setStyles({ - left: xPosMenu, - top: yPosMenu, - position: 'absolute', - 'z-index': '2000', - }) - }, - updateMenuItems: function () { - let selectedRows = - window.qBittorrent.RssDownloader.rssDownloaderRulesTable.selectedRowsIds() - this.showItem('addRule') - switch (selectedRows.length) { - case 0: - // menu when nothing selected - this.hideItem('deleteRule') - this.hideItem('renameRule') - this.hideItem('clearDownloadedEpisodes') - break - case 1: - // menu when single item selected - this.showItem('deleteRule') - this.showItem('renameRule') - this.showItem('clearDownloadedEpisodes') - break - default: - // menu when multiple items selected - this.showItem('deleteRule') - this.hideItem('renameRule') - this.showItem('clearDownloadedEpisodes') - break - } - }, - }) + this.menu.setStyles({ + left: xPosMenu, + top: yPosMenu, + position: 'absolute', + 'z-index': '2000' + }); + }, + updateMenuItems: function() { + let selectedRows = window.qBittorrent.RssDownloader.rssDownloaderRulesTable.selectedRowsIds(); + this.showItem('addRule'); + switch (selectedRows.length) { + case 0: + // menu when nothing selected + this.hideItem('deleteRule'); + this.hideItem('renameRule'); + this.hideItem('clearDownloadedEpisodes'); + break; + case 1: + // menu when single item selected + this.showItem('deleteRule'); + this.showItem('renameRule'); + this.showItem('clearDownloadedEpisodes'); + break; + default: + // menu when multiple items selected + this.showItem('deleteRule'); + this.hideItem('renameRule'); + this.showItem('clearDownloadedEpisodes'); + break; + } + } + }); - return exports() -})() + return exports(); +})(); -Object.freeze(window.qBittorrent.ContextMenu) +Object.freeze(window.qBittorrent.ContextMenu); diff --git a/private/scripts/dynamicTable.js b/private/scripts/dynamicTable.js index f203143..1bf5d3b 100644 --- a/private/scripts/dynamicTable.js +++ b/private/scripts/dynamicTable.js @@ -816,8 +816,7 @@ window.qBittorrent.DynamicTable = (function() { let rowPos = rows.length; while ((rowPos < trs.length) && (trs.length > 0)) { - trs[trs.length - 1].dispose(); - trs.pop(); + trs.pop().destroy(); } }, @@ -839,7 +838,7 @@ window.qBittorrent.DynamicTable = (function() { this.selectedRows.erase(rowId); const tr = this.getTrByRowId(rowId); if (tr !== null) { - tr.dispose(); + tr.destroy(); this.rows.erase(rowId); return true; } @@ -851,8 +850,7 @@ window.qBittorrent.DynamicTable = (function() { this.rows.empty(); const trs = this.tableBody.getElements('tr'); while (trs.length > 0) { - trs[trs.length - 1].dispose(); - trs.pop(); + trs.pop().destroy(); } }, @@ -945,6 +943,7 @@ window.qBittorrent.DynamicTable = (function() { this.newColumn('seen_complete', '', 'Last Seen Complete', 100, false); this.newColumn('last_activity', '', 'Last Activity', 100, false); this.newColumn('availability', '', 'Availability', 100, false); + this.newColumn('reannounce', '', 'Reannounce In', 100, false); this.columns['state_icon'].onclick = ''; this.columns['state_icon'].dataProperties[0] = 'state'; @@ -1001,10 +1000,14 @@ window.qBittorrent.DynamicTable = (function() { case "checkingUP": case "queuedForChecking": case "checkingResumeData": - case "moving": state = "force-recheck"; img_path = "images/force-recheck.svg"; break; + case "moving": + state = "moving"; + img_path = "images/set-location.svg"; + break; + case "error": case "unknown": case "missingFiles": state = "error"; @@ -1331,6 +1334,13 @@ window.qBittorrent.DynamicTable = (function() { td.set('text', value); td.set('title', value); }; + + // reannounce + this.columns['reannounce'].updateTd = function(td, row) { + const time = window.qBittorrent.Misc.friendlyDuration(this.getRowValue(row)); + td.set('text', time); + td.set('title', time); + }; }, applyFilter: function(row, filterName, categoryHash, tagHash, trackerHash, filterTerms) { @@ -1387,6 +1397,10 @@ window.qBittorrent.DynamicTable = (function() { if (state !== 'checkingUP' && state !== 'checkingDL' && state !== 'checkingResumeData') return false; break; + case 'moving': + if (state !== 'moving') + return false; + break; case 'errored': if (state != 'error' && state != "unknown" && state != "missingFiles") return false; @@ -1567,7 +1581,7 @@ window.qBittorrent.DynamicTable = (function() { if (!country_code) { if (td.getChildren('img').length > 0) - td.getChildren('img')[0].dispose(); + td.getChildren('img')[0].destroy(); return; } diff --git a/private/scripts/lib/mocha-0.9.6.js b/private/scripts/lib/mocha-0.9.6.js index 8d4c433..77bc31a 100644 --- a/private/scripts/lib/mocha-0.9.6.js +++ b/private/scripts/lib/mocha-0.9.6.js @@ -2262,7 +2262,8 @@ MUI.Window = new Class({ 'styles': { 'position': 'absolute', // This is set here to make theme transitions smoother 'top': 0, - 'left': 0 + 'left': 0, + 'height': 'auto' } }).inject(this.windowEl); diff --git a/private/scripts/mocha-init.js b/private/scripts/mocha-init.js index 7bd8fca..de64388 100644 --- a/private/scripts/mocha-init.js +++ b/private/scripts/mocha-init.js @@ -89,6 +89,7 @@ let copyNameFN = function() {}; let copyInfohashFN = function(policy) {}; let copyMagnetLinkFN = function() {}; let copyIdFN = function() {}; +let copyCommentFN = function() {}; let setQueuePositionFN = function() {}; let exportTorrentFN = function() {}; @@ -348,7 +349,7 @@ const initializeWindows = function() { const id = 'statisticspage'; new MochaUI.Window({ id: id, - title: 'Statistics', + title: 'Statistics]', loadMethod: 'xhr', contentURL: new URI("views/statistics.html").toString(), maximizable: false, @@ -1005,6 +1006,21 @@ const initializeWindows = function() { return torrentsTable.selectedRowsIds().join("\n"); }; + copyCommentFN = function() { + const selectedRows = torrentsTable.selectedRowsIds(); + const comments = []; + if (selectedRows.length > 0) { + const rows = torrentsTable.getFilteredAndSortedRows(); + for (let i = 0; i < selectedRows.length; ++i) { + const hash = selectedRows[i]; + const comment = rows[hash].full_data.comment; + if (comment && (comment !== "")) + comments.push(comment); + } + } + return comments.join("\n---------\n"); + }; + exportTorrentFN = async function() { const hashes = torrentsTable.selectedRowsIds(); for (const hash of hashes) { diff --git a/private/scripts/prop-files.js b/private/scripts/prop-files.js index ce4c963..cff63e0 100644 --- a/private/scripts/prop-files.js +++ b/private/scripts/prop-files.js @@ -173,7 +173,7 @@ window.qBittorrent.PropFiles = (function() { elem.set('value', priority.toString()); elem.set('html', html); if (selected) - elem.setAttribute('selected', ''); + elem.selected = true; return elem; }; diff --git a/private/scripts/prop-webseeds.js b/private/scripts/prop-webseeds.js index c0eb75a..a24194a 100644 --- a/private/scripts/prop-webseeds.js +++ b/private/scripts/prop-webseeds.js @@ -50,8 +50,7 @@ window.qBittorrent.PropWebseeds = (function() { removeRow: function(url) { if (this.rows.has(url)) { - const tr = this.rows.get(url); - tr.dispose(); + this.rows.get(url).destroy(); this.rows.erase(url); return true; } diff --git a/private/shareratio.html b/private/shareratio.html index 0320790..fb6f607 100644 --- a/private/shareratio.html +++ b/private/shareratio.html @@ -40,16 +40,19 @@ const values = { ratioLimit: window.qBittorrent.Misc.friendlyFloat(origValues[0], 2), seedingTimeLimit: parseInt(origValues[1]), - maxRatio: window.qBittorrent.Misc.friendlyFloat(origValues[2], 2), - maxSeedingTime: parseInt(origValues[3]) + inactiveSeedingTimeLimit: parseInt(origValues[2]), + maxRatio: window.qBittorrent.Misc.friendlyFloat(origValues[3], 2), + maxSeedingTime: parseInt(origValues[4]), + maxInactiveSeedingTime: parseInt(origValues[5]) }; // select default when orig values not passed. using double equals to compare string and int - if ((origValues[0] === "") || ((values.ratioLimit == UseGlobalLimit) && (values.seedingTimeLimit == UseGlobalLimit))) { + if ((origValues[0] === "") || ((values.ratioLimit == UseGlobalLimit) && (values.seedingTimeLimit == UseGlobalLimit)) + && (values.inactiveSeedingTimeLimit == UseGlobalLimit)) { // use default option setSelectedRadioValue('shareLimit', 'default'); } - else if ((values.maxRatio == NoLimit) && (values.maxSeedingTime == NoLimit)) { + else if ((values.maxRatio == NoLimit) && (values.maxSeedingTime == NoLimit) && (values.maxInactiveSeedingTime == NoLimit)) { setSelectedRadioValue('shareLimit', 'none'); // TODO set input boxes to *global* max ratio and seeding time } @@ -60,8 +63,12 @@ $('ratio').set('value', values.ratioLimit); } if (values.seedingTimeLimit >= 0) { - $('setMinutes').set('checked', true); - $('minutes').set('value', values.seedingTimeLimit); + $('setTotalMinutes').set('checked', true); + $('totalMinutes').set('value', values.seedingTimeLimit); + } + if (values.inactiveSeedingTimeLimit >= 0) { + $('setInactiveMinutes').set('checked', true); + $('inactiveMinutes').set('value', values.inactiveSeedingTimeLimit); } } @@ -78,16 +85,18 @@ const shareLimit = getSelectedRadioValue('shareLimit'); let ratioLimitValue = 0.00; let seedingTimeLimitValue = 0; + let inactiveSeedingTimeLimitValue = 0; if (shareLimit === 'default') { - ratioLimitValue = seedingTimeLimitValue = UseGlobalLimit; + ratioLimitValue = seedingTimeLimitValue = inactiveSeedingTimeLimitValue = UseGlobalLimit; } else if (shareLimit === 'none') { - ratioLimitValue = seedingTimeLimitValue = NoLimit; + ratioLimitValue = seedingTimeLimitValue = inactiveSeedingTimeLimitValue = NoLimit; } else if (shareLimit === 'custom') { ratioLimitValue = $('setRatio').get('checked') ? $('ratio').get('value') : -1; - seedingTimeLimitValue = $('setMinutes').get('checked') ? $('minutes').get('value') : -1; + seedingTimeLimitValue = $('setTotalMinutes').get('checked') ? $('totalMinutes').get('value') : -1; + inactiveSeedingTimeLimitValue = $('setInactiveMinutes').get('checked') ? $('inactiveMinutes').get('value') : -1; } else { return false; @@ -99,7 +108,8 @@ data: { hashes: hashesList.join('|'), ratioLimit: ratioLimitValue, - seedingTimeLimit: seedingTimeLimitValue + seedingTimeLimit: seedingTimeLimitValue, + inactiveSeedingTimeLimit: inactiveSeedingTimeLimitValue }, onComplete: function() { window.parent.closeWindows(); @@ -132,7 +142,8 @@ function shareLimitChanged() { const customShareLimit = getSelectedRadioValue('shareLimit') === 'custom'; $('setRatio').set('disabled', !customShareLimit); - $('setMinutes').set('disabled', !customShareLimit); + $('setTotalMinutes').set('disabled', !customShareLimit); + $('setInactiveMinutes').set('disabled', !customShareLimit); enableInputBoxes(); @@ -141,22 +152,24 @@ function enableInputBoxes() { $('ratio').set('disabled', ($('setRatio').get('disabled') || !$('setRatio').get('checked'))); - $('minutes').set('disabled', ($('setMinutes').get('disabled') || !$('setMinutes').get('checked'))); + $('totalMinutes').set('disabled', ($('setTotalMinutes').get('disabled') || !$('setTotalMinutes').get('checked'))); + $('inactiveMinutes').set('disabled', ($('setInactiveMinutes').get('disabled') || !$('setInactiveMinutes').get('checked'))); $('save').set('disabled', !isFormValid()); } function isFormValid() { - return !((getSelectedRadioValue('shareLimit') === 'custom') && !$('setRatio').get('checked') && !$('setMinutes').get('checked')); + return !((getSelectedRadioValue('shareLimit') === 'custom') && !$('setRatio').get('checked') + && !$('setTotalMinutes').get('checked') && !$('setInactiveMinutes').get('checked')); }
    - Use global share limit
    - Set no share limit
    - Set share limit to
    + Use global share limit
    + Set no share limit
    + Set share limit to
    @@ -164,12 +177,17 @@
    - - - + + + +
    +
    + + +
    - +
    diff --git a/private/upload.html b/private/upload.html index 8b0873b..ce4660c 100644 --- a/private/upload.html +++ b/private/upload.html @@ -12,24 +12,21 @@ - +
    - +
    @@ -73,7 +70,15 @@ + + + +
    - +
    - + + + +
    + + + + + +
    + + + + +
    + + +
    + + + + + + + + + + + +
    KiB
    + + +
    +
    + +
    + + +
    + + +
    +
    + + +
    @@ -254,7 +313,7 @@
    - +
    @@ -296,6 +355,33 @@
    +
    + + + + + + + + + + + +
    + + + + + + + +
    +
    + + +
    +
    +
    Proxy Server @@ -305,10 +391,10 @@
    @@ -325,18 +411,12 @@
    -
    - - -
    -
    - - -
    +
    - +
    +
    @@ -364,6 +444,25 @@ Info: The password is saved unencrypted
    + +
    + + + + +
    + + +
    +
    +
    + + +
    +
    + + +
    @@ -584,12 +683,21 @@ - + minutes + + + + + + + minutes + + @@ -646,7 +754,7 @@
    - +
    @@ -662,74 +770,6 @@