support append from cloud storage + uniformize buttons visual in dashboard

This commit is contained in:
gorhill 2015-08-12 12:17:39 -04:00
parent 09790f30a2
commit f338c28cd6
13 changed files with 198 additions and 83 deletions

View File

@ -14,11 +14,16 @@
<div id="cloudWidget" class="hide" data-cloud-entry="myFiltersPane"></div>
<p data-i18n="1pFormatHint"></p>
<p><button id="userFiltersApply" class="important" type="button" disabled="true" data-i18n="1pApplyChanges"></button></p>
<textarea class="userFilters" id="userFilters" dir="auto" spellcheck="false"></textarea>
<p><button id="importUserFiltersFromFile" data-i18n="1pImport"></button> &emsp;
<button id="exportUserFiltersToFile" data-i18n="1pExport"></button>
<input id="importFilePicker" type="file" accept="text/plain" class="hiddenFileInput"></p>
<p>
<button id="userFiltersApply" class="custom important" type="button" disabled="true" data-i18n="1pApplyChanges"></button>&ensp;
<button id="userFiltersRevert" class="custom" type="button" disabled="true" data-i18n="genericRevert"></button>
</p>
<p><textarea class="userFilters" id="userFilters" dir="auto" spellcheck="false"></textarea></p>
<p>
<button id="importUserFiltersFromFile" class="custom" data-i18n="1pImport"></button>&ensp;
<button id="exportUserFiltersToFile" class="custom" data-i18n="1pExport"></button>
<input id="importFilePicker" type="file" accept="text/plain" class="hiddenFileInput">
</p>
<script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script>

View File

@ -17,7 +17,7 @@
<button id="buttonApply" class="important disabled" data-i18n="3pApplyChanges"></button>
<ul id="options">
<li><input type="checkbox" id="autoUpdate"><label data-i18n="3pAutoUpdatePrompt1" for="autoUpdate"></label>&ensp;
<button class="important disabled" id="buttonUpdate" data-i18n="3pUpdateNow"></button>
<button class="custom important disabled" id="buttonUpdate" data-i18n="3pUpdateNow"></button>
<button id="buttonPurgeAll" class="custom disabled" data-i18n="3pPurgeAll"></button>
<li><input type="checkbox" id="parseCosmeticFilters"><label data-i18n="3pParseAllABPHideFiltersPrompt1" for="parseCosmeticFilters"></label>
<button class="whatisthis"></button>
@ -29,8 +29,9 @@
<div id="externalListsDiv">
<p data-i18n="3pExternalListsHint" style="margin: 0 0 0.25em 0; font-size: 13px;"></p>
<textarea id="externalLists" dir="ltr" spellcheck="false"></textarea>
<p style="margin: 0.25em 0 0 0"><button id="externalListsApply" class="important" disabled="true" data-i18n="3pExternalListsApply"></button></p>
<p style="margin: 0.25em 0 0 0">
<textarea id="externalLists" dir="ltr" spellcheck="false"></textarea>
<button id="externalListsApply" class="custom important" disabled="true" data-i18n="3pExternalListsApply"></button></p>
</div>
<div id="busyOverlay">

View File

