uBlock/src/js/1p-filters.js
Raymond Hill f677443878
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.
2019-05-19 15:35:00 -04:00

236 lines
7.7 KiB
JavaScript

/*******************************************************************************
uBlock Origin - a browser extension to block requests.
Copyright (C) 2014-present Raymond Hill
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see {http://www.gnu.org/licenses/}.
Home: https://github.com/gorhill/uBlock
*/
/* global CodeMirror, uDom, uBlockDashboard */
'use strict';
/******************************************************************************/
(( ) => {
/******************************************************************************/
const messaging = vAPI.messaging;
const cmEditor = new CodeMirror(
document.getElementById('userFilters'),
{
autofocus: true,
lineNumbers: true,
lineWrapping: true,
styleActiveLine: true
}
);
uBlockDashboard.patchCodeMirrorEditor(cmEditor);
let cachedUserFilters = '';
/******************************************************************************/
// https://github.com/gorhill/uBlock/issues/3706
// Save/restore cursor position
//
// CoreMirror reference: https://codemirror.net/doc/manual.html#api_selection
window.addEventListener('beforeunload', ( ) => {
vAPI.localStorage.setItem(
'myFiltersCursorPosition',
JSON.stringify(cmEditor.getCursor().line)
);
});
/******************************************************************************/
// This is to give a visual hint that the content of user blacklist has changed.
const userFiltersChanged = function(changed) {
if ( typeof changed !== 'boolean' ) {
changed = self.hasUnsavedData();
}
uDom.nodeFromId('userFiltersApply').disabled = !changed;
uDom.nodeFromId('userFiltersRevert').disabled = !changed;
};
/******************************************************************************/
const renderUserFilters = function(first) {
const onRead = function(details) {
if ( details.error ) { return; }
let content = details.content.trim();
cachedUserFilters = content;
if ( content.length !== 0 ) {
content += '\n';
}
cmEditor.setValue(content);
if ( first ) {
cmEditor.clearHistory();
try {
const line = JSON.parse(
vAPI.localStorage.getItem('myFiltersCursorPosition')
);
if ( typeof line === 'number' ) {
cmEditor.setCursor(line, 0);
}
} catch(ex) {
}
}
userFiltersChanged(false);
};
messaging.send('dashboard', { what: 'readUserFilters' }, onRead);
};
/******************************************************************************/
const handleImportFilePicker = function() {
// https://github.com/chrisaljoudi/uBlock/issues/1004
// Support extraction of filters from ABP backup file
const abpImporter = function(s) {
const reAbpSubscriptionExtractor = /\n\[Subscription\]\n+url=~[^\n]+([\x08-\x7E]*?)(?:\[Subscription\]|$)/ig;
const reAbpFilterExtractor = /\[Subscription filters\]([\x08-\x7E]*?)(?:\[Subscription\]|$)/i;
let matches = reAbpSubscriptionExtractor.exec(s);
// Not an ABP backup file
if ( matches === null ) { return s; }
const out = [];
do {
if ( matches.length === 2 ) {
let filterMatch = reAbpFilterExtractor.exec(matches[1].trim());
if ( filterMatch !== null && filterMatch.length === 2 ) {
out.push(filterMatch[1].trim().replace(/\\\[/g, '['));
}
}
matches = reAbpSubscriptionExtractor.exec(s);
} while ( matches !== null );
return out.join('\n');
};
const fileReaderOnLoadHandler = function() {
let content = abpImporter(this.result);
content = uBlockDashboard.mergeNewLines(
cmEditor.getValue().trim(),
content
);
cmEditor.operation(( ) => {
const cmPos = cmEditor.getCursor();
cmEditor.setValue(`${content}\n`);
cmEditor.setCursor(cmPos);
cmEditor.focus();
});
};
const file = this.files[0];
if ( file === undefined || file.name === '' ) { return; }
if ( file.type.indexOf('text') !== 0 ) { return; }
const fr = new FileReader();
fr.onload = fileReaderOnLoadHandler;
fr.readAsText(file);
};
/******************************************************************************/
const startImportFilePicker = function() {
const input = document.getElementById('importFilePicker');
// Reset to empty string, this will ensure an change event is properly
// triggered if the user pick a file, even if it is the same as the last
// one picked.
input.value = '';
input.click();
};
/******************************************************************************/
const exportUserFiltersToFile = function() {
const val = cmEditor.getValue().trim();
if ( val === '' ) { return; }
const filename = vAPI.i18n('1pExportFilename')
.replace('{{datetime}}', uBlockDashboard.dateNowToSensibleString())
.replace(/ +/g, '_');
vAPI.download({
'url': 'data:text/plain;charset=utf-8,' + encodeURIComponent(val + '\n'),
'filename': filename
});
};
/******************************************************************************/
const applyChanges = function() {
messaging.send(
'dashboard',
{
what: 'writeUserFilters',
content: cmEditor.getValue()
},
details => {
if ( details.error ) { return; }
cachedUserFilters = details.content.trim();
userFiltersChanged(false);
messaging.send('dashboard', { what: 'reloadAllFilters' });
}
);
};
const revertChanges = function() {
let content = cachedUserFilters;
if ( content.length !== 0 ) {
content += '\n';
}
cmEditor.setValue(content);
};
/******************************************************************************/
const getCloudData = function() {
return cmEditor.getValue();
};
const setCloudData = function(data, append) {
if ( typeof data !== 'string' ) { return; }
if ( append ) {
data = uBlockDashboard.mergeNewLines(cmEditor.getValue(), data);
}
cmEditor.setValue(data);
};
self.cloud.onPush = getCloudData;
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);
uDom('#exportUserFiltersToFile').on('click', exportUserFiltersToFile);
uDom('#userFiltersApply').on('click', applyChanges);
uDom('#userFiltersRevert').on('click', revertChanges);
renderUserFilters(true);
cmEditor.on('changes', userFiltersChanged);
CodeMirror.commands.save = applyChanges;
/******************************************************************************/
})();