mirror of
https://github.com/gorhill/uBlock.git
synced 2024-10-04 08:37:11 +02:00
Warn when navigating away from pane with unsaved changes
Related issue: - https://github.com/gorhill/uBlock/issues/3271 When navigating away by clicking another pane tab button, there will be an embedded warning, which can be ignore in order to proceed to the new pane, or dismissed by either clicking on the "Stay" button or anywhere else in the dashboard. When navigating away by trying to close the tab, there will be a built-in browser warning asking for confirmation.
This commit is contained in:
parent
6f9216585b
commit
f677443878
@ -11,6 +11,18 @@
|
|||||||
"message":"uBlock₀ — Dashboard",
|
"message":"uBlock₀ — Dashboard",
|
||||||
"description":"English: uBlock₀ — Dashboard"
|
"description":"English: uBlock₀ — Dashboard"
|
||||||
},
|
},
|
||||||
|
"dashboardUnsavedWarning":{
|
||||||
|
"message":"Warning! You have unsaved changes",
|
||||||
|
"description":"A warning in the dashboard when navigating away from unsaved changes"
|
||||||
|
},
|
||||||
|
"dashboardUnsavedWarningStay":{
|
||||||
|
"message":"Stay",
|
||||||
|
"description":"Label for button to prevent navigating away from unsaved changes"
|
||||||
|
},
|
||||||
|
"dashboardUnsavedWarningIgnore":{
|
||||||
|
"message":"Ignore",
|
||||||
|
"description":"Label for button to ignore unsaved changes"
|
||||||
|
},
|
||||||
"settingsPageName":{
|
"settingsPageName":{
|
||||||
"message":"Settings",
|
"message":"Settings",
|
||||||
"description":"appears as tab name in dashboard"
|
"description":"appears as tab name in dashboard"
|
||||||
|
@ -66,12 +66,32 @@ html, body {
|
|||||||
border-bottom: 1px solid white;
|
border-bottom: 1px solid white;
|
||||||
}
|
}
|
||||||
iframe {
|
iframe {
|
||||||
margin: 0;
|
|
||||||
border: 0;
|
|
||||||
padding: 0;
|
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
#unsavedWarning {
|
||||||
|
box-shadow: rgba(128,128,128,0.4) 0 4px 4px;
|
||||||
|
display: none;
|
||||||
|
left: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
z-index: 20;
|
||||||
|
}
|
||||||
|
#unsavedWarning.on {
|
||||||
|
display: initial;
|
||||||
|
}
|
||||||
|
#unsavedWarning > div:first-of-type {
|
||||||
|
background-color: #ffffcc;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
#unsavedWarning > div:last-of-type {
|
||||||
|
height: 100vh;
|
||||||
|
position: absolute;
|
||||||
|
width: 100vw;
|
||||||
|
}
|
||||||
|
|
||||||
body:not(.canUpdateShortcuts) .tabButton[href="#shortcuts.html"] {
|
body:not(.canUpdateShortcuts) .tabButton[href="#shortcuts.html"] {
|
||||||
display: none;
|
display: none;
|
||||||
|
@ -22,6 +22,14 @@
|
|||||||
--><a class="tabButton" href="#about.html" data-i18n="aboutPageName"></a>
|
--><a class="tabButton" href="#about.html" data-i18n="aboutPageName"></a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="unsavedWarning">
|
||||||
|
<div>
|
||||||
|
<span data-i18n="dashboardUnsavedWarning"></span> 
|
||||||
|
<button class="custom" data-i18n="dashboardUnsavedWarningStay"></button> 
|
||||||
|
<button class="custom" data-i18n="dashboardUnsavedWarningIgnore"></button>
|
||||||
|
</div>
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<iframe id="iframe" src=""></iframe>
|
<iframe id="iframe" src=""></iframe>
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -58,14 +58,13 @@ window.addEventListener('beforeunload', ( ) => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// This is to give a visual hint that the content of user blacklist has changed.
|
// This is to give a visual hint that the content of user blacklist has changed.
|
||||||
|
|
||||||
const userFiltersChanged = function(changed) {
|
const userFiltersChanged = function(changed) {
|
||||||
if ( typeof changed !== 'boolean' ) {
|
if ( typeof changed !== 'boolean' ) {
|
||||||
changed = cmEditor.getValue().trim() !== cachedUserFilters;
|
changed = self.hasUnsavedData();
|
||||||
}
|
}
|
||||||
uDom.nodeFromId('userFiltersApply').disabled = !changed;
|
uDom.nodeFromId('userFiltersApply').disabled = !changed;
|
||||||
uDom.nodeFromId('userFiltersRevert').disabled = !changed;
|
uDom.nodeFromId('userFiltersRevert').disabled = !changed;
|
||||||
@ -214,6 +213,12 @@ self.cloud.onPull = setCloudData;
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
self.hasUnsavedData = function() {
|
||||||
|
return cmEditor.getValue().trim() !== cachedUserFilters;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
// Handle user interaction
|
// Handle user interaction
|
||||||
uDom('#importUserFiltersFromFile').on('click', startImportFilePicker);
|
uDom('#importUserFiltersFromFile').on('click', startImportFilePicker);
|
||||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||||
|
@ -25,19 +25,20 @@
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var listDetails = {},
|
const lastUpdateTemplateString = vAPI.i18n('3pLastUpdate');
|
||||||
filteringSettingsHash = '',
|
const reValidExternalList = /[a-z-]+:\/\/\S*\/\S+/;
|
||||||
lastUpdateTemplateString = vAPI.i18n('3pLastUpdate'),
|
|
||||||
reValidExternalList = /[a-z-]+:\/\/\S*\/\S+/,
|
let listDetails = {};
|
||||||
hideUnusedSet = new Set();
|
let filteringSettingsHash = '';
|
||||||
|
let hideUnusedSet = new Set();
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onMessage = function(msg) {
|
const onMessage = function(msg) {
|
||||||
switch ( msg.what ) {
|
switch ( msg.what ) {
|
||||||
case 'assetUpdated':
|
case 'assetUpdated':
|
||||||
updateAssetStatus(msg);
|
updateAssetStatus(msg);
|
||||||
@ -54,39 +55,39 @@ var onMessage = function(msg) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
var messaging = vAPI.messaging;
|
const messaging = vAPI.messaging;
|
||||||
messaging.addChannelListener('dashboard', onMessage);
|
messaging.addChannelListener('dashboard', onMessage);
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var renderNumber = function(value) {
|
const renderNumber = function(value) {
|
||||||
return value.toLocaleString();
|
return value.toLocaleString();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var renderFilterLists = function(soft) {
|
const renderFilterLists = function(soft) {
|
||||||
var listGroupTemplate = uDom('#templates .groupEntry'),
|
const listGroupTemplate = uDom('#templates .groupEntry');
|
||||||
listEntryTemplate = uDom('#templates .listEntry'),
|
const listEntryTemplate = uDom('#templates .listEntry');
|
||||||
listStatsTemplate = vAPI.i18n('3pListsOfBlockedHostsPerListStats'),
|
const listStatsTemplate = vAPI.i18n('3pListsOfBlockedHostsPerListStats');
|
||||||
renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString,
|
const renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString;
|
||||||
groupNames = new Map([ [ 'user', '' ] ]);
|
const groupNames = new Map([ [ 'user', '' ] ]);
|
||||||
|
|
||||||
// Assemble a pretty list name if possible
|
// Assemble a pretty list name if possible
|
||||||
var listNameFromListKey = function(listKey) {
|
const listNameFromListKey = function(listKey) {
|
||||||
var list = listDetails.current[listKey] || listDetails.available[listKey];
|
const list = listDetails.current[listKey] || listDetails.available[listKey];
|
||||||
var listTitle = list ? list.title : '';
|
const listTitle = list ? list.title : '';
|
||||||
if ( listTitle === '' ) { return listKey; }
|
if ( listTitle === '' ) { return listKey; }
|
||||||
return listTitle;
|
return listTitle;
|
||||||
};
|
};
|
||||||
|
|
||||||
var liFromListEntry = function(listKey, li, hideUnused) {
|
const liFromListEntry = function(listKey, li, hideUnused) {
|
||||||
var entry = listDetails.available[listKey],
|
const entry = listDetails.available[listKey];
|
||||||
elem;
|
|
||||||
if ( !li ) {
|
if ( !li ) {
|
||||||
li = listEntryTemplate.clone().nodeAt(0);
|
li = listEntryTemplate.clone().nodeAt(0);
|
||||||
}
|
}
|
||||||
var on = entry.off !== true;
|
const on = entry.off !== true;
|
||||||
|
let elem;
|
||||||
if ( li.getAttribute('data-listkey') !== listKey ) {
|
if ( li.getAttribute('data-listkey') !== listKey ) {
|
||||||
li.setAttribute('data-listkey', listKey);
|
li.setAttribute('data-listkey', listKey);
|
||||||
elem = li.querySelector('input[type="checkbox"]');
|
elem = li.querySelector('input[type="checkbox"]');
|
||||||
@ -123,7 +124,7 @@ var renderFilterLists = function(soft) {
|
|||||||
li.querySelector('input[type="checkbox"]').checked = on;
|
li.querySelector('input[type="checkbox"]').checked = on;
|
||||||
}
|
}
|
||||||
elem = li.querySelector('span.counts');
|
elem = li.querySelector('span.counts');
|
||||||
var text = '';
|
let text = '';
|
||||||
if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) {
|
if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) {
|
||||||
text = listStatsTemplate
|
text = listStatsTemplate
|
||||||
.replace('{{used}}', renderNumber(on ? entry.entryUsedCount : 0))
|
.replace('{{used}}', renderNumber(on ? entry.entryUsedCount : 0))
|
||||||
@ -131,8 +132,8 @@ var renderFilterLists = function(soft) {
|
|||||||
}
|
}
|
||||||
elem.textContent = text;
|
elem.textContent = text;
|
||||||
// https://github.com/chrisaljoudi/uBlock/issues/104
|
// https://github.com/chrisaljoudi/uBlock/issues/104
|
||||||
var asset = listDetails.cache[listKey] || {};
|
const asset = listDetails.cache[listKey] || {};
|
||||||
var remoteURL = asset.remoteURL;
|
const remoteURL = asset.remoteURL;
|
||||||
li.classList.toggle(
|
li.classList.toggle(
|
||||||
'unsecure',
|
'unsecure',
|
||||||
typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0
|
typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0
|
||||||
@ -155,24 +156,23 @@ var renderFilterLists = function(soft) {
|
|||||||
return li;
|
return li;
|
||||||
};
|
};
|
||||||
|
|
||||||
var listEntryCountFromGroup = function(listKeys) {
|
const listEntryCountFromGroup = function(listKeys) {
|
||||||
if ( Array.isArray(listKeys) === false ) { return ''; }
|
if ( Array.isArray(listKeys) === false ) { return ''; }
|
||||||
var count = 0,
|
let count = 0,
|
||||||
total = 0;
|
total = 0;
|
||||||
var i = listKeys.length;
|
for ( const listKey of listKeys ) {
|
||||||
while ( i-- ) {
|
if ( listDetails.available[listKey].off !== true ) {
|
||||||
if ( listDetails.available[listKeys[i]].off !== true ) {
|
|
||||||
count += 1;
|
count += 1;
|
||||||
}
|
}
|
||||||
total += 1;
|
total += 1;
|
||||||
}
|
}
|
||||||
return total !== 0 ?
|
return total !== 0 ?
|
||||||
'(' + count.toLocaleString() + '/' + total.toLocaleString() + ')' :
|
`(${count.toLocaleString()}/${total.toLocaleString()})` :
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
|
|
||||||
var liFromListGroup = function(groupKey, listKeys) {
|
const liFromListGroup = function(groupKey, listKeys) {
|
||||||
let liGroup = document.querySelector('#lists > .groupEntry[data-groupkey="' + groupKey + '"]');
|
let liGroup = document.querySelector(`#lists > .groupEntry[data-groupkey="${groupKey}"]`);
|
||||||
if ( liGroup === null ) {
|
if ( liGroup === null ) {
|
||||||
liGroup = listGroupTemplate.clone().nodeAt(0);
|
liGroup = listGroupTemplate.clone().nodeAt(0);
|
||||||
let groupName = groupNames.get(groupKey);
|
let groupName = groupNames.get(groupKey);
|
||||||
@ -207,7 +207,7 @@ var renderFilterLists = function(soft) {
|
|||||||
return liGroup;
|
return liGroup;
|
||||||
};
|
};
|
||||||
|
|
||||||
var groupsFromLists = function(lists) {
|
const groupsFromLists = function(lists) {
|
||||||
let groups = new Map();
|
let groups = new Map();
|
||||||
let listKeys = Object.keys(lists);
|
let listKeys = Object.keys(lists);
|
||||||
for ( let listKey of listKeys ) {
|
for ( let listKey of listKeys ) {
|
||||||
@ -225,7 +225,7 @@ var renderFilterLists = function(soft) {
|
|||||||
return groups;
|
return groups;
|
||||||
};
|
};
|
||||||
|
|
||||||
var onListsReceived = function(details) {
|
const onListsReceived = function(details) {
|
||||||
// Before all, set context vars
|
// Before all, set context vars
|
||||||
listDetails = details;
|
listDetails = details;
|
||||||
|
|
||||||
@ -238,22 +238,22 @@ var renderFilterLists = function(soft) {
|
|||||||
uDom('#lists .listEntries .listEntry[data-listkey]').addClass('discard');
|
uDom('#lists .listEntries .listEntry[data-listkey]').addClass('discard');
|
||||||
|
|
||||||
// Remove import widget while we recreate list of lists.
|
// Remove import widget while we recreate list of lists.
|
||||||
var importWidget = uDom('.listEntry.toImport').detach();
|
const importWidget = uDom('.listEntry.toImport').detach();
|
||||||
|
|
||||||
// Visually split the filter lists in purpose-based groups
|
// Visually split the filter lists in purpose-based groups
|
||||||
var ulLists = document.querySelector('#lists'),
|
const ulLists = document.querySelector('#lists');
|
||||||
groups = groupsFromLists(details.available),
|
const groups = groupsFromLists(details.available);
|
||||||
groupKeys = [
|
const groupKeys = [
|
||||||
'user',
|
'user',
|
||||||
'default',
|
'default',
|
||||||
'ads',
|
'ads',
|
||||||
'privacy',
|
'privacy',
|
||||||
'malware',
|
'malware',
|
||||||
'annoyances',
|
'annoyances',
|
||||||
'multipurpose',
|
'multipurpose',
|
||||||
'regions',
|
'regions',
|
||||||
'custom'
|
'custom'
|
||||||
];
|
];
|
||||||
document.body.classList.toggle('hideUnused', mustHideUnusedLists('*'));
|
document.body.classList.toggle('hideUnused', mustHideUnusedLists('*'));
|
||||||
for ( let i = 0; i < groupKeys.length; i++ ) {
|
for ( let i = 0; i < groupKeys.length; i++ ) {
|
||||||
let groupKey = groupKeys[i];
|
let groupKey = groupKeys[i];
|
||||||
@ -269,8 +269,7 @@ var renderFilterLists = function(soft) {
|
|||||||
groups.delete(groupKey);
|
groups.delete(groupKey);
|
||||||
}
|
}
|
||||||
// For all groups not covered above (if any left)
|
// For all groups not covered above (if any left)
|
||||||
groupKeys = Object.keys(groups);
|
for ( const groupKey of Object.keys(groups) ) {
|
||||||
for ( let groupKey of groupKeys.keys() ) {
|
|
||||||
ulLists.appendChild(liFromListGroup(groupKey, groupKey));
|
ulLists.appendChild(liFromListGroup(groupKey, groupKey));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +307,7 @@ var renderFilterLists = function(soft) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var renderWidgets = function() {
|
const renderWidgets = function() {
|
||||||
uDom('#buttonApply').toggleClass(
|
uDom('#buttonApply').toggleClass(
|
||||||
'disabled',
|
'disabled',
|
||||||
filteringSettingsHash === hashFromCurrentFromSettings()
|
filteringSettingsHash === hashFromCurrentFromSettings()
|
||||||
@ -325,8 +324,8 @@ var renderWidgets = function() {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var updateAssetStatus = function(details) {
|
const updateAssetStatus = function(details) {
|
||||||
let li = document.querySelector(
|
const li = document.querySelector(
|
||||||
'#lists .listEntry[data-listkey="' + details.key + '"]'
|
'#lists .listEntry[data-listkey="' + details.key + '"]'
|
||||||
);
|
);
|
||||||
if ( li === null ) { return; }
|
if ( li === null ) { return; }
|
||||||
@ -352,17 +351,14 @@ var updateAssetStatus = function(details) {
|
|||||||
|
|
||||||
**/
|
**/
|
||||||
|
|
||||||
var hashFromCurrentFromSettings = function() {
|
const hashFromCurrentFromSettings = function() {
|
||||||
var hash = [
|
const hash = [
|
||||||
uDom.nodeFromId('parseCosmeticFilters').checked,
|
uDom.nodeFromId('parseCosmeticFilters').checked,
|
||||||
uDom.nodeFromId('ignoreGenericCosmeticFilters').checked
|
uDom.nodeFromId('ignoreGenericCosmeticFilters').checked
|
||||||
];
|
];
|
||||||
var listHash = [],
|
const listHash = [];
|
||||||
listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'),
|
const listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)');
|
||||||
liEntry,
|
for ( const liEntry of listEntries ) {
|
||||||
i = listEntries.length;
|
|
||||||
while ( i-- ) {
|
|
||||||
liEntry = listEntries[i];
|
|
||||||
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
||||||
listHash.push(liEntry.getAttribute('data-listkey'));
|
listHash.push(liEntry.getAttribute('data-listkey'));
|
||||||
}
|
}
|
||||||
@ -378,15 +374,15 @@ var hashFromCurrentFromSettings = function() {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onFilteringSettingsChanged = function() {
|
const onFilteringSettingsChanged = function() {
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onRemoveExternalList = function(ev) {
|
const onRemoveExternalList = function(ev) {
|
||||||
var liEntry = uDom(this).ancestors('[data-listkey]'),
|
const liEntry = uDom(this).ancestors('[data-listkey]');
|
||||||
listKey = liEntry.attr('data-listkey');
|
const listKey = liEntry.attr('data-listkey');
|
||||||
if ( listKey ) {
|
if ( listKey ) {
|
||||||
liEntry.toggleClass('toRemove');
|
liEntry.toggleClass('toRemove');
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
@ -396,10 +392,10 @@ var onRemoveExternalList = function(ev) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var onPurgeClicked = function() {
|
const onPurgeClicked = function(ev) {
|
||||||
var button = uDom(this),
|
const button = uDom(ev.target);
|
||||||
liEntry = button.ancestors('[data-listkey]'),
|
const liEntry = button.ancestors('[data-listkey]');
|
||||||
listKey = liEntry.attr('data-listkey');
|
const listKey = liEntry.attr('data-listkey');
|
||||||
if ( !listKey ) { return; }
|
if ( !listKey ) { return; }
|
||||||
|
|
||||||
messaging.send('dashboard', { what: 'purgeCache', assetKey: listKey });
|
messaging.send('dashboard', { what: 'purgeCache', assetKey: listKey });
|
||||||
@ -419,7 +415,7 @@ var onPurgeClicked = function() {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var selectFilterLists = function(callback) {
|
const selectFilterLists = function(callback) {
|
||||||
// Cosmetic filtering switch
|
// Cosmetic filtering switch
|
||||||
messaging.send('dashboard', {
|
messaging.send('dashboard', {
|
||||||
what: 'userSettings',
|
what: 'userSettings',
|
||||||
@ -433,28 +429,24 @@ var selectFilterLists = function(callback) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Filter lists to select
|
// Filter lists to select
|
||||||
var toSelect = [],
|
const toSelect = [];
|
||||||
liEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'),
|
let liEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)');
|
||||||
i = liEntries.length,
|
for ( const liEntry of liEntries ) {
|
||||||
liEntry;
|
|
||||||
while ( i-- ) {
|
|
||||||
liEntry = liEntries[i];
|
|
||||||
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
||||||
toSelect.push(liEntry.getAttribute('data-listkey'));
|
toSelect.push(liEntry.getAttribute('data-listkey'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// External filter lists to remove
|
// External filter lists to remove
|
||||||
var toRemove = [];
|
const toRemove = [];
|
||||||
liEntries = document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]');
|
liEntries = document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]');
|
||||||
i = liEntries.length;
|
for ( const liEntry of liEntries ) {
|
||||||
while ( i-- ) {
|
toRemove.push(liEntry.getAttribute('data-listkey'));
|
||||||
toRemove.push(liEntries[i].getAttribute('data-listkey'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// External filter lists to import
|
// External filter lists to import
|
||||||
var externalListsElem = document.getElementById('externalLists'),
|
const externalListsElem = document.getElementById('externalLists');
|
||||||
toImport = externalListsElem.value.trim();
|
const toImport = externalListsElem.value.trim();
|
||||||
externalListsElem.value = '';
|
externalListsElem.value = '';
|
||||||
uDom.nodeFromId('importLists').checked = false;
|
uDom.nodeFromId('importLists').checked = false;
|
||||||
|
|
||||||
@ -473,30 +465,28 @@ var selectFilterLists = function(callback) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var buttonApplyHandler = function() {
|
const buttonApplyHandler = function() {
|
||||||
uDom('#buttonApply').removeClass('enabled');
|
uDom('#buttonApply').removeClass('enabled');
|
||||||
var onSelectionDone = function() {
|
selectFilterLists(( ) => {
|
||||||
messaging.send('dashboard', { what: 'reloadAllFilters' });
|
messaging.send('dashboard', { what: 'reloadAllFilters' });
|
||||||
};
|
});
|
||||||
selectFilterLists(onSelectionDone);
|
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var buttonUpdateHandler = function() {
|
const buttonUpdateHandler = function() {
|
||||||
var onSelectionDone = function() {
|
selectFilterLists(( ) => {
|
||||||
document.body.classList.add('updating');
|
document.body.classList.add('updating');
|
||||||
messaging.send('dashboard', { what: 'forceUpdateAssets' });
|
messaging.send('dashboard', { what: 'forceUpdateAssets' });
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
};
|
});
|
||||||
selectFilterLists(onSelectionDone);
|
|
||||||
renderWidgets();
|
renderWidgets();
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var buttonPurgeAllHandler = function(ev) {
|
const buttonPurgeAllHandler = function(ev) {
|
||||||
uDom('#buttonPurgeAll').removeClass('enabled');
|
uDom('#buttonPurgeAll').removeClass('enabled');
|
||||||
messaging.send(
|
messaging.send(
|
||||||
'dashboard',
|
'dashboard',
|
||||||
@ -504,13 +494,15 @@ var buttonPurgeAllHandler = function(ev) {
|
|||||||
what: 'purgeAllCaches',
|
what: 'purgeAllCaches',
|
||||||
hard: ev.ctrlKey && ev.shiftKey
|
hard: ev.ctrlKey && ev.shiftKey
|
||||||
},
|
},
|
||||||
function() { renderFilterLists(true); }
|
( ) => {
|
||||||
|
renderFilterLists(true);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
var autoUpdateCheckboxChanged = function() {
|
const autoUpdateCheckboxChanged = function() {
|
||||||
messaging.send(
|
messaging.send(
|
||||||
'dashboard',
|
'dashboard',
|
||||||
{
|
{
|
||||||
@ -525,16 +517,16 @@ var autoUpdateCheckboxChanged = function() {
|
|||||||
|
|
||||||
// Collapsing of unused lists.
|
// Collapsing of unused lists.
|
||||||
|
|
||||||
var mustHideUnusedLists = function(which) {
|
const mustHideUnusedLists = function(which) {
|
||||||
var hideAll = hideUnusedSet.has('*');
|
const hideAll = hideUnusedSet.has('*');
|
||||||
if ( which === '*' ) { return hideAll; }
|
if ( which === '*' ) { return hideAll; }
|
||||||
return hideUnusedSet.has(which) !== hideAll;
|
return hideUnusedSet.has(which) !== hideAll;
|
||||||
};
|
};
|
||||||
|
|
||||||
var toggleHideUnusedLists = function(which) {
|
const toggleHideUnusedLists = function(which) {
|
||||||
var groupSelector,
|
const doesHideAll = hideUnusedSet.has('*');
|
||||||
doesHideAll = hideUnusedSet.has('*'),
|
let groupSelector;
|
||||||
mustHide;
|
let mustHide;
|
||||||
if ( which === '*' ) {
|
if ( which === '*' ) {
|
||||||
mustHide = doesHideAll === false;
|
mustHide = doesHideAll === false;
|
||||||
groupSelector = '';
|
groupSelector = '';
|
||||||
@ -545,7 +537,7 @@ var toggleHideUnusedLists = function(which) {
|
|||||||
document.body.classList.toggle('hideUnused', mustHide);
|
document.body.classList.toggle('hideUnused', mustHide);
|
||||||
uDom('.groupEntry[data-groupkey]').toggleClass('hideUnused', mustHide);
|
uDom('.groupEntry[data-groupkey]').toggleClass('hideUnused', mustHide);
|
||||||
} else {
|
} else {
|
||||||
var doesHide = hideUnusedSet.has(which);
|
const doesHide = hideUnusedSet.has(which);
|
||||||
if ( doesHide ) {
|
if ( doesHide ) {
|
||||||
hideUnusedSet.delete(which);
|
hideUnusedSet.delete(which);
|
||||||
} else {
|
} else {
|
||||||
@ -564,7 +556,7 @@ var toggleHideUnusedLists = function(which) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
var revealHiddenUsedLists = function() {
|
const revealHiddenUsedLists = function() {
|
||||||
uDom('#lists .listEntry.unused > input[type="checkbox"]:checked')
|
uDom('#lists .listEntry.unused > input[type="checkbox"]:checked')
|
||||||
.ancestors('.listEntry[data-listkey]')
|
.ancestors('.listEntry[data-listkey]')
|
||||||
.removeClass('unused');
|
.removeClass('unused');
|
||||||
@ -582,10 +574,11 @@ uDom('#lists').on('click', '.groupEntry[data-groupkey] > .geDetails', function(e
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
(function() {
|
// Initialize from saved state.
|
||||||
var aa;
|
{
|
||||||
|
let aa;
|
||||||
try {
|
try {
|
||||||
var json = vAPI.localStorage.getItem('hideUnusedFilterLists');
|
const json = vAPI.localStorage.getItem('hideUnusedFilterLists');
|
||||||
if ( json !== null ) {
|
if ( json !== null ) {
|
||||||
aa = JSON.parse(json);
|
aa = JSON.parse(json);
|
||||||
}
|
}
|
||||||
@ -595,35 +588,33 @@ uDom('#lists').on('click', '.groupEntry[data-groupkey] > .geDetails', function(e
|
|||||||
aa = [ '*' ];
|
aa = [ '*' ];
|
||||||
}
|
}
|
||||||
hideUnusedSet = new Set(aa);
|
hideUnusedSet = new Set(aa);
|
||||||
})();
|
}
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
// Cloud-related.
|
// Cloud-related.
|
||||||
|
|
||||||
var toCloudData = function() {
|
const toCloudData = function() {
|
||||||
var bin = {
|
const bin = {
|
||||||
parseCosmeticFilters: uDom.nodeFromId('parseCosmeticFilters').checked,
|
parseCosmeticFilters: uDom.nodeFromId('parseCosmeticFilters').checked,
|
||||||
ignoreGenericCosmeticFilters: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked,
|
ignoreGenericCosmeticFilters: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked,
|
||||||
selectedLists: []
|
selectedLists: []
|
||||||
};
|
};
|
||||||
|
|
||||||
var liEntries = uDom('#lists .listEntry'), liEntry;
|
const liEntries = document.querySelectorAll('#lists .listEntry');
|
||||||
var i = liEntries.length;
|
for ( const liEntry of liEntries ) {
|
||||||
while ( i-- ) {
|
if ( liEntry.querySelector('input').checked ) {
|
||||||
liEntry = liEntries.at(i);
|
bin.selectedLists.push(liEntry.getAttribute('data-listkey'));
|
||||||
if ( liEntry.descendants('input').prop('checked') ) {
|
|
||||||
bin.selectedLists.push(liEntry.attr('data-listkey'));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bin;
|
return bin;
|
||||||
};
|
};
|
||||||
|
|
||||||
var fromCloudData = function(data, append) {
|
const fromCloudData = function(data, append) {
|
||||||
if ( typeof data !== 'object' || data === null ) { return; }
|
if ( typeof data !== 'object' || data === null ) { return; }
|
||||||
|
|
||||||
var elem, checked;
|
let elem, checked;
|
||||||
|
|
||||||
elem = uDom.nodeFromId('parseCosmeticFilters');
|
elem = uDom.nodeFromId('parseCosmeticFilters');
|
||||||
checked = data.parseCosmeticFilters === true || append && elem.checked;
|
checked = data.parseCosmeticFilters === true || append && elem.checked;
|
||||||
@ -633,21 +624,20 @@ var fromCloudData = function(data, append) {
|
|||||||
checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked;
|
checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked;
|
||||||
elem.checked = listDetails.ignoreGenericCosmeticFilters = checked;
|
elem.checked = listDetails.ignoreGenericCosmeticFilters = checked;
|
||||||
|
|
||||||
var selectedSet = new Set(data.selectedLists),
|
const selectedSet = new Set(data.selectedLists);
|
||||||
listEntries = uDom('#lists .listEntry'),
|
const listEntries = uDom('#lists .listEntry');
|
||||||
listEntry, listKey;
|
for ( let i = 0, n = listEntries.length; i < n; i++ ) {
|
||||||
for ( var i = 0, n = listEntries.length; i < n; i++ ) {
|
const listEntry = listEntries.at(i);
|
||||||
listEntry = listEntries.at(i);
|
const listKey = listEntry.attr('data-listkey');
|
||||||
listKey = listEntry.attr('data-listkey');
|
const hasListKey = selectedSet.has(listKey);
|
||||||
var hasListKey = selectedSet.has(listKey);
|
|
||||||
selectedSet.delete(listKey);
|
selectedSet.delete(listKey);
|
||||||
var input = listEntry.descendants('input').first();
|
const input = listEntry.descendants('input').first();
|
||||||
if ( append && input.prop('checked') ) { continue; }
|
if ( append && input.prop('checked') ) { continue; }
|
||||||
input.prop('checked', hasListKey);
|
input.prop('checked', hasListKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there are URL-like list keys left in the selected set, import them.
|
// If there are URL-like list keys left in the selected set, import them.
|
||||||
for ( listKey of selectedSet ) {
|
for ( const listKey of selectedSet ) {
|
||||||
if ( reValidExternalList.test(listKey) === false ) {
|
if ( reValidExternalList.test(listKey) === false ) {
|
||||||
selectedSet.delete(listKey);
|
selectedSet.delete(listKey);
|
||||||
}
|
}
|
||||||
@ -672,6 +662,12 @@ self.cloud.onPull = fromCloudData;
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
self.hasUnsavedData = function() {
|
||||||
|
return hashFromCurrentFromSettings() !== filteringSettingsHash;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
|
uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
|
||||||
uDom('#parseCosmeticFilters').on('change', onFilteringSettingsChanged);
|
uDom('#parseCosmeticFilters').on('change', onFilteringSettingsChanged);
|
||||||
uDom('#ignoreGenericCosmeticFilters').on('change', onFilteringSettingsChanged);
|
uDom('#ignoreGenericCosmeticFilters').on('change', onFilteringSettingsChanged);
|
||||||
|
@ -25,48 +25,93 @@
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
const resizeFrame = function() {
|
const resizeFrame = function() {
|
||||||
let navRect = document.getElementById('dashboard-nav').getBoundingClientRect();
|
const navRect = document.getElementById('dashboard-nav')
|
||||||
let viewRect = document.documentElement.getBoundingClientRect();
|
.getBoundingClientRect();
|
||||||
|
const viewRect = document.documentElement.getBoundingClientRect();
|
||||||
document.getElementById('iframe').style.setProperty(
|
document.getElementById('iframe').style.setProperty(
|
||||||
'height',
|
'height',
|
||||||
(viewRect.height - navRect.height) + 'px'
|
(viewRect.height - navRect.height) + 'px'
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const loadDashboardPanel = function() {
|
const discardUnsavedData = function(synchronous = false) {
|
||||||
let pane = window.location.hash.slice(1);
|
const paneFrame = document.getElementById('iframe');
|
||||||
|
const paneWindow = paneFrame.contentWindow;
|
||||||
|
if (
|
||||||
|
typeof paneWindow.hasUnsavedData !== 'function' ||
|
||||||
|
paneWindow.hasUnsavedData() === false
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( synchronous ) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const modal = uDom.nodeFromId('unsavedWarning');
|
||||||
|
modal.classList.add('on');
|
||||||
|
modal.focus();
|
||||||
|
|
||||||
|
const onDone = status => {
|
||||||
|
modal.classList.remove('on');
|
||||||
|
document.removeEventListener('click', onClick, true);
|
||||||
|
resolve(status);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClick = ev => {
|
||||||
|
const target = ev.target;
|
||||||
|
if ( target.matches('[data-i18n="dashboardUnsavedWarningStay"]') ) {
|
||||||
|
return onDone(false);
|
||||||
|
}
|
||||||
|
if ( target.matches('[data-i18n="dashboardUnsavedWarningIgnore"]') ) {
|
||||||
|
return onDone(true);
|
||||||
|
}
|
||||||
|
if ( modal.querySelector('[data-i18n="dashboardUnsavedWarning"]').contains(target) ) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
onDone(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('click', onClick, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const loadDashboardPanel = function(pane = '') {
|
||||||
if ( pane === '' ) {
|
if ( pane === '' ) {
|
||||||
pane = vAPI.localStorage.getItem('dashboardLastVisitedPane');
|
pane = vAPI.localStorage.getItem('dashboardLastVisitedPane');
|
||||||
if ( pane === null ) {
|
if ( pane === null ) {
|
||||||
pane = 'settings.html';
|
pane = 'settings.html';
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
vAPI.localStorage.setItem('dashboardLastVisitedPane', pane);
|
|
||||||
}
|
}
|
||||||
let tabButton = uDom('[href="#' + pane + '"]');
|
const tabButton = uDom(`[href="#${pane}"]`);
|
||||||
if ( !tabButton || tabButton.hasClass('selected') ) { return; }
|
if ( !tabButton || tabButton.hasClass('selected') ) { return; }
|
||||||
uDom('.tabButton.selected').toggleClass('selected', false);
|
const loadPane = ( ) => {
|
||||||
uDom('iframe').attr('src', pane);
|
self.location.replace(`#${pane}`);
|
||||||
tabButton.toggleClass('selected', true);
|
uDom('.tabButton.selected').toggleClass('selected', false);
|
||||||
|
tabButton.toggleClass('selected', true);
|
||||||
|
uDom.nodeFromId('iframe').setAttribute('src', pane);
|
||||||
|
vAPI.localStorage.setItem('dashboardLastVisitedPane', pane);
|
||||||
|
};
|
||||||
|
const r = discardUnsavedData();
|
||||||
|
if ( r === false ) { return; }
|
||||||
|
if ( r === true ) {
|
||||||
|
return loadPane();
|
||||||
|
}
|
||||||
|
r.then(status => {
|
||||||
|
if ( status === false ) { return; }
|
||||||
|
loadPane();
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const onTabClickHandler = function(e) {
|
const onTabClickHandler = function(ev) {
|
||||||
let url = window.location.href,
|
loadDashboardPanel(ev.target.hash.slice(1));
|
||||||
pos = url.indexOf('#');
|
ev.preventDefault();
|
||||||
if ( pos !== -1 ) {
|
|
||||||
url = url.slice(0, pos);
|
|
||||||
}
|
|
||||||
url += this.hash;
|
|
||||||
if ( url !== window.location.href ) {
|
|
||||||
window.location.replace(url);
|
|
||||||
loadDashboardPanel();
|
|
||||||
}
|
|
||||||
e.preventDefault();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/106
|
// https://github.com/uBlockOrigin/uBlock-issues/issues/106
|
||||||
@ -80,6 +125,13 @@ loadDashboardPanel();
|
|||||||
window.addEventListener('resize', resizeFrame);
|
window.addEventListener('resize', resizeFrame);
|
||||||
uDom('.tabButton').on('click', onTabClickHandler);
|
uDom('.tabButton').on('click', onTabClickHandler);
|
||||||
|
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/API/Window/beforeunload_event
|
||||||
|
window.addEventListener('beforeunload', ( ) => {
|
||||||
|
if ( discardUnsavedData(true) ) { return; }
|
||||||
|
event.preventDefault();
|
||||||
|
event.returnValue = '';
|
||||||
|
});
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
})();
|
})();
|
||||||
|
@ -25,7 +25,7 @@
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
(function() {
|
(( ) => {
|
||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
@ -329,7 +329,7 @@ const onFilterChanged = (function() {
|
|||||||
overlay = null,
|
overlay = null,
|
||||||
last = '';
|
last = '';
|
||||||
|
|
||||||
let process = function() {
|
const process = function() {
|
||||||
timer = undefined;
|
timer = undefined;
|
||||||
if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; }
|
if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; }
|
||||||
let filter = uDom.nodeFromSelector('#ruleFilter input').value;
|
let filter = uDom.nodeFromSelector('#ruleFilter input').value;
|
||||||
@ -359,7 +359,7 @@ const onFilterChanged = (function() {
|
|||||||
const onTextChanged = (function() {
|
const onTextChanged = (function() {
|
||||||
let timer;
|
let timer;
|
||||||
|
|
||||||
let process = function(now) {
|
const process = function(now) {
|
||||||
timer = undefined;
|
timer = undefined;
|
||||||
const diff = document.getElementById('diff');
|
const diff = document.getElementById('diff');
|
||||||
let isClean = mergeView.editor().isClean(cleanEditToken);
|
let isClean = mergeView.editor().isClean(cleanEditToken);
|
||||||
@ -474,6 +474,12 @@ self.cloud.onPull = function(data, append) {
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
self.hasUnsavedData = function() {
|
||||||
|
return mergeView.editor().isClean(cleanEditToken) === false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
messaging.send('dashboard', { what: 'getRules' }, renderRules);
|
messaging.send('dashboard', { what: 'getRules' }, renderRules);
|
||||||
|
|
||||||
// Handle user interaction
|
// Handle user interaction
|
||||||
|
@ -238,6 +238,12 @@ self.cloud.onPull = setCloudData;
|
|||||||
|
|
||||||
/******************************************************************************/
|
/******************************************************************************/
|
||||||
|
|
||||||
|
self.hasUnsavedData = function() {
|
||||||
|
return cmEditor.getValue().trim() !== cachedWhitelist;
|
||||||
|
};
|
||||||
|
|
||||||
|
/******************************************************************************/
|
||||||
|
|
||||||
uDom('#importWhitelistFromFile').on('click', startImportFilePicker);
|
uDom('#importWhitelistFromFile').on('click', startImportFilePicker);
|
||||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||||
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
|
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
|
||||||
|
Loading…
Reference in New Issue
Block a user