@ -615,6 +615,10 @@
"message": "Submit",
"description": "for generic 'submit' buttons"
},
"genericRevert": {
"message": "Revert",
"description": "for generic 'revert' buttons"
},
"dummy":{
"message":"This entry must be the last one",
"description":"so we dont need to deal with comma for last entry"

View File

@ -73,28 +73,6 @@ li.listEntry > a:nth-of-type(3) {
.dim {
opacity: 0.5;
}
/* I designed the button with: http://charliepark.org/bootstrap_buttons/ */
button.custom {
padding: 0.6em 1em;
border: 1px solid transparent;
border-color: #80b3ff #80b3ff hsl(216, 100%, 75%);
border-radius: 3px;
background-color: hsl(216, 100%, 75%);
background-image: linear-gradient(#a8cbff, #80b3ff);
background-repeat: repeat-x;
color: #222;
opacity: 0.8;
}
button.custom.disabled {
border-color: #dddddd #dddddd hsl(36, 0%, 85%);
background-color: hsl(36, 0%, 72%);
background-image: linear-gradient(#f2f2f2, #dddddd);
color: #aaa;
pointer-events: none;
}
button.custom:hover {
opacity: 1.0;
}
#buttonApply {
display: initial;
position: fixed;

View File

@ -17,6 +17,30 @@ body {
color: black;
font: 14px/1.3 sans-serif;
}
/* I designed the button with: http://charliepark.org/bootstrap_buttons/ */
button.custom {
padding: 0.6em 1em;
border: 1px solid transparent;
border-color: #ccc #ccc #bbb #bbb;
border-radius: 3px;
background-color: hsl(216, 0%, 75%);
background-image: linear-gradient(#f2f2f2, #dddddd);
background-repeat: repeat-x;
color: #000;
opacity: 0.8;
}
button.custom.disabled,
button.custom[disabled] {
border-color: #ddd #ddd hsl(36, 0%, 85%);
background-color: hsl(36, 0%, 72%);
background-image: linear-gradient(#f2f2f2, #dddddd);
color: #666;
opacity: 0.6;
pointer-events: none;
}
button.custom:hover {
opacity: 1.0;
}
button.important {
padding: 0.6em 1em;
border: 1px solid transparent;
@ -28,14 +52,6 @@ button.important {
color: #222;
opacity: 0.8;
}
button.important[disabled],
button.important.disabled {
border-color: #dddddd #dddddd hsl(36, 0%, 85%);
background-color: hsl(36, 0%, 72%);
background-image: linear-gradient(#f2f2f2, #dddddd);
color: #888;
pointer-events: none;
}
button.important:hover {
opacity: 1.0;
}

View File

@ -19,8 +19,8 @@
<div class="pane left">
<div class="ruleActions">
<h2 data-i18n="rulesPermanentHeader"></h2>
<button type="button" id="exportButton" data-i18n="rulesExport"></button>
<button type="button" id="revertButton" data-i18n="rulesRevert"></button>
<button type="button" class="custom" id="exportButton" data-i18n="rulesExport"></button>
<button type="button" class="custom" id="revertButton" data-i18n="rulesRevert"></button>
</div>
<div class="rulesContainer">
<ul></ul>
@ -29,11 +29,11 @@
<div class="pane right">
<div class="ruleActions">
<h2 data-i18n="rulesTemporaryHeader"></h2>
<button type="button" id="commitButton" data-i18n="rulesCommit"></button>
<button type="button" id="editEnterButton" data-i18n="rulesEdit"></button>
<button type="button" id="editStopButton" data-i18n="rulesEditSave"></button>
<button type="button" id="editCancelButton" data-i18n="rulesEditDiscard"></button>
<button type="button" id="importButton" data-i18n="rulesImport"></button>
<button type="button" class="custom" id="commitButton" data-i18n="rulesCommit"></button>
<button type="button" class="custom" id="editEnterButton" data-i18n="rulesEdit"></button>
<button type="button" class="custom" id="editStopButton" data-i18n="rulesEditSave"></button>
<button type="button" class="custom" id="editCancelButton" data-i18n="rulesEditDiscard"></button>
<button type="button" class="custom" id="importButton" data-i18n="rulesImport"></button>
</div>
<div class="rulesContainer">
<textarea spellcheck="false"></textarea>

View File

@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, uDom */
/* global vAPI, uDom, uBlockDashboard */
/******************************************************************************/
@ -40,8 +40,9 @@ var messager = vAPI.messaging.channel('1p-filters.js');
// This is to give a visual hint that the content of user blacklist has changed.
function userFiltersChanged() {
uDom.nodeFromId('userFiltersApply').disabled =
uDom('#userFilters').val().trim() === cachedUserFilters;
var changed = uDom.nodeFromId('userFilters').value.trim() !== cachedUserFilters;
uDom.nodeFromId('userFiltersApply').disabled = !changed;
uDom.nodeFromId('userFiltersRevert').disabled = !changed;
}
/******************************************************************************/
@ -53,6 +54,7 @@ function renderUserFilters() {
}
cachedUserFilters = details.content.trim();
uDom.nodeFromId('userFilters').value = details.content;
userFiltersChanged();
};
messager.send({ what: 'readUserFilters' }, onRead);
}
@ -133,14 +135,14 @@ var exportUserFiltersToFile = function() {
.replace('{{datetime}}', now.toLocaleString())
.replace(/ +/g, '_');
vAPI.download({
'url': 'data:text/plain;charset=utf-8,' + encodeURIComponent(val),
'url': 'data:text/plain;charset=utf-8,' + encodeURIComponent(val + '\n'),
'filename': filename
});
};
/******************************************************************************/
var userFiltersApplyHandler = function() {
var applyChanges = function() {
var onWritten = function(details) {
if ( details.error ) {
return;
@ -156,17 +158,26 @@ var userFiltersApplyHandler = function() {
messager.send(request, onWritten);
};
var revertChanges = function() {
uDom.nodeFromId('userFilters').value = cachedUserFilters + '\n';
userFiltersChanged();
};
/******************************************************************************/
var getCloudData = function() {
return uDom.nodeFromId('userFilters').value;
};
var setCloudData = function(data) {
var setCloudData = function(data, append) {
if ( typeof data !== 'string' ) {
return;
}
uDom.nodeFromId('userFilters').value = data;
var textarea = uDom.nodeFromId('userFilters');
if ( append ) {
data = uBlockDashboard.mergeNewLines(textarea.value, data);
}
textarea.value = data;
userFiltersChanged();
};
@ -180,7 +191,8 @@ uDom('#importUserFiltersFromFile').on('click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker);
uDom('#exportUserFiltersToFile').on('click', exportUserFiltersToFile);
uDom('#userFilters').on('input', userFiltersChanged);
uDom('#userFiltersApply').on('click', userFiltersApplyHandler);
uDom('#userFiltersApply').on('click', applyChanges);
uDom('#userFiltersRevert').on('click', revertChanges);
renderUserFilters();

View File

@ -110,9 +110,9 @@ var pushData = function() {
/******************************************************************************/
var pullData = function() {
var pullData = function(ev) {
if ( typeof self.cloud.onPull === 'function' ) {
self.cloud.onPull(self.cloud.data);
self.cloud.onPull(self.cloud.data, ev.shiftKey);
}
};
@ -196,4 +196,6 @@ messager.send({ what: 'cloudGetOptions' }, onInitialize);
/******************************************************************************/
// https://www.youtube.com/watch?v=aQFp67VoiDA
})();

View File

@ -24,7 +24,86 @@
/******************************************************************************/
uDom.onLoad(function() {
self.uBlockDashboard = self.uBlockDashboard || {};
/******************************************************************************/
// Helper for client panes:
// Remove literal duplicate lines from a set based on another set.
self.uBlock.mergeNewLines = function(text, newText) {
var lineBeg, textEnd, lineEnd;
var line, hash, bucket;
// Step 1: build dictionary for existing lines.
var fromDict = Object.create(null);
lineBeg = 0;
textEnd = text.length;
while ( lineBeg < textEnd ) {
lineEnd = text.indexOf('\n', lineBeg);
if ( lineEnd === -1 ) {
lineEnd = text.indexOf('\r', lineBeg);
if ( lineEnd === -1 ) {
lineEnd = textEnd;
}
}
line = text.slice(lineBeg, lineEnd).trim();
lineBeg = lineEnd + 1;
if ( line.length === 0 ) {
continue;
}
hash = line.slice(0, 8);
bucket = fromDict[hash];
if ( bucket === undefined ) {
fromDict[hash] = line;
} else if ( typeof bucket === 'string' ) {
fromDict[hash] = [bucket, line];
} else /* if ( Array.isArray(bucket) ) */ {
bucket.push(line);
}
}
// Step 2: use above dictionary to filter out duplicate lines.
var out = [ '' ];
lineBeg = 0;
textEnd = newText.length;
while ( lineBeg < textEnd ) {
lineEnd = newText.indexOf('\n', lineBeg);
if ( lineEnd === -1 ) {
lineEnd = newText.indexOf('\r', lineBeg);
if ( lineEnd === -1 ) {
lineEnd = textEnd;
}
}
line = newText.slice(lineBeg, lineEnd).trim();
lineBeg = lineEnd + 1;
if ( line.length === 0 ) {
if ( out[out.length - 1] !== '' ) {
out.push('');
}
continue;
}
bucket = fromDict[line.slice(0, 8)];
if ( bucket === undefined ) {
out.push(line);
continue;
}
if ( typeof bucket === 'string' && line !== bucket ) {
out.push(line);
continue;
}
if ( bucket.indexOf(line) === -1 ) {
out.push(line);
/* continue; */
}
}
return text.trim() + '\n' + out.join('\n');
};
/******************************************************************************/
(function() {
// Open links in the proper window
uDom('a').attr('target', '_blank');
uDom('a[href*="dashboard.html"]').attr('target', '_parent');
@ -34,4 +113,4 @@ uDom.onLoad(function() {
.descendants('.whatisthis-expandable')
.toggleClass('whatisthis-expanded');
});
});
})();

View File

@ -158,7 +158,7 @@ function exportUserRulesToFile() {
.replace('{{datetime}}', now.toLocaleString())
.replace(/ +/g, '_');
vAPI.download({
'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li')),
'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li') + '\n'),
'filename': filename,
'saveAs': true
});
@ -238,10 +238,13 @@ var getCloudData = function() {
return rulesFromHTML('#diff .left li');
};
var setCloudData = function(data) {
var setCloudData = function(data, append) {
if ( typeof data !== 'string' ) {
return;
}
if ( append ) {
data = rulesFromHTML('#diff .right li') + '\n' + data;
}
var request = {
'what': 'setSessionRules',
'rules': data

View File

@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uBlock
*/
/* global vAPI, uDom */
/* global vAPI, uDom, uBlockDashboard */
/******************************************************************************/
@ -41,24 +41,24 @@ var reUnwantedChars = /[\x00-\x09\x0b\x0c\x0e-\x1f!"$'()<>{}|\\^\[\]`~]/;
/******************************************************************************/
var whitelistChanged = function() {
var s = uDom.nodeFromId('whitelist').value.trim();
var textarea = uDom.nodeFromId('whitelist');
var s = textarea.value.trim();
var changed = s === cachedWhitelist;
var bad = reUnwantedChars.test(s);
uDom('#whitelistApply').prop(
'disabled',
s === cachedWhitelist || bad
);
uDom('#whitelist').toggleClass('bad', bad);
uDom.nodeFromId('whitelistApply').disabled = changed || bad;
uDom.nodeFromId('whitelistRevert').disabled = changed;
textarea.classList.toggle('bad', bad);
};
/******************************************************************************/
var renderWhitelist = function() {
var onRead = function(whitelist) {
cachedWhitelist = whitelist;
uDom.nodeFromId('whitelist').value = whitelist;
cachedWhitelist = whitelist.trim();
uDom.nodeFromId('whitelist').value = cachedWhitelist + '\n';
whitelistChanged();
};
messager.send({ what: 'getWhitelist' }, onRead);
whitelistChanged();
};
/******************************************************************************/
@ -104,15 +104,15 @@ var exportWhitelistToFile = function() {
.replace('{{datetime}}', now.toLocaleString())
.replace(/ +/g, '_');
vAPI.download({
'url': 'data:text/plain;charset=utf-8,' + encodeURIComponent(val),
'url': 'data:text/plain;charset=utf-8,' + encodeURIComponent(val + '\n'),
'filename': filename
});
};
/******************************************************************************/
var whitelistApplyHandler = function() {
cachedWhitelist = uDom('#whitelist').val().trim();
var applyChanges = function() {
cachedWhitelist = uDom.nodeFromId('whitelist').value.trim();
var request = {
what: 'setWhitelist',
whitelist: cachedWhitelist
@ -120,17 +120,26 @@ var whitelistApplyHandler = function() {
messager.send(request, renderWhitelist);
};
var revertChanges = function() {
uDom.nodeFromId('whitelist').value = cachedWhitelist + '\n';
whitelistChanged();
};
/******************************************************************************/
var getCloudData = function() {
return uDom.nodeFromId('whitelist').value;
};
var setCloudData = function(data) {
var setCloudData = function(data, append) {
if ( typeof data !== 'string' ) {
return;
}
uDom.nodeFromId('whitelist').value = data;
var textarea = uDom.nodeFromId('whitelist');
if ( append ) {
data = uBlockDashboard.mergeNewLines(textarea.value.trim(), data);
}
textarea.value = data.trim() + '\n';
whitelistChanged();
};
@ -143,7 +152,8 @@ uDom('#importWhitelistFromFile').on('click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker);
uDom('#exportWhitelistToFile').on('click', exportWhitelistToFile);
uDom('#whitelist').on('input', whitelistChanged);
uDom('#whitelistApply').on('click', whitelistApplyHandler);
uDom('#whitelistApply').on('click', applyChanges);
uDom('#whitelistRevert').on('click', revertChanges);
renderWhitelist();

View File

@ -39,11 +39,11 @@
</div>
<div style="margin: 2.5em 1em;">
<p><button type="button" id="export" data-i18n="aboutBackupDataButton"></button>&ensp;
<button type="button" id="import" data-i18n="aboutRestoreDataButton"></button>
<p><button class="custom" type="button" id="export" data-i18n="aboutBackupDataButton"></button>&ensp;
<button class="custom" type="button" id="import" data-i18n="aboutRestoreDataButton"></button>
<input id="restoreFilePicker" type="file" accept="text/plain" class="hiddenFileInput">
<p>
<p><button type="button" id="reset" data-i18n="aboutResetDataButton"></button>
<p><button class="custom" type="button" id="reset" data-i18n="aboutResetDataButton"></button>
</div>
<script src="js/vapi-common.js"></script>

View File

@ -14,11 +14,16 @@
<div id="cloudWidget" class="hide" data-cloud-entry="whitelistPane"></div>
<p data-i18n="whitelistPrompt"></p>
<p><button id="whitelistApply" class="important" type="button" disabled="true" data-i18n="whitelistApply"></button></p>
<textarea id="whitelist" dir="auto" spellcheck="false"></textarea>
<p><button id="importWhitelistFromFile" data-i18n="whitelistImport"></button> &emsp;
<button id="exportWhitelistToFile" data-i18n="whitelistExport"></button>
<input id="importFilePicker" type="file" accept="text/plain" class="hiddenFileInput"></p>
<p>
<button id="whitelistApply" class="custom important" type="button" disabled="true" data-i18n="whitelistApply"></button>&ensp;
<button id="whitelistRevert" class="custom" type="button" disabled="true" data-i18n="genericRevert"></button>
</p>
<p><textarea id="whitelist" dir="auto" spellcheck="false"></textarea></p>
<p>
<button id="importWhitelistFromFile" class="custom" data-i18n="whitelistImport"></button>&ensp;
<button id="exportWhitelistToFile" class="custom" data-i18n="whitelistExport"></button>
<input id="importFilePicker" type="file" accept="text/plain" class="hiddenFileInput">
</p>
<script src="lib/punycode.js"></script>
<script src="js/vapi-common.js"></script>