mirror of
https://github.com/gorhill/uMatrix.git
synced 2024-05-20 04:03:13 +12:00
imported cloud storage support from uBlock: user rules supported for now
This commit is contained in:
parent
9e4e4943f3
commit
f3876463d3
|
@ -865,6 +865,183 @@ vAPI.cookies.remove = function(details, callback) {
|
|||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.cloud = (function() {
|
||||
var chunkCountPerFetch = 16; // Must be a power of 2
|
||||
|
||||
// Mind chrome.storage.sync.MAX_ITEMS (512 at time of writing)
|
||||
var maxChunkCountPerItem = Math.floor(512 * 0.75) & ~(chunkCountPerFetch - 1);
|
||||
|
||||
// Mind chrome.storage.sync.QUOTA_BYTES_PER_ITEM (8192 at time of writing)
|
||||
var maxChunkSize = Math.floor(chrome.storage.sync.QUOTA_BYTES_PER_ITEM * 0.75);
|
||||
|
||||
// Mind chrome.storage.sync.QUOTA_BYTES_PER_ITEM (8192 at time of writing)
|
||||
var maxStorageSize = chrome.storage.sync.QUOTA_BYTES;
|
||||
|
||||
var options = {
|
||||
defaultDeviceName: window.navigator.platform,
|
||||
deviceName: window.localStorage.getItem('deviceName') || ''
|
||||
};
|
||||
|
||||
// This is used to find out a rough count of how many chunks exists:
|
||||
// We "poll" at specific index in order to get a rough idea of how
|
||||
// large is the stored string.
|
||||
// This allows reading a single item with only 2 sync operations -- a
|
||||
// good thing given chrome.storage.syncMAX_WRITE_OPERATIONS_PER_MINUTE
|
||||
// and chrome.storage.syncMAX_WRITE_OPERATIONS_PER_HOUR.
|
||||
|
||||
var getCoarseChunkCount = function(dataKey, callback) {
|
||||
var bin = {};
|
||||
for ( var i = 0; i < maxChunkCountPerItem; i += 16 ) {
|
||||
bin[dataKey + i.toString()] = '';
|
||||
}
|
||||
|
||||
chrome.storage.sync.get(bin, function(bin) {
|
||||
if ( chrome.runtime.lastError ) {
|
||||
callback(0, chrome.runtime.lastError.message);
|
||||
return;
|
||||
}
|
||||
|
||||
var chunkCount = 0;
|
||||
for ( var i = 0; i < maxChunkCountPerItem; i += 16 ) {
|
||||
if ( bin[dataKey + i.toString()] === '' ) {
|
||||
break;
|
||||
}
|
||||
chunkCount = i + 16;
|
||||
}
|
||||
|
||||
callback(chunkCount);
|
||||
});
|
||||
};
|
||||
|
||||
var deleteChunks = function(dataKey, start) {
|
||||
var keys = [];
|
||||
|
||||
// No point in deleting more than:
|
||||
// - The max number of chunks per item
|
||||
// - The max number of chunks per storage limit
|
||||
var n = Math.min(
|
||||
maxChunkCountPerItem,
|
||||
Math.ceil(maxStorageSize / maxChunkSize)
|
||||
);
|
||||
for ( var i = start; i < n; i++ ) {
|
||||
keys.push(dataKey + i.toString());
|
||||
}
|
||||
chrome.storage.sync.remove(keys);
|
||||
};
|
||||
|
||||
var start = function(/* dataKeys */) {
|
||||
};
|
||||
|
||||
var push = function(dataKey, data, callback) {
|
||||
var bin = {
|
||||
'source': options.deviceName || options.defaultDeviceName,
|
||||
'tstamp': Date.now(),
|
||||
'data': data,
|
||||
'size': 0
|
||||
};
|
||||
bin.size = JSON.stringify(bin).length;
|
||||
var item = JSON.stringify(bin);
|
||||
|
||||
// Chunkify taking into account QUOTA_BYTES_PER_ITEM:
|
||||
// https://developer.chrome.com/extensions/storage#property-sync
|
||||
// "The maximum size (in bytes) of each individual item in sync
|
||||
// "storage, as measured by the JSON stringification of its value
|
||||
// "plus its key length."
|
||||
bin = {};
|
||||
var chunkCount = Math.ceil(item.length / maxChunkSize);
|
||||
for ( var i = 0; i < chunkCount; i++ ) {
|
||||
bin[dataKey + i.toString()] = item.substr(i * maxChunkSize, maxChunkSize);
|
||||
}
|
||||
bin[dataKey + i.toString()] = ''; // Sentinel
|
||||
|
||||
chrome.storage.sync.set(bin, function() {
|
||||
var errorStr;
|
||||
if ( chrome.runtime.lastError ) {
|
||||
errorStr = chrome.runtime.lastError.message;
|
||||
}
|
||||
callback(errorStr);
|
||||
|
||||
// Remove potentially unused trailing chunks
|
||||
deleteChunks(dataKey, chunkCount);
|
||||
});
|
||||
};
|
||||
|
||||
var pull = function(dataKey, callback) {
|
||||
var assembleChunks = function(bin) {
|
||||
if ( chrome.runtime.lastError ) {
|
||||
callback(null, chrome.runtime.lastError.message);
|
||||
return;
|
||||
}
|
||||
|
||||
// Assemble chunks into a single string.
|
||||
var json = [], jsonSlice;
|
||||
var i = 0;
|
||||
for (;;) {
|
||||
jsonSlice = bin[dataKey + i.toString()];
|
||||
if ( jsonSlice === '' ) {
|
||||
break;
|
||||
}
|
||||
json.push(jsonSlice);
|
||||
i += 1;
|
||||
}
|
||||
|
||||
var entry = null;
|
||||
try {
|
||||
entry = JSON.parse(json.join(''));
|
||||
} catch(ex) {
|
||||
}
|
||||
callback(entry);
|
||||
};
|
||||
|
||||
var fetchChunks = function(coarseCount, errorStr) {
|
||||
if ( coarseCount === 0 || typeof errorStr === 'string' ) {
|
||||
callback(null, errorStr);
|
||||
return;
|
||||
}
|
||||
|
||||
var bin = {};
|
||||
for ( var i = 0; i < coarseCount; i++ ) {
|
||||
bin[dataKey + i.toString()] = '';
|
||||
}
|
||||
|
||||
chrome.storage.sync.get(bin, assembleChunks);
|
||||
};
|
||||
|
||||
getCoarseChunkCount(dataKey, fetchChunks);
|
||||
};
|
||||
|
||||
var getOptions = function(callback) {
|
||||
if ( typeof callback !== 'function' ) {
|
||||
return;
|
||||
}
|
||||
callback(options);
|
||||
};
|
||||
|
||||
var setOptions = function(details, callback) {
|
||||
if ( typeof details !== 'object' || details === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( typeof details.deviceName === 'string' ) {
|
||||
window.localStorage.setItem('deviceName', details.deviceName);
|
||||
options.deviceName = details.deviceName;
|
||||
}
|
||||
|
||||
getOptions(callback);
|
||||
};
|
||||
|
||||
return {
|
||||
start: start,
|
||||
push: push,
|
||||
pull: pull,
|
||||
getOptions: getOptions,
|
||||
setOptions: setOptions
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -2851,6 +2851,125 @@ vAPI.punycodeURL = function(url) {
|
|||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.cloud = (function() {
|
||||
var extensionBranchPath = 'extensions.' + location.host;
|
||||
var cloudBranchPath = extensionBranchPath + '.cloudStorage';
|
||||
|
||||
// https://github.com/gorhill/uBlock/issues/80#issuecomment-132081658
|
||||
// We must use get/setComplexValue in order to properly handle strings
|
||||
// with unicode characters.
|
||||
var iss = Ci.nsISupportsString;
|
||||
var argstr = Components.classes['@mozilla.org/supports-string;1']
|
||||
.createInstance(iss);
|
||||
|
||||
var options = {
|
||||
defaultDeviceName: '',
|
||||
deviceName: ''
|
||||
};
|
||||
|
||||
// User-supplied device name.
|
||||
try {
|
||||
options.deviceName = Services.prefs
|
||||
.getBranch(extensionBranchPath + '.')
|
||||
.getComplexValue('deviceName', iss)
|
||||
.data;
|
||||
} catch(ex) {
|
||||
}
|
||||
|
||||
var getDefaultDeviceName = function() {
|
||||
var name = '';
|
||||
try {
|
||||
name = Services.prefs
|
||||
.getBranch('services.sync.client.')
|
||||
.getComplexValue('name', iss)
|
||||
.data;
|
||||
} catch(ex) {
|
||||
}
|
||||
|
||||
return name || window.navigator.platform || window.navigator.oscpu;
|
||||
};
|
||||
|
||||
var start = function(dataKeys) {
|
||||
var extensionBranch = Services.prefs.getBranch(extensionBranchPath + '.');
|
||||
var syncBranch = Services.prefs.getBranch('services.sync.prefs.sync.');
|
||||
|
||||
// Mark config entries as syncable
|
||||
argstr.data = '';
|
||||
var dataKey;
|
||||
for ( var i = 0; i < dataKeys.length; i++ ) {
|
||||
dataKey = dataKeys[i];
|
||||
if ( extensionBranch.prefHasUserValue('cloudStorage.' + dataKey) === false ) {
|
||||
extensionBranch.setComplexValue('cloudStorage.' + dataKey, iss, argstr);
|
||||
}
|
||||
syncBranch.setBoolPref(cloudBranchPath + '.' + dataKey, true);
|
||||
}
|
||||
};
|
||||
|
||||
var push = function(datakey, data, callback) {
|
||||
var branch = Services.prefs.getBranch(cloudBranchPath + '.');
|
||||
var bin = {
|
||||
'source': options.deviceName || getDefaultDeviceName(),
|
||||
'tstamp': Date.now(),
|
||||
'data': data,
|
||||
'size': 0
|
||||
};
|
||||
bin.size = JSON.stringify(bin).length;
|
||||
argstr.data = JSON.stringify(bin);
|
||||
branch.setComplexValue(datakey, iss, argstr);
|
||||
if ( typeof callback === 'function' ) {
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
var pull = function(datakey, callback) {
|
||||
var result = null;
|
||||
var branch = Services.prefs.getBranch(cloudBranchPath + '.');
|
||||
try {
|
||||
var json = branch.getComplexValue(datakey, iss).data;
|
||||
if ( typeof json === 'string' ) {
|
||||
result = JSON.parse(json);
|
||||
}
|
||||
} catch(ex) {
|
||||
}
|
||||
callback(result);
|
||||
};
|
||||
|
||||
var getOptions = function(callback) {
|
||||
if ( typeof callback !== 'function' ) {
|
||||
return;
|
||||
}
|
||||
options.defaultDeviceName = getDefaultDeviceName();
|
||||
callback(options);
|
||||
};
|
||||
|
||||
var setOptions = function(details, callback) {
|
||||
if ( typeof details !== 'object' || details === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
var branch = Services.prefs.getBranch(extensionBranchPath + '.');
|
||||
|
||||
if ( typeof details.deviceName === 'string' ) {
|
||||
argstr.data = details.deviceName;
|
||||
branch.setComplexValue('deviceName', iss, argstr);
|
||||
options.deviceName = details.deviceName;
|
||||
}
|
||||
|
||||
getOptions(callback);
|
||||
};
|
||||
|
||||
return {
|
||||
start: start,
|
||||
push: push,
|
||||
pull: pull,
|
||||
getOptions: getOptions,
|
||||
setOptions: setOptions
|
||||
};
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.browserData = {};
|
||||
|
||||
/******************************************************************************/
|
||||
|
|
|
@ -314,7 +314,10 @@
|
|||
"message": "Collapse placeholder of blocked elements",
|
||||
"description": "English: Collapse placeholder of blocked elements"
|
||||
},
|
||||
|
||||
"settingsCloudStorageEnabled" : {
|
||||
"message": "Enable cloud storage support",
|
||||
"description": ""
|
||||
},
|
||||
|
||||
"privacyPageTitle" : {
|
||||
"message": "uMatrix – Privacy",
|
||||
|
@ -680,6 +683,31 @@
|
|||
"description":"Appears in Firefox's add-on preferences"
|
||||
},
|
||||
|
||||
"cloudPush": {
|
||||
"message": "Export to cloud storage",
|
||||
"description": "tooltip"
|
||||
},
|
||||
"cloudPull": {
|
||||
"message": "Import from cloud storage",
|
||||
"description": "tooltip"
|
||||
},
|
||||
"cloudNoData": {
|
||||
"message": "...\n...",
|
||||
"description": ""
|
||||
},
|
||||
"cloudDeviceNamePrompt": {
|
||||
"message": "This device name:",
|
||||
"description": "used as a prompt for the user to provide a custom device name"
|
||||
},
|
||||
"genericSubmit": {
|
||||
"message": "Submit",
|
||||
"description": "for generic 'submit' buttons"
|
||||
},
|
||||
"genericRevert": {
|
||||
"message": "Revert",
|
||||
"description": "for generic 'revert' buttons"
|
||||
},
|
||||
|
||||
"errorCantConnectTo":{
|
||||
"message":"Network error: Unable to connect to {{url}}",
|
||||
"description":""
|
||||
|
|
92
src/css/cloud-ui.css
Normal file
92
src/css/cloud-ui.css
Normal file
|
@ -0,0 +1,92 @@
|
|||
#cloudWidget {
|
||||
background: url("../img/cloud.png") hsl(216, 100%, 93%);
|
||||
border-radius: 3px;
|
||||
margin: 0.5em 0;
|
||||
padding: 1em;
|
||||
position: relative;
|
||||
}
|
||||
#cloudWidget.hide {
|
||||
display: none;
|
||||
}
|
||||
#cloudWidget > button {
|
||||
font-size: 160%;
|
||||
padding: 0.1em 0.2em;
|
||||
}
|
||||
#cloudPull[disabled] {
|
||||
visibility: hidden;
|
||||
}
|
||||
#cloudPush:after ,
|
||||
#cloudPull:before {
|
||||
font-family: FontAwesome;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
line-height: 1;
|
||||
vertical-align: baseline;
|
||||
display: inline-block;
|
||||
}
|
||||
body[dir="ltr"] #cloudPush:after {
|
||||
content: '\f0ee';
|
||||
}
|
||||
body[dir="rtl"] #cloudPush:after {
|
||||
content: '\f0ee';
|
||||
}
|
||||
body[dir="ltr"] #cloudPull:before {
|
||||
content: '\f0ed';
|
||||
}
|
||||
body[dir="rtl"] #cloudPull:before {
|
||||
content: '\f0ed';
|
||||
}
|
||||
#cloudWidget > span {
|
||||
color: gray;
|
||||
display: inline-block;
|
||||
font-size: 90%;
|
||||
margin: 0 1em;
|
||||
padding: 0;
|
||||
vertical-align: bottom;
|
||||
white-space: pre;
|
||||
}
|
||||
#cloudWidget > .nodata {
|
||||
}
|
||||
#cloudWidget > #cloudCog {
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
font-size: 110%;
|
||||
margin: 0;
|
||||
opacity: 0.5;
|
||||
padding: 4px;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
}
|
||||
body[dir="ltr"] #cloudWidget > #cloudCog {
|
||||
right: 0;
|
||||
}
|
||||
body[dir="rtl"] #cloudWidget > #cloudCog {
|
||||
left: 0;
|
||||
}
|
||||
#cloudWidget > #cloudCog:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
#cloudWidget > #cloudOptions {
|
||||
align-items: center;
|
||||
-webkit-align-items: center;
|
||||
background-color: rgba(0, 0, 0, 0.75);
|
||||
bottom: 0;
|
||||
display: none;
|
||||
justify-content: center;
|
||||
-webkit-justify-content: center;
|
||||
left: 0;
|
||||
position: fixed;
|
||||
right: 0;
|
||||
top: 0;
|
||||
z-index: 2000;
|
||||
}
|
||||
#cloudWidget > #cloudOptions.show {
|
||||
display: flex;
|
||||
display: -webkit-flex;
|
||||
}
|
||||
#cloudWidget > #cloudOptions > div {
|
||||
background-color: white;
|
||||
border-radius: 3px;
|
||||
padding: 1em;
|
||||
text-align: center;
|
||||
}
|
|
@ -17,6 +17,7 @@ div > p:last-child {
|
|||
margin: 0;
|
||||
padding: 0;
|
||||
position: relative;
|
||||
vertical-align: top;
|
||||
white-space: normal;
|
||||
width: calc(50% - 2px);
|
||||
}
|
||||
|
|
BIN
src/img/cloud.png
Normal file
BIN
src/img/cloud.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
|
@ -104,6 +104,7 @@ return {
|
|||
autoUpdate: false,
|
||||
clearBrowserCache: true,
|
||||
clearBrowserCacheAfter: 60,
|
||||
cloudStorageEnabled: false,
|
||||
collapseBlocked: false,
|
||||
colorBlindFriendly: false,
|
||||
deleteCookies: false,
|
||||
|
|
201
src/js/cloud-ui.js
Normal file
201
src/js/cloud-ui.js
Normal file
|
@ -0,0 +1,201 @@
|
|||
/*******************************************************************************
|
||||
|
||||
uBlock Origin - a browser extension to block requests.
|
||||
Copyright (C) 2015 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 uDom */
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
(function() {
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
self.cloud = {
|
||||
options: {},
|
||||
datakey: '',
|
||||
data: undefined,
|
||||
onPush: null,
|
||||
onPull: null
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var widget = uDom.nodeFromId('cloudWidget');
|
||||
if ( widget === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.cloud.datakey = widget.getAttribute('data-cloud-entry') || '';
|
||||
if ( self.cloud.datakey === '' ) {
|
||||
return;
|
||||
}
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var messager = vAPI.messaging.channel('cloud-ui.js');
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var onCloudDataReceived = function(entry) {
|
||||
if ( typeof entry !== 'object' || entry === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
self.cloud.data = entry.data;
|
||||
|
||||
uDom.nodeFromId('cloudPull').removeAttribute('disabled');
|
||||
|
||||
var timeOptions = {
|
||||
weekday: 'short',
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
day: 'numeric',
|
||||
hour: 'numeric',
|
||||
minute: 'numeric',
|
||||
second: 'numeric',
|
||||
timeZoneName: 'short'
|
||||
};
|
||||
|
||||
var time = new Date(entry.tstamp);
|
||||
widget.querySelector('span').textContent =
|
||||
entry.source + '\n' +
|
||||
time.toLocaleString('fullwide', timeOptions);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var fetchCloudData = function() {
|
||||
messager.send(
|
||||
{
|
||||
what: 'cloudPull',
|
||||
datakey: self.cloud.datakey
|
||||
},
|
||||
onCloudDataReceived
|
||||
);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var pushData = function() {
|
||||
if ( typeof self.cloud.onPush !== 'function' ) {
|
||||
return;
|
||||
}
|
||||
messager.send(
|
||||
{
|
||||
what: 'cloudPush',
|
||||
datakey: self.cloud.datakey,
|
||||
data: self.cloud.onPush()
|
||||
},
|
||||
fetchCloudData
|
||||
);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var pullData = function(ev) {
|
||||
if ( typeof self.cloud.onPull === 'function' ) {
|
||||
self.cloud.onPull(self.cloud.data, ev.shiftKey);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var openOptions = function() {
|
||||
var input = uDom.nodeFromId('cloudDeviceName');
|
||||
input.value = self.cloud.options.deviceName;
|
||||
input.setAttribute('placeholder', self.cloud.options.defaultDeviceName);
|
||||
uDom.nodeFromId('cloudOptions').classList.add('show');
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var closeOptions = function(ev) {
|
||||
var root = uDom.nodeFromId('cloudOptions');
|
||||
if ( ev.target !== root ) {
|
||||
return;
|
||||
}
|
||||
root.classList.remove('show');
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var submitOptions = function() {
|
||||
var onOptions = function(options) {
|
||||
if ( typeof options !== 'object' || options === null ) {
|
||||
return;
|
||||
}
|
||||
self.cloud.options = options;
|
||||
};
|
||||
|
||||
messager.send({
|
||||
what: 'cloudSetOptions',
|
||||
options: {
|
||||
deviceName: uDom.nodeFromId('cloudDeviceName').value
|
||||
}
|
||||
}, onOptions);
|
||||
uDom.nodeFromId('cloudOptions').classList.remove('show');
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var onInitialize = function(options) {
|
||||
if ( typeof options !== 'object' || options === null ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( !options.enabled ) {
|
||||
return;
|
||||
}
|
||||
self.cloud.options = options;
|
||||
|
||||
fetchCloudData();
|
||||
|
||||
var html = [
|
||||
'<button id="cloudPush" type="button" title="cloudPush"></button>',
|
||||
'<span data-i18n="cloudNoData"></span>',
|
||||
'<button id="cloudPull" type="button" title="cloudPull" disabled></button>',
|
||||
'<span id="cloudCog" class="fa"></span>',
|
||||
'<div id="cloudOptions">',
|
||||
' <div>',
|
||||
' <p><label data-i18n="cloudDeviceNamePrompt"></label> <input id="cloudDeviceName" type="text" value="">',
|
||||
' <p><button id="cloudOptionsSubmit" type="button" data-i18n="genericSubmit"></button>',
|
||||
' </div>',
|
||||
'</div>',
|
||||
].join('');
|
||||
|
||||
vAPI.insertHTML(widget, html);
|
||||
vAPI.i18n.render(widget);
|
||||
widget.classList.remove('hide');
|
||||
|
||||
uDom('#cloudPush').on('click', pushData);
|
||||
uDom('#cloudPull').on('click', pullData);
|
||||
uDom('#cloudCog').on('click', openOptions);
|
||||
uDom('#cloudOptions').on('click', closeOptions);
|
||||
uDom('#cloudOptionsSubmit').on('click', submitOptions);
|
||||
};
|
||||
|
||||
messager.send({ what: 'cloudGetOptions' }, onInitialize);
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// https://www.youtube.com/watch?v=aQFp67VoiDA
|
||||
|
||||
})();
|
|
@ -19,6 +19,8 @@
|
|||
Home: https://github.com/gorhill/uMatrix
|
||||
*/
|
||||
|
||||
/* global vAPI, uDom */
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
// This file should always be included at the end of the `body` tag, so as
|
||||
|
@ -30,46 +32,32 @@
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
var text;
|
||||
// Helper to deal with the i18n'ing of HTML files.
|
||||
vAPI.i18n.render = function(context) {
|
||||
uDom('[data-i18n]', context).forEach(function(elem) {
|
||||
elem.html(vAPI.i18n(elem.attr('data-i18n')));
|
||||
});
|
||||
|
||||
var nodeList = document.querySelectorAll('[data-i18n]');
|
||||
var i = nodeList.length;
|
||||
var node;
|
||||
while ( i-- ) {
|
||||
node = nodeList[i];
|
||||
vAPI.insertHTML(node, vAPI.i18n(node.getAttribute('data-i18n')));
|
||||
}
|
||||
uDom('[title]', context).forEach(function(elem) {
|
||||
var title = vAPI.i18n(elem.attr('title'));
|
||||
if ( title ) {
|
||||
elem.attr('title', title);
|
||||
}
|
||||
});
|
||||
|
||||
// copy text of <h1> if any to document title
|
||||
node = document.querySelector('h1');
|
||||
if ( node !== null ) {
|
||||
document.title = node.textContent;
|
||||
}
|
||||
uDom('[placeholder]', context).forEach(function(elem) {
|
||||
elem.attr('placeholder', vAPI.i18n(elem.attr('placeholder')));
|
||||
});
|
||||
|
||||
// Tool tips
|
||||
nodeList = document.querySelectorAll('[data-i18n-tip]');
|
||||
i = nodeList.length;
|
||||
while ( i-- ) {
|
||||
node = nodeList[i];
|
||||
node.setAttribute('data-tip', vAPI.i18n(node.getAttribute('data-i18n-tip')));
|
||||
}
|
||||
nodeList = document.querySelectorAll('[title]');
|
||||
i = nodeList.length;
|
||||
while ( i-- ) {
|
||||
node = nodeList[i];
|
||||
text = node.getAttribute('title');
|
||||
node.setAttribute('title', vAPI.i18n(text) || text);
|
||||
}
|
||||
uDom('[data-i18n-tip]', context).forEach(function(elem) {
|
||||
elem.attr(
|
||||
'data-tip',
|
||||
vAPI.i18n(elem.attr('data-i18n-tip')).replace(/<br>/g, '\n').replace(/\n{3,}/g, '\n\n')
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
nodeList = document.querySelectorAll('input[placeholder]');
|
||||
i = nodeList.length;
|
||||
while ( i-- ) {
|
||||
node = nodeList[i];
|
||||
node.setAttribute(
|
||||
'placeholder',
|
||||
vAPI.i18n(node.getAttribute('placeholder')) || ''
|
||||
);
|
||||
}
|
||||
vAPI.i18n.render();
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
|
|
@ -567,6 +567,68 @@ vAPI.messaging.listen('contentscript-end.js', onMessage);
|
|||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// cloud-ui.js
|
||||
|
||||
(function() {
|
||||
|
||||
'use strict';
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var µm = µMatrix;
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
var onMessage = function(request, sender, callback) {
|
||||
// Async
|
||||
switch ( request.what ) {
|
||||
case 'cloudGetOptions':
|
||||
vAPI.cloud.getOptions(function(options) {
|
||||
options.enabled = µm.userSettings.cloudStorageEnabled === true;
|
||||
callback(options);
|
||||
});
|
||||
return;
|
||||
|
||||
case 'cloudSetOptions':
|
||||
vAPI.cloud.setOptions(request.options, callback);
|
||||
return;
|
||||
|
||||
case 'cloudPull':
|
||||
return vAPI.cloud.pull(request.datakey, callback);
|
||||
|
||||
case 'cloudPush':
|
||||
return vAPI.cloud.push(request.datakey, request.data, callback);
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Sync
|
||||
var response;
|
||||
|
||||
switch ( request.what ) {
|
||||
// For when cloud storage is disabled.
|
||||
case 'cloudPull':
|
||||
// fallthrough
|
||||
case 'cloudPush':
|
||||
break;
|
||||
|
||||
default:
|
||||
return vAPI.messaging.UNHANDLED;
|
||||
}
|
||||
|
||||
callback(response);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
vAPI.messaging.listen('cloud-ui.js', onMessage);
|
||||
|
||||
})();
|
||||
|
||||
/******************************************************************************/
|
||||
/******************************************************************************/
|
||||
|
||||
// settings.js
|
||||
|
||||
(function() {
|
||||
|
|
|
@ -59,13 +59,17 @@ var processUserRules = function(response) {
|
|||
i = rules.length;
|
||||
while ( i-- ) {
|
||||
rule = rules[i].trim();
|
||||
permanentRules[rule] = allRules[rule] = true;
|
||||
if ( rule.length !== 0 ) {
|
||||
permanentRules[rule] = allRules[rule] = true;
|
||||
}
|
||||
}
|
||||
rules = response.temporaryRules.split(/\n+/);
|
||||
i = rules.length;
|
||||
while ( i-- ) {
|
||||
rule = rules[i].trim();
|
||||
temporaryRules[rule] = allRules[rule] = true;
|
||||
if ( rule.length !== 0 ) {
|
||||
temporaryRules[rule] = allRules[rule] = true;
|
||||
}
|
||||
}
|
||||
rules = Object.keys(allRules).sort(directiveSort);
|
||||
for ( i = 0; i < rules.length; i++ ) {
|
||||
|
@ -78,7 +82,7 @@ var processUserRules = function(response) {
|
|||
} else if ( onLeft ) {
|
||||
permanentList.push('<li>', rule);
|
||||
temporaryList.push('<li class="notRight toRemove">', rule);
|
||||
} else {
|
||||
} else if ( onRight ) {
|
||||
permanentList.push('<li> ');
|
||||
temporaryList.push('<li class="notLeft">', rule);
|
||||
}
|
||||
|
@ -153,7 +157,7 @@ var fromNoScript = function(content) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
function handleImportFilePicker() {
|
||||
var handleImportFilePicker = function() {
|
||||
var fileReaderOnLoadHandler = function() {
|
||||
if ( typeof this.result !== 'string' || this.result === '' ) {
|
||||
return;
|
||||
|
@ -181,7 +185,7 @@ function handleImportFilePicker() {
|
|||
var fr = new FileReader();
|
||||
fr.onload = fileReaderOnLoadHandler;
|
||||
fr.readAsText(file);
|
||||
}
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
|
@ -281,6 +285,26 @@ var temporaryRulesToggler = function(ev) {
|
|||
|
||||
/******************************************************************************/
|
||||
|
||||
self.cloud.onPush = function() {
|
||||
return rulesFromHTML('#diff .left li');
|
||||
};
|
||||
|
||||
self.cloud.onPull = function(data, append) {
|
||||
if ( typeof data !== 'string' ) {
|
||||
return;
|
||||
}
|
||||
if ( append ) {
|
||||
data = rulesFromHTML('#diff .right li') + '\n' + data;
|
||||
}
|
||||
var request = {
|
||||
'what': 'setUserRules',
|
||||
'temporaryRules': data
|
||||
};
|
||||
messager.send(request, processUserRules);
|
||||
};
|
||||
|
||||
/******************************************************************************/
|
||||
|
||||
uDom.onLoad(function() {
|
||||
// Handle user interaction
|
||||
uDom('#importButton').on('click', startImportFilePicker);
|
||||
|
|
|
@ -44,6 +44,9 @@ ul > li {
|
|||
<li>
|
||||
<input id="collapseBlocked" type="checkbox" data-range="bool">
|
||||
<label data-i18n="settingsCollapseBlocked" for="collapseBlocked"></label>
|
||||
<li>
|
||||
<input id="cloudStorageEnabled" type="checkbox" data-range="bool">
|
||||
<label data-i18n="settingsCloudStorageEnabled" for="cloudStorageEnabled"></label>
|
||||
</ul>
|
||||
|
||||
|
||||
|
|
|
@ -5,11 +5,14 @@
|
|||
<title>uMatrix — Your rules</title>
|
||||
<link rel="stylesheet" type="text/css" href="css/common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/dashboard-common.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
|
||||
<link rel="stylesheet" type="text/css" href="css/user-rules.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div id="cloudWidget" class="hide" data-cloud-entry="myRulesPane"></div>
|
||||
|
||||
<!-- <p data-i18n="userRulesFormatHint"></p> -->
|
||||
<div id="diff">
|
||||
<div class="pane left">
|
||||
|
@ -42,6 +45,7 @@
|
|||
<script src="js/udom.js"></script>
|
||||
<script src="js/i18n.js"></script>
|
||||
<script src="js/dashboard-common.js"></script>
|
||||
<script src="js/cloud-ui.js"></script>
|
||||
<script src="js/user-rules.js"></script>
|
||||
|
||||
</body>
|
||||
|
|
Loading…
Reference in a new issue