From 42e4713c87d899952685b15c4641f08f1d6280b1 Mon Sep 17 00:00:00 2001 From: rthaut Date: Thu, 15 Jun 2017 21:54:29 -0600 Subject: [PATCH 01/30] include dialog polyfill CSS for webextension --- gulpfile.js | 12 ++++++++---- manifest.json | 1 + 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 9d4eb10..56589f7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -146,10 +146,14 @@ gulp.task('build:webextension:js', function () { .pipe(gulp.dest('./dist/webextension/js')); }); gulp.task('build:webextension:css', function () { - return gulp.src(stylesheets) - .pipe(concat(package.name + '.css')) - .pipe(cssnano({ zindex: false })) - .pipe(header(fs.readFileSync('./banners/webextension.txt', 'utf8'), { package: package })) + return merge( + gulp.src(stylesheets) + .pipe(concat(package.name + '.css')) + .pipe(cssnano({ zindex: false })) + .pipe(header(fs.readFileSync('./banners/webextension.txt', 'utf8'), { package: package })), + gulp.src('./node_modules/dialog-polyfill/dialog-polyfill.css') + .pipe(cssnano({ zindex: false })) + ) .pipe(gulp.dest('./dist/webextension/css')); }); gulp.task('build:webextension:manifest', function () { diff --git a/manifest.json b/manifest.json index 36c2e0f..fbd1e97 100644 --- a/manifest.json +++ b/manifest.json @@ -16,6 +16,7 @@ "*://*.deviantart.com/*" ], "css": [ + "css/dialog-polyfill.css", "css/<%= package.name %>.css" ], "js": [ From da4408ff019030c0020ae734d62fd41847225819 Mon Sep 17 00:00:00 2001 From: rthaut Date: Thu, 15 Jun 2017 22:01:35 -0600 Subject: [PATCH 02/30] JSDoc corrections --- lib/js/classes/base/Filter.class.js | 10 +++++----- lib/js/classes/base/FilterList.class.js | 8 ++++---- lib/js/classes/base/FilterObject.class.js | 8 ++++---- lib/js/classes/users/UserList.class.js | 8 ++++---- lib/js/classes/users/UsersFilter.class.js | 4 ++-- lib/js/daFilter.class.js | 4 ++-- lib/js/scripts/daDialog.class.js | 1 + lib/js/scripts/storage.js | 4 ++-- 8 files changed, 24 insertions(+), 23 deletions(-) diff --git a/lib/js/classes/base/Filter.class.js b/lib/js/classes/base/Filter.class.js index 2e5ab3d..ee3916c 100644 --- a/lib/js/classes/base/Filter.class.js +++ b/lib/js/classes/base/Filter.class.js @@ -16,7 +16,7 @@ class Filter { /** * Creates a new FilterObject with the supplied properties * @param {Object} properties - * @return {FilterObject} + * @returns {FilterObject} */ create(properties) { return new FilterObject(''); @@ -110,7 +110,7 @@ class Filter { /** * Determines if the specified FilterObject is currently hidden * @param {FilterObject} filterObject - * @return {Boolean} + * @returns {Boolean} */ isHidden(filterObject) { console.group(this.label + '.isHidden()'); @@ -128,7 +128,7 @@ class Filter { /** * Hides the specified FilterObject * @param {FilterObject} filterObject - * @return {Boolean} + * @returns {Boolean} */ hide(filterObject) { console.group(this.label + '.hide()'); @@ -146,7 +146,7 @@ class Filter { /** * Shows the specified FilterObject * @param {FilterObject} filterObject - * @return {Boolean} + * @returns {Boolean} */ show(filterObject) { console.group(this.label + '.hide()'); @@ -164,7 +164,7 @@ class Filter { /** * Toggles the specified FilterObject * @param {FilterObject} filterObject - * @return {Boolean} + * @returns {Boolean} */ toggle(filterObject) { console.group(this.label + '.hide()'); diff --git a/lib/js/classes/base/FilterList.class.js b/lib/js/classes/base/FilterList.class.js index 9297bcd..5bdf84c 100644 --- a/lib/js/classes/base/FilterList.class.js +++ b/lib/js/classes/base/FilterList.class.js @@ -50,7 +50,7 @@ class FilterList { /** * Adds/removes the provided FilterObject to/from the current FilterList * @param {FilterObject} filterObject - * @return {Number} + * @returns {Number} */ toggle(filterObject) { console.group(this.listObjectName + '.toggle()'); @@ -76,7 +76,7 @@ class FilterList { /** * Adds the provided FilterObject to the current FilterList * @param {FilterObject} filterObject - * @return {Boolean} + * @returns {Boolean} */ add(filterObject) { console.group(this.listObjectName + '.add()'); @@ -99,7 +99,7 @@ class FilterList { /** * Removes the provided FilterObject from the current FilterList * @param {FilterObject} filterObject - * @return {Boolean} + * @returns {Boolean} */ remove(filterObject) { console.group(this.listObjectName + '.remove()'); @@ -122,7 +122,7 @@ class FilterList { /** * Finds the provided FilterObject in this FilterList * @param {FilterObject} filterObject - * @return {Number} + * @returns {Number} */ find(filterObject) { console.group(this.listObjectName + '.find()'); diff --git a/lib/js/classes/base/FilterObject.class.js b/lib/js/classes/base/FilterObject.class.js index 9aa030f..a27f965 100644 --- a/lib/js/classes/base/FilterObject.class.js +++ b/lib/js/classes/base/FilterObject.class.js @@ -6,8 +6,8 @@ class FilterObject { /** * Constructor * @param {String} [objectName] - * @param {Array} [objectProperties] - * @param {Array} [uniqueProperties] + * @param {Array} [objectProperties=[]] + * @param {Array} [uniqueProperties=[]] */ constructor(objectName = 'FilterObject', objectProperties = [], uniqueProperties = []) { this._objectName = objectName; @@ -17,7 +17,7 @@ class FilterObject { /** * Determines if the FilterObject has all stored properties populated - * @return {Boolean} + * @returns {Boolean} */ isComplete() { console.group(this._objectName + '.isComplete()'); @@ -38,7 +38,7 @@ class FilterObject { /** * Determines if the FilterObject has at least one stored property populated - * @return {Boolean} + * @returns {Boolean} */ isValid() { console.group(this._objectName + '.isValid()'); diff --git a/lib/js/classes/users/UserList.class.js b/lib/js/classes/users/UserList.class.js index ce660cf..c207f26 100644 --- a/lib/js/classes/users/UserList.class.js +++ b/lib/js/classes/users/UserList.class.js @@ -15,10 +15,10 @@ class UserList extends FilterList { /** * Cleans the stored FilterList of hidden users - * @param {Boolean} strictFiltering - * @return {Boolean} + * @param {Boolean} [strict] + * @returns {Boolean} */ - clean(strictFiltering = false) { + clean(strict = false) { console.group(this.listObjectName + '.clean()'); var dirty = this.data, @@ -34,7 +34,7 @@ class UserList extends FilterList { dirty[i]['username'] = dirty[i]['username'].toLowerCase(); user = new UserObject(dirty[i]['username']); - if (strictFiltering) { + if (strict) { if (user.isComplete()) { clean.push(dirty[i]); } else { diff --git a/lib/js/classes/users/UsersFilter.class.js b/lib/js/classes/users/UsersFilter.class.js index 8fb2426..61a527c 100644 --- a/lib/js/classes/users/UsersFilter.class.js +++ b/lib/js/classes/users/UsersFilter.class.js @@ -14,7 +14,7 @@ class UsersFilter extends Filter { /** * Creates a new FilterObject with the supplied properties * @param {Object} properties - * @return {FilterObject} + * @returns {FilterObject} */ create(properties) { return new UserObject(properties.username); @@ -117,7 +117,7 @@ class UsersFilter extends Filter { /** * Builds the table for managing users - * @return {Element} + * @returns {Element} */ getTable() { console.group(this.label + '.getTable()'); diff --git a/lib/js/daFilter.class.js b/lib/js/daFilter.class.js index 3402cd8..6f3cb5f 100644 --- a/lib/js/daFilter.class.js +++ b/lib/js/daFilter.class.js @@ -15,7 +15,7 @@ class deviantARTFilter { /** * Getter for "User Placeholders" Boolean setting - * @return {Boolean} + * @returns {Boolean} */ get usePlaceholders() { return getStoredJSON('usePlaceholders', true); @@ -324,7 +324,7 @@ class deviantARTFilter { /** * Exports filters to object for import - * @return {Object} + * @returns {Object} */ exportFilters() { console.group('deviantARTFilter.exportFilters()'); diff --git a/lib/js/scripts/daDialog.class.js b/lib/js/scripts/daDialog.class.js index 586de3d..827c155 100644 --- a/lib/js/scripts/daDialog.class.js +++ b/lib/js/scripts/daDialog.class.js @@ -8,6 +8,7 @@ var daDialog = { * @param {String} [title] * @param {Array} buttons * @param {Object} [options] + * @returns {HTMLElement} */ dialog: function (content, title, buttons, options) { diff --git a/lib/js/scripts/storage.js b/lib/js/scripts/storage.js index fc38e22..bcbdd8d 100644 --- a/lib/js/scripts/storage.js +++ b/lib/js/scripts/storage.js @@ -24,7 +24,7 @@ function setStoredJSON(key, value) { * Retrieves data from local storage * @param {String} key * @param {*} [def] - * @return {String} + * @returns {String} */ function getStoredValue(key, def) { if (typeof GM_getValue === 'function') { @@ -47,7 +47,7 @@ function getStoredValue(key, def) { * Retrieves and parses JSON data from local storage * @param {String} key * @param {*} [def] - * @return {*} + * @returns {*} */ function getStoredJSON(key, def) { var value = getStoredValue(key, def); From f9ce9698fa31fac7e0f1d63e23f0440de5f366ca Mon Sep 17 00:00:00 2001 From: rthaut Date: Thu, 15 Jun 2017 22:03:55 -0600 Subject: [PATCH 03/30] added optional params to filter add/remove/toggle methods to prevent automatically saving and/or applying changes --- lib/js/classes/base/Filter.class.js | 27 +++++++++++++++++++------ lib/js/classes/base/FilterList.class.js | 25 +++++++++++++++-------- 2 files changed, 38 insertions(+), 14 deletions(-) diff --git a/lib/js/classes/base/Filter.class.js b/lib/js/classes/base/Filter.class.js index ee3916c..7c22764 100644 --- a/lib/js/classes/base/Filter.class.js +++ b/lib/js/classes/base/Filter.class.js @@ -128,13 +128,18 @@ class Filter { /** * Hides the specified FilterObject * @param {FilterObject} filterObject + * @param {Boolean} [save=true] + * @param {Boolean} [apply=true] * @returns {Boolean} */ - hide(filterObject) { + hide(filterObject, apply = true, save = true) { console.group(this.label + '.hide()'); - var result = this.list.add(filterObject); + var result = this.list.add(filterObject, save); + + if (apply) { this.apply(); + } console.log('Return', result); console.log('Complete'); @@ -146,13 +151,18 @@ class Filter { /** * Shows the specified FilterObject * @param {FilterObject} filterObject + * @param {Boolean} [save=true] + * @param {Boolean} [apply=true] * @returns {Boolean} */ - show(filterObject) { + show(filterObject, apply = true, save = true) { console.group(this.label + '.hide()'); - var result = this.list.remove(filterObject); + var result = this.list.remove(filterObject, save); + + if (apply) { this.apply(); + } console.log('Return', result); console.log('Complete'); @@ -164,13 +174,18 @@ class Filter { /** * Toggles the specified FilterObject * @param {FilterObject} filterObject + * @param {Boolean} [save=true] + * @param {Boolean} [apply=true] * @returns {Boolean} */ - toggle(filterObject) { + toggle(filterObject, apply = true, save = true) { console.group(this.label + '.hide()'); - var result = this.list.toggle(filterObject); + var result = this.list.toggle(filterObject, save); + + if (apply) { this.apply(); + } console.log('Return', result); console.log('Complete'); diff --git a/lib/js/classes/base/FilterList.class.js b/lib/js/classes/base/FilterList.class.js index 5bdf84c..3b3af0a 100644 --- a/lib/js/classes/base/FilterList.class.js +++ b/lib/js/classes/base/FilterList.class.js @@ -50,9 +50,10 @@ class FilterList { /** * Adds/removes the provided FilterObject to/from the current FilterList * @param {FilterObject} filterObject + * @param {Boolean} [save=true] * @returns {Number} */ - toggle(filterObject) { + toggle(filterObject, save = true) { console.group(this.listObjectName + '.toggle()'); var idx = this.find(filterObject); @@ -64,7 +65,9 @@ class FilterList { idx = this.data.length - 1; } - this.save(); + if (save) { + this.save(); + } console.log('Return', idx); console.log('Complete'); @@ -76,9 +79,10 @@ class FilterList { /** * Adds the provided FilterObject to the current FilterList * @param {FilterObject} filterObject + * @param {Boolean} [save=true] * @returns {Boolean} */ - add(filterObject) { + add(filterObject, save = true) { console.group(this.listObjectName + '.add()'); var idx = this.find(filterObject); @@ -86,22 +90,25 @@ class FilterList { if (!found) { this.data.push(filterObject); - this.save(); + if (save) { + this.save(); + } } - console.log('Return', found); + console.log('Return', !found); console.log('Complete'); console.groupEnd(); - return found; + return !found; } /** * Removes the provided FilterObject from the current FilterList * @param {FilterObject} filterObject + * @param {Boolean} [save=true] * @returns {Boolean} */ - remove(filterObject) { + remove(filterObject, save = true) { console.group(this.listObjectName + '.remove()'); var idx = this.find(filterObject); @@ -109,7 +116,9 @@ class FilterList { if (found) { this.data.splice(idx, 1); - this.save(); + if (save) { + this.save(); + } } console.log('Return', found); From 9ef9b088756a1e8a472ec4293cdda793fbc3e500 Mon Sep 17 00:00:00 2001 From: rthaut Date: Thu, 15 Jun 2017 22:46:10 -0600 Subject: [PATCH 04/30] new button-less modal for daDialog --- lib/js/scripts/daDialog.class.js | 50 ++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/lib/js/scripts/daDialog.class.js b/lib/js/scripts/daDialog.class.js index 827c155..eaf8d81 100644 --- a/lib/js/scripts/daDialog.class.js +++ b/lib/js/scripts/daDialog.class.js @@ -6,7 +6,7 @@ var daDialog = { * Creates and shows a new HTML5 dialog * @param {HTMLElement} content * @param {String} [title] - * @param {Array} buttons + * @param {Array} [buttons] * @param {Object} [options] * @returns {HTMLElement} */ @@ -27,27 +27,29 @@ var daDialog = { var footer = document.createElement('footer'); - var btn, spacer; - for (var i = 0; i < buttons.length; i++) { - btn = document.createElement('button'); - btn.className = ['smbutton', 'smbutton-' + buttons[i].color, 'smbutton-size-large', 'smbutton-shadow'].join(' '); - btn.innerText = buttons[i].text; + if (buttons !== undefined && buttons.length > 0) { + var btn, spacer; + for (var i = 0; i < buttons.length; i++) { + btn = document.createElement('button'); + btn.className = ['smbutton', 'smbutton-' + buttons[i].color, 'smbutton-size-large', 'smbutton-shadow'].join(' '); + btn.innerText = buttons[i].text; - if (buttons[i].value !== undefined && buttons[i].value !== null) { - btn.setAttribute('return-value', buttons[i].value); - } + if (buttons[i].value !== undefined && buttons[i].value !== null) { + btn.setAttribute('return-value', buttons[i].value); + } - btn.addEventListener('click', function (event) { - dialog.close(event.target.getAttribute('return-value')); - }); + btn.addEventListener('click', function (event) { + dialog.close(event.target.getAttribute('return-value')); + }); - if (i > 0) { - spacer = document.createElement('span'); - spacer.innerText = ' '; - footer.appendChild(spacer); - } + if (i > 0) { + spacer = document.createElement('span'); + spacer.innerText = ' '; + footer.appendChild(spacer); + } - footer.appendChild(btn); + footer.appendChild(btn); + } } dialog.appendChild(footer); @@ -143,5 +145,17 @@ var daDialog = { }; return daDialog.dialog(content, title, buttons, options); + }, + + /** + * Displays a button-less modal using HTML5 dialog element and polyfill + * @param {String} text + * @param {String} [title] + */ + modal: function (text, title) { + var content = document.createElement('p'); + content.innerText = text; + + return daDialog.dialog(content, title); } } From ed6794a76e674692327105da1f2013aa950a3da8 Mon Sep 17 00:00:00 2001 From: rthaut Date: Thu, 15 Jun 2017 22:48:00 -0600 Subject: [PATCH 05/30] made import process async via ECMAScript 2015 Promises (re #20) --- lib/js/classes/base/Filter.class.js | 44 ++++- lib/js/daFilter.class.js | 248 ++++++++++++++-------------- 2 files changed, 162 insertions(+), 130 deletions(-) diff --git a/lib/js/classes/base/Filter.class.js b/lib/js/classes/base/Filter.class.js index 7c22764..5d5e7be 100644 --- a/lib/js/classes/base/Filter.class.js +++ b/lib/js/classes/base/Filter.class.js @@ -13,6 +13,44 @@ class Filter { this.list = list; } + /** + * Imports an array of FilterObjects asynchronously + * @param {Array} data + * @returns {Promise} + */ + import(data) { + console.log(this.label + '.import()'); + + var self = this; + return new Promise(function (resolve, reject) { + var results = { + 'total': data.length, + 'duplicate': 0, + 'invalid': 0, + 'success': 0 + }; + + data.forEach(function (properties) { + var filterObject = self.create(properties); + if (!filterObject.isValid()) { + results.invalid++; + } else { + if (self.hide(filterObject, false, false)) { + results.success++; + } else { + results.duplicate++; + } + } + }); + + self.list.save(); + self.apply(); + + console.log(self.label + '.import() :: Resolve', results); + resolve(results); + }); + } + /** * Creates a new FilterObject with the supplied properties * @param {Object} properties @@ -138,7 +176,7 @@ class Filter { var result = this.list.add(filterObject, save); if (apply) { - this.apply(); + this.apply(); } console.log('Return', result); @@ -161,7 +199,7 @@ class Filter { var result = this.list.remove(filterObject, save); if (apply) { - this.apply(); + this.apply(); } console.log('Return', result); @@ -184,7 +222,7 @@ class Filter { var result = this.list.toggle(filterObject, save); if (apply) { - this.apply(); + this.apply(); } console.log('Return', result); diff --git a/lib/js/daFilter.class.js b/lib/js/daFilter.class.js index 6f3cb5f..aeac919 100644 --- a/lib/js/daFilter.class.js +++ b/lib/js/daFilter.class.js @@ -35,8 +35,6 @@ class deviantARTFilter { addControls() { console.group('deviantARTFilter.addControls()'); - console.log('Adding overhead menu item.'); - var menuItem = $('') .html('Manage Filters') .addClass('oh-l') @@ -49,10 +47,7 @@ class deviantARTFilter { $('#oh-menu-deviant', '#overhead').after(menuCell); - console.log('Overhead menu item added.'); - - console.log('Complete'); - console.groupEnd(); + console.log('deviantARTFilter.addControls() :: Complete'); } /** @@ -60,12 +55,12 @@ class deviantARTFilter { * @param {Event} event */ toggleSettingChangeEventHandler(event) { - console.group('deviantARTFilter.toggleSettingChangeEventHandler()'); + console.log('deviantARTFilter.toggleSettingChangeEventHandler()'); var target = $(event.target); var setting = target.attr('name'); - console.log('Toggling setting "' + setting + '".'); + console.log('deviantARTFilter.addControls() :: Toggling setting "' + setting + '"'); switch (setting) { case 'placeholders': @@ -75,8 +70,7 @@ class deviantARTFilter { break; } - console.log('Complete'); - console.groupEnd(); + console.log('deviantARTFilter.toggleSettingChangeEventHandler() :: Complete'); } /** @@ -84,7 +78,7 @@ class deviantARTFilter { * @param {Event} event */ exportFiltersClickEventHandler(event) { - console.group('deviantARTFilter.exportFiltersClickEventHandler()'); + console.log('deviantARTFilter.exportFiltersClickEventHandler()'); event.preventDefault(); @@ -93,8 +87,7 @@ class deviantARTFilter { $('#importFiltersButton').prop('disabled', true).addClass('disabledbutton'); - console.log('Complete'); - console.groupEnd(); + console.log('deviantARTFilter.exportFiltersClickEventHandler() :: Complete'); } /** @@ -102,23 +95,37 @@ class deviantARTFilter { * @param {Event} event */ importFiltersClickEventHandler(event) { - console.group('deviantARTFilter.importFiltersClickEventHandler()'); + console.log('deviantARTFilter.importFiltersClickEventHandler()'); event.preventDefault(); - if ($('#filtersTextBox').val().length > 0) { - try { - this.importFilters(JSON.parse($('#filtersTextBox').val())); - } catch (ex) { - console.error(ex); - daDialog.alert('Unable to parse filters from JSON: ', ex.toString()); - } - } else { + if ($('#filtersTextBox').val().length == 0) { daDialog.alert('Nothing to import.'); + console.log('deviantARTFilter.importFiltersClickEventHandler() :: Nothing to import'); + return; + } + + var filterData; + try { + filterData = JSON.parse($('#filtersTextBox').val()); + } catch (error) { + console.error(error); + daDialog.alert('Unable to parse filters from JSON: ' + error.toString(), 'Import Failed'); + return; } - console.log('Complete'); - console.groupEnd(); + var dialog = daDialog.modal('Your filters are currently being imported. You will be notified when the import is compete.', 'Importing Filters'); + + var self = this; + setTimeout(function () { + self.importFilters(filterData).then(function (results) { + var message = self.importResultsToMessage(results); + dialog.close(); + daDialog.alert(message, 'Import Finished Successfully'); + }); + }, 100); + + console.log('deviantARTFilter.importFiltersClickEventHandler() :: Complete'); } /** @@ -126,20 +133,17 @@ class deviantARTFilter { * @param {Event} event */ importExportFilterChangeEventHandler(event) { - console.group('deviantARTFilter.importExportFilterChangeEventHandler()'); + console.log('deviantARTFilter.importExportFilterChangeEventHandler()'); if ($('#filtersTextBox').val().length > 0) { - console.log('Textarea has content; only allowing import'); $('#exportFiltersButton').prop('disabled', true).addClass('disabledbutton'); $('#importFiltersButton').prop('disabled', false).removeClass('disabledbutton'); } else { - console.log('Textarea has no content; only allowing export'); $('#exportFiltersButton').prop('disabled', false).removeClass('disabledbutton'); $('#importFiltersButton').prop('disabled', true).addClass('disabledbutton'); } - console.log('Complete'); - console.groupEnd(); + console.log('deviantARTFilter.importExportFilterChangeEventHandler() :: Complete'); } /** @@ -147,7 +151,7 @@ class deviantARTFilter { * @param {Event} event */ manage(event) { - console.group('deviantARTFilter.manage()'); + console.log('deviantARTFilter.manage()'); var content = $('
') @@ -155,16 +159,14 @@ class deviantARTFilter { .addClass('manage-filters-tabs') .appendTo(content); - if (this.filters.length) { - for (var i = 0; i < this.filters.length; i++) { - var listTab = $('') - .addClass('manage-filters-tab') - .addClass('active') - .attr('data-tab', 'manage-' + this.filters[i].label.toLowerCase() + '-tab-content') - .html('Filtered ' + this.filters[i].label) - .appendTo(tabs); - } - } + this.filters.forEach(function (filter) { + var listTab = $('') + .addClass('manage-filters-tab') + .addClass('active') + .attr('data-tab', 'manage-' + filter.label.toLowerCase() + '-tab-content') + .html('Filtered ' + filter.label) + .appendTo(tabs); + }); var settingsTab = $('') .addClass('manage-filters-tab') @@ -172,17 +174,15 @@ class deviantARTFilter { .html('Settings') .appendTo(tabs); - if (this.filters.length) { - for (var i = 0; i < this.filters.length; i++) { - var listContent = $('
') - .addClass('manage-filters-tab-content') - .attr('id', 'manage-' + this.filters[i].label.toLowerCase() + '-tab-content') - .appendTo(content); + this.filters.forEach(function (filter) { + var listContent = $('
') + .addClass('manage-filters-tab-content') + .attr('id', 'manage-' + filter.label.toLowerCase() + '-tab-content') + .appendTo(content); - var listTable = this.filters[i].getTable() - .appendTo(listContent); - } - } + var listTable = filter.getTable() + .appendTo(listContent); + }); var settingsContent = $('
') .addClass('manage-filters-tab-content') @@ -228,27 +228,25 @@ class deviantARTFilter { .appendTo(cleanFiltersFieldset); - if (this.filters.length) { - for (var i = 0; i < this.filters.length; i++) { - var cleanListButton = $('