mirror of https://github.com/gorhill/uBlock.git
support append from cloud storage + uniformize buttons visual in dashboard
This commit is contained in:
parent
09790f30a2
commit
f338c28cd6
|
@ -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>  
|
||||
<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> 
|
||||
<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> 
|
||||
<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>
|
||||
|
|
|
@ -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> 
|
||||
<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>
|
||||
<p style="margin: 0.25em 0 0 0">
|
||||
<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>
|
||||
<button id="externalListsApply" class="custom important" disabled="true" data-i18n="3pExternalListsApply"></button></p>
|
||||
</div>
|
||||
|
||||
<div id="busyOverlay">
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
})();
|
||||
|
|
|
@ -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');
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -39,11 +39,11 @@
|
|||
</div>
|
||||
|
||||
<div style="margin: 2.5em 1em;">
|
||||
<p><button type="button" id="export" data-i18n="aboutBackupDataButton"></button> 
|
||||
<button type="button" id="import" data-i18n="aboutRestoreDataButton"></button>
|
||||
<p><button class="custom" type="button" id="export" data-i18n="aboutBackupDataButton"></button> 
|
||||
<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>
|
||||
|
|
|
@ -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>  
|
||||
<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> 
|
||||
<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> 
|
||||
<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>
|
||||
|
|
Loading…
Reference in New Issue