mirror of https://github.com/gorhill/uBlock.git
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",
|
||||
"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":{
|
||||
"message":"Settings",
|
||||
"description":"appears as tab name in dashboard"
|
||||
|
|
|
@ -66,12 +66,32 @@ html, body {
|
|||
border-bottom: 1px solid white;
|
||||
}
|
||||
iframe {
|
||||
margin: 0;
|
||||
border: 0;
|
||||
padding: 0;
|
||||
background-color: transparent;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
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"] {
|
||||
display: none;
|
||||
|
|
|
@ -22,6 +22,14 @@
|
|||
--><a class="tabButton" href="#about.html" data-i18n="aboutPageName"></a>
|
||||
</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>
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
const userFiltersChanged = function(changed) {
|
||||
if ( typeof changed !== 'boolean' ) {
|
||||
changed = cmEditor.getValue().trim() !== cachedUserFilters;
|
||||
changed = self.hasUnsavedData();
|
||||
}
|
||||
uDom.nodeFromId('userFiltersApply').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
|
||||
uDom('#importUserFiltersFromFile').on('click', startImportFilePicker);
|
||||
uDom('#importFilePicker').on('change', handleImportFilePicker);
|
||||
|
|
|
@ -25,19 +25,20 @@
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
(function() {
|
||||
(( ) => {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var listDetails = {},
|
||||
filteringSettingsHash = '',
|
||||
lastUpdateTemplateString = vAPI.i18n('3pLastUpdate'),
|
||||
reValidExternalList = /[a-z-]+:\/\/\S*\/\S+/,
|
||||
hideUnusedSet = new Set();
|
||||
const lastUpdateTemplateString = vAPI.i18n('3pLastUpdate');
|
||||
const reValidExternalList = /[a-z-]+:\/\/\S*\/\S+/;
|
||||
|
||||
let listDetails = {};
|
||||
let filteringSettingsHash = '';
|
||||
let hideUnusedSet = new Set();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var onMessage = function(msg) {
|
||||
const onMessage = function(msg) {
|
||||
switch ( msg.what ) {
|
||||
case 'assetUpdated':
|
||||
updateAssetStatus(msg);
|
||||
|
@ -54,39 +55,39 @@ var onMessage = function(msg) {
|
|||
}
|
||||
};
|
||||
|
||||
var messaging = vAPI.messaging;
|
||||
const messaging = vAPI.messaging;
|
||||
messaging.addChannelListener('dashboard', onMessage);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var renderNumber = function(value) {
|
||||
const renderNumber = function(value) {
|
||||
return value.toLocaleString();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var renderFilterLists = function(soft) {
|
||||
var listGroupTemplate = uDom('#templates .groupEntry'),
|
||||
listEntryTemplate = uDom('#templates .listEntry'),
|
||||
listStatsTemplate = vAPI.i18n('3pListsOfBlockedHostsPerListStats'),
|
||||
renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString,
|
||||
groupNames = new Map([ [ 'user', '' ] ]);
|
||||
const renderFilterLists = function(soft) {
|
||||
const listGroupTemplate = uDom('#templates .groupEntry');
|
||||
const listEntryTemplate = uDom('#templates .listEntry');
|
||||
const listStatsTemplate = vAPI.i18n('3pListsOfBlockedHostsPerListStats');
|
||||
const renderElapsedTimeToString = vAPI.i18n.renderElapsedTimeToString;
|
||||
const groupNames = new Map([ [ 'user', '' ] ]);
|
||||
|
||||
// Assemble a pretty list name if possible
|
||||
var listNameFromListKey = function(listKey) {
|
||||
var list = listDetails.current[listKey] || listDetails.available[listKey];
|
||||
var listTitle = list ? list.title : '';
|
||||
const listNameFromListKey = function(listKey) {
|
||||
const list = listDetails.current[listKey] || listDetails.available[listKey];
|
||||
const listTitle = list ? list.title : '';
|
||||
if ( listTitle === '' ) { return listKey; }
|
||||
return listTitle;
|
||||
};
|
||||
|
||||
var liFromListEntry = function(listKey, li, hideUnused) {
|
||||
var entry = listDetails.available[listKey],
|
||||
elem;
|
||||
const liFromListEntry = function(listKey, li, hideUnused) {
|
||||
const entry = listDetails.available[listKey];
|
||||
if ( !li ) {
|
||||
li = listEntryTemplate.clone().nodeAt(0);
|
||||
}
|
||||
var on = entry.off !== true;
|
||||
const on = entry.off !== true;
|
||||
let elem;
|
||||
if ( li.getAttribute('data-listkey') !== listKey ) {
|
||||
li.setAttribute('data-listkey', listKey);
|
||||
elem = li.querySelector('input[type="checkbox"]');
|
||||
|
@ -123,7 +124,7 @@ var renderFilterLists = function(soft) {
|
|||
li.querySelector('input[type="checkbox"]').checked = on;
|
||||
}
|
||||
elem = li.querySelector('span.counts');
|
||||
var text = '';
|
||||
let text = '';
|
||||
if ( !isNaN(+entry.entryUsedCount) && !isNaN(+entry.entryCount) ) {
|
||||
text = listStatsTemplate
|
||||
.replace('{{used}}', renderNumber(on ? entry.entryUsedCount : 0))
|
||||
|
@ -131,8 +132,8 @@ var renderFilterLists = function(soft) {
|
|||
}
|
||||
elem.textContent = text;
|
||||
// https://github.com/chrisaljoudi/uBlock/issues/104
|
||||
var asset = listDetails.cache[listKey] || {};
|
||||
var remoteURL = asset.remoteURL;
|
||||
const asset = listDetails.cache[listKey] || {};
|
||||
const remoteURL = asset.remoteURL;
|
||||
li.classList.toggle(
|
||||
'unsecure',
|
||||
typeof remoteURL === 'string' && remoteURL.lastIndexOf('http:', 0) === 0
|
||||
|
@ -155,24 +156,23 @@ var renderFilterLists = function(soft) {
|
|||
return li;
|
||||
};
|
||||
|
||||
var listEntryCountFromGroup = function(listKeys) {
|
||||
const listEntryCountFromGroup = function(listKeys) {
|
||||
if ( Array.isArray(listKeys) === false ) { return ''; }
|
||||
var count = 0,
|
||||
let count = 0,
|
||||
total = 0;
|
||||
var i = listKeys.length;
|
||||
while ( i-- ) {
|
||||
if ( listDetails.available[listKeys[i]].off !== true ) {
|
||||
for ( const listKey of listKeys ) {
|
||||
if ( listDetails.available[listKey].off !== true ) {
|
||||
count += 1;
|
||||
}
|
||||
total += 1;
|
||||
}
|
||||
return total !== 0 ?
|
||||
'(' + count.toLocaleString() + '/' + total.toLocaleString() + ')' :
|
||||
`(${count.toLocaleString()}/${total.toLocaleString()})` :
|
||||
'';
|
||||
};
|
||||
|
||||
var liFromListGroup = function(groupKey, listKeys) {
|
||||
let liGroup = document.querySelector('#lists > .groupEntry[data-groupkey="' + groupKey + '"]');
|
||||
const liFromListGroup = function(groupKey, listKeys) {
|
||||
let liGroup = document.querySelector(`#lists > .groupEntry[data-groupkey="${groupKey}"]`);
|
||||
if ( liGroup === null ) {
|
||||
liGroup = listGroupTemplate.clone().nodeAt(0);
|
||||
let groupName = groupNames.get(groupKey);
|
||||
|
@ -207,7 +207,7 @@ var renderFilterLists = function(soft) {
|
|||
return liGroup;
|
||||
};
|
||||
|
||||
var groupsFromLists = function(lists) {
|
||||
const groupsFromLists = function(lists) {
|
||||
let groups = new Map();
|
||||
let listKeys = Object.keys(lists);
|
||||
for ( let listKey of listKeys ) {
|
||||
|
@ -225,7 +225,7 @@ var renderFilterLists = function(soft) {
|
|||
return groups;
|
||||
};
|
||||
|
||||
var onListsReceived = function(details) {
|
||||
const onListsReceived = function(details) {
|
||||
// Before all, set context vars
|
||||
listDetails = details;
|
||||
|
||||
|
@ -238,22 +238,22 @@ var renderFilterLists = function(soft) {
|
|||
uDom('#lists .listEntries .listEntry[data-listkey]').addClass('discard');
|
||||
|
||||
// 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
|
||||
var ulLists = document.querySelector('#lists'),
|
||||
groups = groupsFromLists(details.available),
|
||||
groupKeys = [
|
||||
'user',
|
||||
'default',
|
||||
'ads',
|
||||
'privacy',
|
||||
'malware',
|
||||
'annoyances',
|
||||
'multipurpose',
|
||||
'regions',
|
||||
'custom'
|
||||
];
|
||||
const ulLists = document.querySelector('#lists');
|
||||
const groups = groupsFromLists(details.available);
|
||||
const groupKeys = [
|
||||
'user',
|
||||
'default',
|
||||
'ads',
|
||||
'privacy',
|
||||
'malware',
|
||||
'annoyances',
|
||||
'multipurpose',
|
||||
'regions',
|
||||
'custom'
|
||||
];
|
||||
document.body.classList.toggle('hideUnused', mustHideUnusedLists('*'));
|
||||
for ( let i = 0; i < groupKeys.length; i++ ) {
|
||||
let groupKey = groupKeys[i];
|
||||
|
@ -269,8 +269,7 @@ var renderFilterLists = function(soft) {
|
|||
groups.delete(groupKey);
|
||||
}
|
||||
// For all groups not covered above (if any left)
|
||||
groupKeys = Object.keys(groups);
|
||||
for ( let groupKey of groupKeys.keys() ) {
|
||||
for ( const groupKey of Object.keys(groups) ) {
|
||||
ulLists.appendChild(liFromListGroup(groupKey, groupKey));
|
||||
}
|
||||
|
||||
|
@ -308,7 +307,7 @@ var renderFilterLists = function(soft) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var renderWidgets = function() {
|
||||
const renderWidgets = function() {
|
||||
uDom('#buttonApply').toggleClass(
|
||||
'disabled',
|
||||
filteringSettingsHash === hashFromCurrentFromSettings()
|
||||
|
@ -325,8 +324,8 @@ var renderWidgets = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var updateAssetStatus = function(details) {
|
||||
let li = document.querySelector(
|
||||
const updateAssetStatus = function(details) {
|
||||
const li = document.querySelector(
|
||||
'#lists .listEntry[data-listkey="' + details.key + '"]'
|
||||
);
|
||||
if ( li === null ) { return; }
|
||||
|
@ -352,17 +351,14 @@ var updateAssetStatus = function(details) {
|
|||
|
||||
**/
|
||||
|
||||
var hashFromCurrentFromSettings = function() {
|
||||
var hash = [
|
||||
const hashFromCurrentFromSettings = function() {
|
||||
const hash = [
|
||||
uDom.nodeFromId('parseCosmeticFilters').checked,
|
||||
uDom.nodeFromId('ignoreGenericCosmeticFilters').checked
|
||||
];
|
||||
var listHash = [],
|
||||
listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'),
|
||||
liEntry,
|
||||
i = listEntries.length;
|
||||
while ( i-- ) {
|
||||
liEntry = listEntries[i];
|
||||
const listHash = [];
|
||||
const listEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)');
|
||||
for ( const liEntry of listEntries ) {
|
||||
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
||||
listHash.push(liEntry.getAttribute('data-listkey'));
|
||||
}
|
||||
|
@ -378,15 +374,15 @@ var hashFromCurrentFromSettings = function() {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var onFilteringSettingsChanged = function() {
|
||||
const onFilteringSettingsChanged = function() {
|
||||
renderWidgets();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var onRemoveExternalList = function(ev) {
|
||||
var liEntry = uDom(this).ancestors('[data-listkey]'),
|
||||
listKey = liEntry.attr('data-listkey');
|
||||
const onRemoveExternalList = function(ev) {
|
||||
const liEntry = uDom(this).ancestors('[data-listkey]');
|
||||
const listKey = liEntry.attr('data-listkey');
|
||||
if ( listKey ) {
|
||||
liEntry.toggleClass('toRemove');
|
||||
renderWidgets();
|
||||
|
@ -396,10 +392,10 @@ var onRemoveExternalList = function(ev) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var onPurgeClicked = function() {
|
||||
var button = uDom(this),
|
||||
liEntry = button.ancestors('[data-listkey]'),
|
||||
listKey = liEntry.attr('data-listkey');
|
||||
const onPurgeClicked = function(ev) {
|
||||
const button = uDom(ev.target);
|
||||
const liEntry = button.ancestors('[data-listkey]');
|
||||
const listKey = liEntry.attr('data-listkey');
|
||||
if ( !listKey ) { return; }
|
||||
|
||||
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
|
||||
messaging.send('dashboard', {
|
||||
what: 'userSettings',
|
||||
|
@ -433,28 +429,24 @@ var selectFilterLists = function(callback) {
|
|||
});
|
||||
|
||||
// Filter lists to select
|
||||
var toSelect = [],
|
||||
liEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)'),
|
||||
i = liEntries.length,
|
||||
liEntry;
|
||||
while ( i-- ) {
|
||||
liEntry = liEntries[i];
|
||||
const toSelect = [];
|
||||
let liEntries = document.querySelectorAll('#lists .listEntry[data-listkey]:not(.toRemove)');
|
||||
for ( const liEntry of liEntries ) {
|
||||
if ( liEntry.querySelector('input[type="checkbox"]:checked') !== null ) {
|
||||
toSelect.push(liEntry.getAttribute('data-listkey'));
|
||||
}
|
||||
}
|
||||
|
||||
// External filter lists to remove
|
||||
var toRemove = [];
|
||||
const toRemove = [];
|
||||
liEntries = document.querySelectorAll('#lists .listEntry.toRemove[data-listkey]');
|
||||
i = liEntries.length;
|
||||
while ( i-- ) {
|
||||
toRemove.push(liEntries[i].getAttribute('data-listkey'));
|
||||
for ( const liEntry of liEntries ) {
|
||||
toRemove.push(liEntry.getAttribute('data-listkey'));
|
||||
}
|
||||
|
||||
// External filter lists to import
|
||||
var externalListsElem = document.getElementById('externalLists'),
|
||||
toImport = externalListsElem.value.trim();
|
||||
const externalListsElem = document.getElementById('externalLists');
|
||||
const toImport = externalListsElem.value.trim();
|
||||
externalListsElem.value = '';
|
||||
uDom.nodeFromId('importLists').checked = false;
|
||||
|
||||
|
@ -473,30 +465,28 @@ var selectFilterLists = function(callback) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var buttonApplyHandler = function() {
|
||||
const buttonApplyHandler = function() {
|
||||
uDom('#buttonApply').removeClass('enabled');
|
||||
var onSelectionDone = function() {
|
||||
selectFilterLists(( ) => {
|
||||
messaging.send('dashboard', { what: 'reloadAllFilters' });
|
||||
};
|
||||
selectFilterLists(onSelectionDone);
|
||||
});
|
||||
renderWidgets();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var buttonUpdateHandler = function() {
|
||||
var onSelectionDone = function() {
|
||||
const buttonUpdateHandler = function() {
|
||||
selectFilterLists(( ) => {
|
||||
document.body.classList.add('updating');
|
||||
messaging.send('dashboard', { what: 'forceUpdateAssets' });
|
||||
renderWidgets();
|
||||
};
|
||||
selectFilterLists(onSelectionDone);
|
||||
});
|
||||
renderWidgets();
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var buttonPurgeAllHandler = function(ev) {
|
||||
const buttonPurgeAllHandler = function(ev) {
|
||||
uDom('#buttonPurgeAll').removeClass('enabled');
|
||||
messaging.send(
|
||||
'dashboard',
|
||||
|
@ -504,13 +494,15 @@ var buttonPurgeAllHandler = function(ev) {
|
|||
what: 'purgeAllCaches',
|
||||
hard: ev.ctrlKey && ev.shiftKey
|
||||
},
|
||||
function() { renderFilterLists(true); }
|
||||
( ) => {
|
||||
renderFilterLists(true);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var autoUpdateCheckboxChanged = function() {
|
||||
const autoUpdateCheckboxChanged = function() {
|
||||
messaging.send(
|
||||
'dashboard',
|
||||
{
|
||||
|
@ -525,16 +517,16 @@ var autoUpdateCheckboxChanged = function() {
|
|||
|
||||
// Collapsing of unused lists.
|
||||
|
||||
var mustHideUnusedLists = function(which) {
|
||||
var hideAll = hideUnusedSet.has('*');
|
||||
const mustHideUnusedLists = function(which) {
|
||||
const hideAll = hideUnusedSet.has('*');
|
||||
if ( which === '*' ) { return hideAll; }
|
||||
return hideUnusedSet.has(which) !== hideAll;
|
||||
};
|
||||
|
||||
var toggleHideUnusedLists = function(which) {
|
||||
var groupSelector,
|
||||
doesHideAll = hideUnusedSet.has('*'),
|
||||
mustHide;
|
||||
const toggleHideUnusedLists = function(which) {
|
||||
const doesHideAll = hideUnusedSet.has('*');
|
||||
let groupSelector;
|
||||
let mustHide;
|
||||
if ( which === '*' ) {
|
||||
mustHide = doesHideAll === false;
|
||||
groupSelector = '';
|
||||
|
@ -545,7 +537,7 @@ var toggleHideUnusedLists = function(which) {
|
|||
document.body.classList.toggle('hideUnused', mustHide);
|
||||
uDom('.groupEntry[data-groupkey]').toggleClass('hideUnused', mustHide);
|
||||
} else {
|
||||
var doesHide = hideUnusedSet.has(which);
|
||||
const doesHide = hideUnusedSet.has(which);
|
||||
if ( doesHide ) {
|
||||
hideUnusedSet.delete(which);
|
||||
} else {
|
||||
|
@ -564,7 +556,7 @@ var toggleHideUnusedLists = function(which) {
|
|||
);
|
||||
};
|
||||
|
||||
var revealHiddenUsedLists = function() {
|
||||
const revealHiddenUsedLists = function() {
|
||||
uDom('#lists .listEntry.unused > input[type="checkbox"]:checked')
|
||||
.ancestors('.listEntry[data-listkey]')
|
||||
.removeClass('unused');
|
||||
|
@ -582,10 +574,11 @@ uDom('#lists').on('click', '.groupEntry[data-groupkey] > .geDetails', function(e
|
|||
);
|
||||
});
|
||||
|
||||
(function() {
|
||||
var aa;
|
||||
// Initialize from saved state.
|
||||
{
|
||||
let aa;
|
||||
try {
|
||||
var json = vAPI.localStorage.getItem('hideUnusedFilterLists');
|
||||
const json = vAPI.localStorage.getItem('hideUnusedFilterLists');
|
||||
if ( json !== null ) {
|
||||
aa = JSON.parse(json);
|
||||
}
|
||||
|
@ -595,35 +588,33 @@ uDom('#lists').on('click', '.groupEntry[data-groupkey] > .geDetails', function(e
|
|||
aa = [ '*' ];
|
||||
}
|
||||
hideUnusedSet = new Set(aa);
|
||||
})();
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// Cloud-related.
|
||||
|
||||
var toCloudData = function() {
|
||||
var bin = {
|
||||
const toCloudData = function() {
|
||||
const bin = {
|
||||
parseCosmeticFilters: uDom.nodeFromId('parseCosmeticFilters').checked,
|
||||
ignoreGenericCosmeticFilters: uDom.nodeFromId('ignoreGenericCosmeticFilters').checked,
|
||||
selectedLists: []
|
||||
};
|
||||
|
||||
var liEntries = uDom('#lists .listEntry'), liEntry;
|
||||
var i = liEntries.length;
|
||||
while ( i-- ) {
|
||||
liEntry = liEntries.at(i);
|
||||
if ( liEntry.descendants('input').prop('checked') ) {
|
||||
bin.selectedLists.push(liEntry.attr('data-listkey'));
|
||||
const liEntries = document.querySelectorAll('#lists .listEntry');
|
||||
for ( const liEntry of liEntries ) {
|
||||
if ( liEntry.querySelector('input').checked ) {
|
||||
bin.selectedLists.push(liEntry.getAttribute('data-listkey'));
|
||||
}
|
||||
}
|
||||
|
||||
return bin;
|
||||
};
|
||||
|
||||
var fromCloudData = function(data, append) {
|
||||
const fromCloudData = function(data, append) {
|
||||
if ( typeof data !== 'object' || data === null ) { return; }
|
||||
|
||||
var elem, checked;
|
||||
let elem, checked;
|
||||
|
||||
elem = uDom.nodeFromId('parseCosmeticFilters');
|
||||
checked = data.parseCosmeticFilters === true || append && elem.checked;
|
||||
|
@ -633,21 +624,20 @@ var fromCloudData = function(data, append) {
|
|||
checked = data.ignoreGenericCosmeticFilters === true || append && elem.checked;
|
||||
elem.checked = listDetails.ignoreGenericCosmeticFilters = checked;
|
||||
|
||||
var selectedSet = new Set(data.selectedLists),
|
||||
listEntries = uDom('#lists .listEntry'),
|
||||
listEntry, listKey;
|
||||
for ( var i = 0, n = listEntries.length; i < n; i++ ) {
|
||||
listEntry = listEntries.at(i);
|
||||
listKey = listEntry.attr('data-listkey');
|
||||
var hasListKey = selectedSet.has(listKey);
|
||||
const selectedSet = new Set(data.selectedLists);
|
||||
const listEntries = uDom('#lists .listEntry');
|
||||
for ( let i = 0, n = listEntries.length; i < n; i++ ) {
|
||||
const listEntry = listEntries.at(i);
|
||||
const listKey = listEntry.attr('data-listkey');
|
||||
const hasListKey = selectedSet.has(listKey);
|
||||
selectedSet.delete(listKey);
|
||||
var input = listEntry.descendants('input').first();
|
||||
const input = listEntry.descendants('input').first();
|
||||
if ( append && input.prop('checked') ) { continue; }
|
||||
input.prop('checked', hasListKey);
|
||||
}
|
||||
|
||||
// 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 ) {
|
||||
selectedSet.delete(listKey);
|
||||
}
|
||||
|
@ -672,6 +662,12 @@ self.cloud.onPull = fromCloudData;
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
self.hasUnsavedData = function() {
|
||||
return hashFromCurrentFromSettings() !== filteringSettingsHash;
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
uDom('#autoUpdate').on('change', autoUpdateCheckboxChanged);
|
||||
uDom('#parseCosmeticFilters').on('change', onFilteringSettingsChanged);
|
||||
uDom('#ignoreGenericCosmeticFilters').on('change', onFilteringSettingsChanged);
|
||||
|
|
|
@ -25,48 +25,93 @@
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
(function() {
|
||||
(( ) => {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
const resizeFrame = function() {
|
||||
let navRect = document.getElementById('dashboard-nav').getBoundingClientRect();
|
||||
let viewRect = document.documentElement.getBoundingClientRect();
|
||||
const navRect = document.getElementById('dashboard-nav')
|
||||
.getBoundingClientRect();
|
||||
const viewRect = document.documentElement.getBoundingClientRect();
|
||||
document.getElementById('iframe').style.setProperty(
|
||||
'height',
|
||||
(viewRect.height - navRect.height) + 'px'
|
||||
);
|
||||
};
|
||||
|
||||
const loadDashboardPanel = function() {
|
||||
let pane = window.location.hash.slice(1);
|
||||
const discardUnsavedData = function(synchronous = false) {
|
||||
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 === '' ) {
|
||||
pane = vAPI.localStorage.getItem('dashboardLastVisitedPane');
|
||||
if ( pane === null ) {
|
||||
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; }
|
||||
uDom('.tabButton.selected').toggleClass('selected', false);
|
||||
uDom('iframe').attr('src', pane);
|
||||
tabButton.toggleClass('selected', true);
|
||||
const loadPane = ( ) => {
|
||||
self.location.replace(`#${pane}`);
|
||||
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) {
|
||||
let url = window.location.href,
|
||||
pos = url.indexOf('#');
|
||||
if ( pos !== -1 ) {
|
||||
url = url.slice(0, pos);
|
||||
}
|
||||
url += this.hash;
|
||||
if ( url !== window.location.href ) {
|
||||
window.location.replace(url);
|
||||
loadDashboardPanel();
|
||||
}
|
||||
e.preventDefault();
|
||||
const onTabClickHandler = function(ev) {
|
||||
loadDashboardPanel(ev.target.hash.slice(1));
|
||||
ev.preventDefault();
|
||||
};
|
||||
|
||||
// https://github.com/uBlockOrigin/uBlock-issues/issues/106
|
||||
|
@ -80,6 +125,13 @@ loadDashboardPanel();
|
|||
window.addEventListener('resize', resizeFrame);
|
||||
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,
|
||||
last = '';
|
||||
|
||||
let process = function() {
|
||||
const process = function() {
|
||||
timer = undefined;
|
||||
if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; }
|
||||
let filter = uDom.nodeFromSelector('#ruleFilter input').value;
|
||||
|
@ -359,7 +359,7 @@ const onFilterChanged = (function() {
|
|||
const onTextChanged = (function() {
|
||||
let timer;
|
||||
|
||||
let process = function(now) {
|
||||
const process = function(now) {
|
||||
timer = undefined;
|
||||
const diff = document.getElementById('diff');
|
||||
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);
|
||||
|
||||
// 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('#importFilePicker').on('change', handleImportFilePicker);
|
||||
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
|
||||
|
|
Loading…
Reference in New Issue