Commit d411b7f5 authored by Andrea Giammarchi's avatar Andrea Giammarchi

Use the io-filter-table instead of the textarea

parent a7f97b71
......@@ -15,9 +15,11 @@
* along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
*/
@import "io-filter-table.scss";
@import "io-list-box.scss";
@import "io-popout.scss";
@import "io-popout-fixes.scss";
@import "io-list-box.scss";
@import "io-toggle.scss";
html
{
......@@ -1176,12 +1178,53 @@ html[dir="rtl"] #content-whitelist form button
Advanced tab content
*/
#custom-filters
{
margin-top: 3rem;
}
#custom-filters h3
{
font-size: 1.125rem;
font-weight: 400;
text-transform: uppercase;
}
#custom-filters .io-filter-table-title
{
font-weight: 600;
}
#custom-filters span:not(.io-filter-table-title)
{
margin: 0.5rem;
}
html:not([dir="rtl"]) #custom-filters .io-filter-table-title
{
margin-right: 2rem;
}
html[dir="rtl"] #custom-filters .io-filter-table-title
{
margin-left: 2rem;
}
/* spans after the toggle are shown/hidden accordingly to the toggle state */
#custom-filters io-toggle.io-filter-table-state:not([checked]) + span,
#custom-filters io-toggle.io-filter-table-state[checked] + span + span
{
display: none;
}
#custom-filters .io-filter-table-active,
#custom-filters .io-filter-table-inactive
{
color: #077CA6;
}
#all-filter-lists-table li.show-message .last-update,
#all-filter-lists-table li:not(.show-message) .message,
#custom-filters:not([data-mode="empty"]) #empty-custom-filters,
#custom-filters[data-mode="empty"] #custom-filters-raw,
#custom-filters:not([data-mode="write"]) #custom-filters-raw-controls,
#custom-filters:not([data-mode="read"]) #custom-filters-edit,
.state span,
#acceptable-ads:not(.show-dnt-notification) #dnt
{
......@@ -1209,25 +1252,6 @@ html[dir="rtl"] #content-whitelist form button
margin-bottom: 0.8rem;
}
#custom-filters-raw
{
width: 100%;
height: 23.6rem;
padding: 0.8rem;
}
#custom-filters-raw:focus
{
border: 2px solid #077CA6;
outline: none;
}
#empty-custom-filters
{
padding: 1.5rem;
border: 1px solid #CDCDCD;
}
/*
Help tab content
*/
......
......@@ -31,6 +31,22 @@ io-filter-list
--io-filter-list: ready;
width: 100%;
padding: 0;
/* used to bootstrap the component once it's visible */
animation: -io-filter-list 0.001s;
}
/* used to bootstrap the component once it's visible */
@keyframes -io-filter-list
{
from
{
--io-filter-list: #fff;
}
to
{
--io-filter-list: #000;
}
}
io-filter-list,
......@@ -41,6 +57,12 @@ io-filter-list *::after
box-sizing: border-box;
}
io-filter-list[disabled] io-checkbox,
io-filter-list[disabled] io-toggle,
{
pointer-events: none;
}
io-filter-list table
{
color: #505050;
......
......@@ -22,7 +22,11 @@ io-filter-table
{
display: flex;
flex-direction: column;
min-width: 700px;
}
io-filter-table[disabled]
{
opacity: 0.6;
}
io-filter-table > io-filter-search
......
......@@ -293,30 +293,19 @@
</div>
</section>
<section class="cols">
<section>
<header>
<h2 class="i18n_options_customFilters_title"></h2>
<p id="custom-filters-description" class="i18n_options_customFilters_description"></p>
</header>
<div id="custom-filters">
<h3 class="i18n_options_customFilters_widget_title"></h3>
<div id="empty-custom-filters">
<p class="i18n_options_customFilters_tip"></p>
<button class="i18n_options_customFilters_start primary" data-action="edit-custom-filters">
</button>
<p>
<a class="i18n_options_customFilters_learn" id="link-filters" target="_blank"></a>
</p>
</div>
<textarea id="custom-filters-raw" rows="15" spellcheck="false" autocomplete="off"></textarea>
<div class="side-controls">
<button id="custom-filters-edit" class="i18n_options_customFilter_edit secondary" data-action="edit-custom-filters">
</button>
<div class="side-controls" id="custom-filters-raw-controls">
<button class="i18n_options_customFilter_cancel secondary" data-action="cancel-custom-filters"></button>
<button class="i18n_options_customFilter_save primary" data-action="save-custom-filters"></button>
</div>
</div>
<h3>
<span class="i18n_options_customFilters_widget_title io-filter-table-title"></span>
<io-toggle class="io-filter-table-state" checked></io-toggle>
<span class="i18n_options_filterList_state_active io-filter-table-active"></span>
<span class="i18n_options_filterList_state_disabled io-filter-table-inactive"></span>
</h3>
<io-filter-table></io-filter-table>
</div>
</section>
</div>
......
......@@ -20,10 +20,13 @@
"use strict";
require("./io-filter-table");
require("./io-list-box");
require("./io-popout");
require("./io-toggle");
const {$} = require("./dom");
const port = browser.runtime.connect({name: "ui"});
let subscriptionsMap = Object.create(null);
let filtersMap = Object.create(null);
......@@ -488,8 +491,6 @@ function updateFilter(filter)
else
{
customFilters.push(filter.text);
if (isCustomFiltersLoaded)
updateCustomFiltersUi();
}
filtersMap[filter.text] = filter;
......@@ -500,7 +501,17 @@ function loadCustomFilters(filters)
for (const filter of filters)
updateFilter(filter);
setCustomFiltersView("read");
const cfTable = $("#custom-filters io-filter-table");
cfTable.filters = filters.filter(({text}) => customFilters.includes(text));
const toggle = $("#custom-filters io-toggle.io-filter-table-state");
cfTable.disabled = !toggle.checked;
toggle.addEventListener("change", toggleFiltersTable);
}
function toggleFiltersTable(event)
{
const cfTable = $("#custom-filters io-filter-table");
cfTable.disabled = !event.currentTarget.checked;
}
function removeCustomFilter(text)
......@@ -508,14 +519,6 @@ function removeCustomFilter(text)
const index = customFilters.indexOf(text);
if (index >= 0)
customFilters.splice(index, 1);
updateCustomFiltersUi();
}
function updateCustomFiltersUi()
{
const customFiltersListElement = $("#custom-filters-raw");
customFiltersListElement.value = customFilters.join("\n");
}
function getLanguageTitle(item)
......@@ -612,18 +615,12 @@ function execAction(action, element)
closeDialog();
break;
}
case "cancel-custom-filters":
setCustomFiltersView("read");
break;
case "change-language-subscription":
changeLanguageSubscription(findParentData(element, "access", false));
break;
case "close-dialog":
closeDialog();
break;
case "edit-custom-filters":
setCustomFiltersView("write");
break;
case "hide-more-filters-section":
$("#more-filters").setAttribute("aria-hidden", true);
break;
......@@ -658,17 +655,6 @@ function execAction(action, element)
url: findParentData(element, "access", false)
});
break;
case "save-custom-filters":
sendMessageHandleErrors({
type: "filters.importRaw",
text: $("#custom-filters-raw").value,
removeExisting: true
},
() =>
{
setCustomFiltersView("read");
});
break;
case "show-more-filters-section":
$("#more-filters").setAttribute("aria-hidden", false);
break;
......@@ -780,27 +766,6 @@ function changeLanguageSubscription(url)
}
}
function setCustomFiltersView(mode)
{
const customFiltersElement = $("#custom-filters-raw");
updateCustomFiltersUi();
if (mode == "read")
{
customFiltersElement.disabled = true;
if (!customFiltersElement.value)
{
setCustomFiltersView("empty");
return;
}
}
else if (mode == "write")
{
customFiltersElement.disabled = false;
}
$("#custom-filters").dataset.mode = mode;
}
function onClick(e)
{
let actions = findParentData(e.target, "action", false);
......@@ -1012,7 +977,6 @@ function onDOMLoaded()
getDocLink("filterdoc").then(link =>
{
setElementLinks("custom-filters-description", link);
$("#link-filters").setAttribute("href", link);
});
getDocLink("subscriptions").then(link =>
......@@ -1020,9 +984,6 @@ function onDOMLoaded()
$("#filter-lists-learn-more").setAttribute("href", link);
});
$("#custom-filters-raw").setAttribute("placeholder",
getMessage("options_customFilters_edit_placeholder", ["/ads/track/*"]));
// Help tab
getDocLink("adblock_plus_report_bug").then(link =>
{
......@@ -1207,7 +1168,6 @@ function populateLists()
for (const property in collections)
collections[property].clearAll();
setCustomFiltersView("empty");
browser.runtime.sendMessage({
type: "subscriptions.get",
special: true
......@@ -1454,8 +1414,6 @@ function onPrefMessage(key, value, initial)
checkbox.setAttribute("aria-checked", value);
}
const port = browser.runtime.connect({name: "ui"});
port.onMessage.addListener((message) =>
{
switch (message.type)
......
......@@ -116,6 +116,12 @@ class IOElement extends HyperHTMLElement
return IOElement.getID(this);
}
// returns true only when the component is live and styled
get ready()
{
return !!this.offsetParent && this.isStyled();
}
// whenever an element is created, render its content once
created() { this.render(); }
......
......@@ -26,10 +26,10 @@ const IOScrollbar = require("./io-scrollbar");
const {utils, wire} = IOElement;
const {$} = require("./dom");
const port = browser.runtime.connect({name: "ui"});
const prevFilterText = new WeakMap();
const port = browser.runtime.connect({name: "ui"});
port.postMessage({
type: "filters.listen",
filter: ["disabled"]
......@@ -38,6 +38,16 @@ port.postMessage({
// <io-filter-list disabled />.{filters = [...]}
class IOFilterList extends IOElement
{
static get booleanAttributes()
{
return ["disabled"];
}
static get observedAttributes()
{
return ["filters"];
}
get selected()
{
return this._selected || (this._selected = new Set());
......@@ -71,21 +81,6 @@ class IOFilterList extends IOElement
};
}
static get observedAttributes()
{
return ["disabled", "filters"];
}
get disabled()
{
return this.hasAttribute("disabled");
}
set disabled(value)
{
utils.boolean.attribute(this, "disabled", value);
}
get filters()
{
return this.state.filters || [];
......@@ -93,14 +88,14 @@ class IOFilterList extends IOElement
set filters(value)
{
// if the offsetParent is null, hence the component is not visible, or
// if the related CSS is not loaded yet, this component cannot bootstrap
// because its TBODY will never be scrollable so there's no way
// to calculate its viewport height in pixels
// in such case, just execute later on until the CSS is parsed
if (!this.isStyled())
if (!this.ready)
{
this._filters = value;
window.addEventListener("load", this);
return;
}
this.selected = [];
......@@ -139,6 +134,23 @@ class IOFilterList extends IOElement
created()
{
setupPort.call(this);
// force one off setup whenever the component enters the view
if (!this.ready)
this.addEventListener(
"animationstart",
function prepare(event)
{
this.removeEventListener(event.type, prepare);
if (this._filters)
{
this.filters = this._filters;
this._filters = null;
}
}
);
// the rest of the setup
this.scrollbar = new IOScrollbar();
this.scrollbar.direction = "vertical";
this.scrollbar.addEventListener("scroll", () =>
......@@ -190,12 +202,6 @@ class IOFilterList extends IOElement
}
}
onload()
{
window.removeEventListener("load", this);
this.filters = this._filters;
}
onheaderclick(event)
{
const th = event.target.closest("th");
......@@ -407,7 +413,10 @@ class IOFilterList extends IOElement
tbody.scrollTop = scrollTop % rowHeight;
}
// keep growing the fake list until the tbody becomes scrollable
else if (!tbody || tbody.scrollHeight <= tbody.clientHeight)
else if (
!tbody ||
(tbody.scrollHeight <= tbody.clientHeight && tbody.clientHeight)
)
{
this.setState({
tbody: tbody || $("tbody", this),
......
......@@ -21,7 +21,7 @@ const IOElement = require("./io-element");
const IOFilterList = require("./io-filter-list");
const IOFilterSearch = require("./io-filter-search");
const {$, clipboard} = require("./dom");
const {clipboard} = require("./dom");
const {bind, wire} = IOElement;
......@@ -157,11 +157,12 @@ class IOFilterTable extends IOElement
onFilterAdd(event)
{
const unknown = new WeakSet();
const filtersBefore = this.filters;
const filters = event.detail
.split(/(?:\r\n|\n)/)
.map(text =>
{
let value = this.filters.find(
let value = filtersBefore.find(
filter => filter.text === text
);
if (!value)
......@@ -179,12 +180,22 @@ class IOFilterTable extends IOElement
{
if (!errors.length)
{
for (const filter of filters)
// this is false only when the extension sets filters right away,
// as example the first time custom filters are created.
// in that case, the order should be the one the extension decided.
if (filtersBefore === this.filters)
{
if (!unknown.has(filter))
this.filters.splice(this.filters.indexOf(filter), 1);
this.filters.unshift(filter);
for (const filter of filters)
{
if (!unknown.has(filter))
this.filters.splice(this.filters.indexOf(filter), 1);
this.filters.unshift(filter);
}
}
// needed in case there were no filters whatsoever
// and the table never got a chance to initialize
// will be more like a no-op if already initialized
this.render();
updateList(this.list);
this.list.scrollTo(this.filters[0]);
this.search.value = "";
......@@ -205,15 +216,20 @@ class IOFilterTable extends IOElement
{
const {disabled} = this;
const {filters, match, ready} = this.state;
if (!ready)
if (!ready || !filters.length)
return;
// simply update inner components
// no need to render any html in here
// update inner components setting filters
// only if necessary
this.search.disabled = disabled;
this.search.filters = filters;
this.search.match = match;
if (this.search.filters !== filters)
this.search.filters = filters;
this.list.disabled = disabled;
this.list.filters = filters;
if (this.list.filters !== filters)
this.list.filters = filters;
this.renderFooter();
}
......
......@@ -599,6 +599,9 @@
filterStorage.removeFilter(filter);
else
filterStorage.removeFilter(filter, subscription, message.index);
// in order to behave, from consumer perspective, like any other
// method that could produce errors, return an Array, even if empty
return [];
}
function listen(type, filters, newFilter, message, senderTabId)
......
......@@ -270,7 +270,7 @@
},
"options_filterList_state_disabled": {
"description": "State of filter list in Advanced tab",
"message": "Disabled"
"message": "Inactive"
},
"options_filterList_now": {
"description": "Text inside 'Last update' column in Advanced tab",
......@@ -294,38 +294,16 @@
},
"options_customFilters_title": {
"description": "Section title in Advanced tab",
"message": "Create and edit your filter list"
"message": "Your custom filters"
},
"options_customFilters_description": {
"description": "Custom filter lists section description in Advanced tab",
"message": "Create your own filters to further control what content Adblock Plus blocks. <a0>Learn how to create filters</a0>"
"message": "Create and maintain your own filters to further control what content Adblock Plus allows or blocks. <a0>Learn how to write filters</a0>"
},
"options_customFilters_widget_title": {
"description": "Custom filter widget title in Advanced tab",
"message": "My filter list"
},
"options_customFilters_tip": {
"description": "Custom filter widget(empty) hint text in Advanced tab",
"message": "Filters you create will show up here."
},
"options_customFilters_start": {
"description": "Custom filter widget(empty) button text in Advanced tab",
"message": "Start creating my filter list"
},
"options_customFilters_learn": {
"description": "Custom filter widget(empty) link text in Advanced tab",
"message": "Learn how to create filters"
},
"options_customFilters_edit_placeholder": {
"description": "Custom filter widget(empty) placeholder text in Advanced tab",
"message": "e.g. $filter$",
"placeholders": {
"filter": {
"content": "$1",
"example": "/ads/track/*"
}
}
},
"options_filterList_lastDownload_invalidURL": {
"description": "Error message in advanced tab",
"message": "Failed, not a valid address"
......@@ -358,18 +336,10 @@
"description": "Context menu item in advanced tab, appears after click on subscription",
"message": "source"
},
"options_customFilter_edit": {
"description": "Label below single input view of user's filter list in Advanced tab",
"message": "Edit filters"
},
"options_customFilter_cancel": {
"description": "Label below raw view of user's filter list in Advanced tab",
"message": "Cancel"
},
"options_customFilter_save": {
"description": "Label below raw view of user's filter list in Advanced tab",
"message": "Save"
},
"options_help_description": {
"description": "Help tab description",
"message": "Find help or get in touch with us"
......
......@@ -59,7 +59,7 @@
},
"css": "node-sass ./css/$1.scss ./skin/$1.css",
"js": [
"!prod eslint ./js/**/*.js",
"eslint ./js/**/*.js",
"echo \"/* eslint-disable */$(browserify --node $1)\">$2"
],
"test": {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment