diff --git a/src/_locales/en/messages.json b/src/_locales/en/messages.json index fb9446a..5ac5b93 100644 --- a/src/_locales/en/messages.json +++ b/src/_locales/en/messages.json @@ -35,6 +35,10 @@ "message": "Hosts files", "description": "a tab in dashboard" }, + "rawSettingsPageName": { + "message": "More", + "description": "a tab in dashboard" + }, "aboutPageName": { "message": "About", "description": "a tab in dashboard" @@ -528,6 +532,10 @@ "description":"" }, + "rawSettingsWarning" : { + "message": "Warning! Change these raw configuration settings at your own risk.", + "description": "" + }, "aboutChangelog" : { "message": "Change log", @@ -727,5 +735,10 @@ "errorCantConnectTo":{ "message":"Network error: Unable to connect to {{url}}", "description":"" + }, + + "genericApplyChanges": { + "message": "Apply changes", + "description": "for generic 'Apply changes' buttons" } } diff --git a/src/css/dashboard.css b/src/css/dashboard.css new file mode 100644 index 0000000..0b728ca --- /dev/null +++ b/src/css/dashboard.css @@ -0,0 +1,67 @@ +body { + margin: 0; + border: 0; + padding: 0; + font: 15px sans-serif; + position: relative; + width: 100vw; + height: 100vh; + overflow: hidden; + } +#dashboard-nav { + margin: 0; + border: 0; + padding: 0; + position: absolute; + top: 0; + width: 100vw; + height: 50px; + z-index: 10; + } +#dashboard-nav-widgets { + margin: 0; + border-bottom: 1px solid #ccc; + padding: 4px 0 0 0; + white-space: nowrap; + background-color: white; + } +#dashboard-nav-widgets span { + padding: 0 0.5em; + font-size: larger; + } +.tabButton { + margin: 0; + border: 1px solid #ccc; + border-top-left-radius: 3px; + border-top-right-radius: 3px; + padding: 4px; + display: inline-block; + position: relative; + top: 1px; + color: black; + background-color: #eee; + font: inherit; + cursor: pointer; + text-decoration: none; + } +.tabButton:focus { + outline: 0; + } +.tabButton:active,.tabButton:visited { + color: inherited; + } +.tabButton.selected { + border-bottom: 1px solid white; + background-color: white; + } +iframe { + margin: 0; + border: 0; + padding: 0; + background-color: transparent; + overflow: auto; + position: absolute; + top: 50px; + width: 100%; + height: calc(100% - 50px); + } diff --git a/src/css/raw-settings.css b/src/css/raw-settings.css new file mode 100644 index 0000000..4d9e49d --- /dev/null +++ b/src/css/raw-settings.css @@ -0,0 +1,18 @@ +body { + display: flex; + flex-direction: column; + height: 100vh; + justify-content: space-between; + } +p { + margin: 0.5em 0; + } +textarea { + box-sizing: border-box; + flex-grow: 1; + resize: none; + text-align: left; + white-space: pre; + width: 100%; + word-wrap: normal; + } diff --git a/src/dashboard.html b/src/dashboard.html index 629cb35..752fecf 100644 --- a/src/dashboard.html +++ b/src/dashboard.html @@ -1,79 +1,11 @@ - + - + @@ -84,6 +16,7 @@ iframe { + diff --git a/src/js/background.js b/src/js/background.js index 5ac3651..4f58062 100644 --- a/src/js/background.js +++ b/src/js/background.js @@ -1,7 +1,7 @@ /******************************************************************************* uMatrix - a browser extension to black/white list requests. - Copyright (C) 2014-2017 Raymond Hill + Copyright (C) 2014-2018 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 @@ -79,7 +79,84 @@ var requestStatsFactory = function() { return new RequestStats(); }; -/******************************************************************************/ +/******************************************************************************* + + SVG-based icons below were extracted from + fontawesome-webfont.svg v4.7. Excerpt of copyright notice at + the top of the file: + + > Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 + > By ,,, + > Copyright Dave Gandy 2016. All rights reserved. + + Excerpt of the license information in the fontawesome CSS + file bundled with the package: + + > Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome + > License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + + Font icons: + - glyph-name: "external_link" + +*/ + +var rawSettingsDefault = { + placeholderBackground: + [ + 'url("data:image/png;base64,', + 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAK', + 'CAAAAACoWZBhAAAABGdBTUEAALGPC/xh', + 'BQAAAAJiS0dEAP+Hj8y/AAAAB3RJTUUH', + '3wwIAAgyL/YaPAAAACJJREFUCFtjfMbO', + 'AAQ/gZiFnQPEBAEmGIMIJgtIL8QEgtoA', + 'In4D/96X1KAAAAAldEVYdGRhdGU6Y3Jl', + 'YXRlADIwMTUtMTItMDhUMDA6MDg6NTAr', + 'MDM6MDAasuuJAAAAJXRFWHRkYXRlOm1v', + 'ZGlmeQAyMDE1LTEyLTA4VDAwOjA4OjUw', + 'KzAzOjAwa+9TNQAAAABJRU5ErkJggg==', + '") ', + 'repeat scroll #fff' + ].join(''), + placeholderBorder: '1px solid rgba(0, 0, 0, 0.1)', + imagePlaceholder: true, + imagePlaceholderBackground: 'default', + imagePlaceholderBorder: 'default', + framePlaceholder: true, + framePlaceholderDocument: + [ + '', + '', + '', + '', + '', + '{{url}}', + '' + ].join(''), + framePlaceholderBackground: 'default', +}; + /******************************************************************************/ return { @@ -109,6 +186,10 @@ return { processReferer: false }, + rawSettingsDefault: rawSettingsDefault, + rawSettings: Object.assign({}, rawSettingsDefault), + rawSettingsWriteTime: 0, + clearBrowserCacheCycle: 0, cspNoInlineScript: "script-src 'unsafe-eval' blob: *", cspNoWorker: undefined, diff --git a/src/js/contentscript.js b/src/js/contentscript.js index 438a47a..f278a6a 100644 --- a/src/js/contentscript.js +++ b/src/js/contentscript.js @@ -1,7 +1,7 @@ /******************************************************************************* uMatrix - a Chromium browser extension to black/white list requests. - Copyright (C) 2014-2017 Raymond Hill + Copyright (C) 2014-2018 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 @@ -187,11 +187,16 @@ var collapser = (function() { target.hidden = true; continue; } - if ( tag === 'iframe' ) { + switch ( tag ) { + case 'iframe': + if ( placeholders.frame !== true ) { break; } docurl = 'data:text/html,' + encodeURIComponent( - placeholders.iframe.replace(reURLPlaceholder, src) + placeholders.frameDocument.replace( + reURLPlaceholder, + src + ) ); replaced = false; // Using contentWindow.location prevent tainting browser @@ -206,14 +211,24 @@ var collapser = (function() { if ( !replaced ) { target.setAttribute('src', docurl); } - continue; + break; + case 'img': + if ( placeholders.image !== true ) { break; } + target.style.setProperty('display', 'inline-block'); + target.style.setProperty('min-width', '20px', 'important'); + target.style.setProperty('min-height', '20px', 'important'); + target.style.setProperty( + 'border', + placeholders.imageBorder, + 'important' + ); + target.style.setProperty( + 'background', + placeholders.imageBackground, + 'important' + ); + break; } - target.setAttribute(src1stProps[tag], placeholders[tag]); - target.style.setProperty('display', 'inline-block'); - target.style.setProperty('min-width', '20px', 'important'); - target.style.setProperty('min-height', '20px', 'important'); - target.style.setProperty('border', placeholders.border, 'important'); - target.style.setProperty('background', placeholders.background, 'important'); } }; diff --git a/src/js/dashboard.js b/src/js/dashboard.js index 3c1bff0..9c488f2 100644 --- a/src/js/dashboard.js +++ b/src/js/dashboard.js @@ -1,7 +1,7 @@ /******************************************************************************* - µMatrix - a Chromium browser extension to black/white list requests. - Copyright (C) 2014 Raymond Hill + uMatrix - a Chromium browser extension to black/white list requests. + Copyright (C) 2014-2018 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 @@ -21,40 +21,35 @@ /* global uDom */ -/******************************************************************************/ - -(function() { - 'use strict'; /******************************************************************************/ -var loadDashboardPanel = function(hash) { - var button = uDom(hash); - var url = button.attr('data-dashboard-panel-url'); - uDom('iframe').attr('src', url); - uDom('.tabButton').forEach(function(button){ - button.toggleClass('selected', button.attr('data-dashboard-panel-url') === url); +(function() { + + var loadDashboardPanel = function(hash) { + var button = uDom(hash); + var url = button.attr('data-dashboard-panel-url'); + uDom('iframe').attr('src', url); + uDom('.tabButton').forEach(function(button){ + button.toggleClass( + 'selected', + button.attr('data-dashboard-panel-url') === url + ); + }); + }; + + var onTabClickHandler = function() { + loadDashboardPanel(window.location.hash); + }; + + uDom.onLoad(function() { + window.addEventListener('hashchange', onTabClickHandler); + var hash = window.location.hash; + if ( hash.length < 2 ) { + hash = '#settings'; + } + loadDashboardPanel(hash); }); -}; - -/******************************************************************************/ - -var onTabClickHandler = function() { - loadDashboardPanel(window.location.hash); -}; - -/******************************************************************************/ - -uDom.onLoad(function() { - window.addEventListener('hashchange', onTabClickHandler); - var hash = window.location.hash; - if ( hash.length < 2 ) { - hash = '#settings'; - } - loadDashboardPanel(hash); -}); - -/******************************************************************************/ })(); diff --git a/src/js/messaging.js b/src/js/messaging.js index 70c5ed3..c888a9b 100644 --- a/src/js/messaging.js +++ b/src/js/messaging.js @@ -1,7 +1,7 @@ /******************************************************************************* uMatrix - a Chromium browser extension to black/white list requests. - Copyright (C) 2014-2017 Raymond Hill + Copyright (C) 2014-2018 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 @@ -89,6 +89,10 @@ function onMessage(request, sender, callback) { ); break; + case 'readRawSettings': + response = µm.stringFromRawSettings(); + break; + case 'reloadHostsFiles': µm.reloadHostsFiles(); break; @@ -107,6 +111,10 @@ function onMessage(request, sender, callback) { response = µm.changeUserSettings(request.name, request.value); break; + case 'writeRawSettings': + µm.rawSettingsFromString(request.content); + break; + default: return vAPI.messaging.UNHANDLED; } @@ -447,6 +455,37 @@ var contentScriptLocalStorageHandler = function(tabId, originURL) { // Evaluate many URLs against the matrix. var lookupBlockedCollapsibles = function(tabId, requests) { + if ( placeholdersReadTime < µm.rawSettingsWriteTime ) { + placeholders = undefined; + } + + if ( placeholders === undefined ) { + placeholders = { + frame: µm.rawSettings.framePlaceholder, + image: µm.rawSettings.imagePlaceholder + }; + if ( placeholders.frame ) { + placeholders.frameDocument = + µm.rawSettings.framePlaceholderDocument.replace( + '{{bg}}', + µm.rawSettings.framePlaceholderBackground !== 'default' ? + µm.rawSettings.framePlaceholderBackground : + µm.rawSettings.placeholderBackground + ); + } + if ( placeholders.image ) { + placeholders.imageBorder = + µm.rawSettings.imagePlaceholderBorder !== 'default' ? + µm.rawSettings.imagePlaceholderBorder : + µm.rawSettings.placeholderBorder; + placeholders.imageBackground = + µm.rawSettings.imagePlaceholderBackground !== 'default' ? + µm.rawSettings.imagePlaceholderBackground : + µm.rawSettings.placeholderBackground; + } + placeholdersReadTime = Date.now(); + } + var response = { blockedResources: [], hash: requests.hash, @@ -464,35 +503,11 @@ var lookupBlockedCollapsibles = function(tabId, requests) { pageStore.lookupBlockedCollapsibles(requests, response); } - // TODO: evaluate whether the issue reported below still exists. - // https://github.com/gorhill/uMatrix/issues/205 - // If blocked, the URL must be recorded by the page store, so as to - // ensure they are properly reflected in the matrix. - - if ( response.placeholders === null ) { - placeholders = { - background: - vAPI.localStorage.getItem('placeholderBackground') || - µm.defaultLocalUserSettings.placeholderBackground, - border: - vAPI.localStorage.getItem('placeholderBorder') || - µm.defaultLocalUserSettings.placeholderBorder, - iframe: - vAPI.localStorage.getItem('placeholderDocument') || - µm.defaultLocalUserSettings.placeholderDocument, - img: - vAPI.localStorage.getItem('placeholderImage') || - µm.defaultLocalUserSettings.placeholderImage - }; - placeholders.iframe = - placeholders.iframe.replace('{{bg}}', placeholders.background); - response.placeholders = placeholders; - } - return response; }; -var placeholders = null; +var placeholders, + placeholdersReadTime = 0; /******************************************************************************/ @@ -788,7 +803,7 @@ var µm = µMatrix; /******************************************************************************/ var restoreUserData = function(userData) { - var countdown = 3; + var countdown = 4; var onCountdown = function() { countdown -= 1; if ( countdown === 0 ) { @@ -797,10 +812,12 @@ var restoreUserData = function(userData) { }; var onAllRemoved = function() { - // Be sure to adjust `countdown` if adding/removing anything below - µm.XAL.keyvalSetMany(userData.settings, onCountdown); - µm.XAL.keyvalSetOne('userMatrix', userData.rules, onCountdown); - µm.XAL.keyvalSetOne('liveHostsFiles', userData.hostsFiles, onCountdown); + vAPI.storage.set(userData.settings, onCountdown); + vAPI.storage.set({ userMatrix: userData.rules }, onCountdown); + vAPI.storage.set({ liveHostsFiles: userData.hostsFiles }, onCountdown); + if ( userData.rawSettings instanceof Object ) { + µMatrix.saveRawSettings(userData.rawSettings, onCountdown); + } }; // If we are going to restore all, might as well wipe out clean local @@ -837,7 +854,8 @@ var onMessage = function(request, sender, callback) { when: Date.now(), settings: µm.userSettings, rules: µm.pMatrix.toString(), - hostsFiles: µm.liveHostsFiles + hostsFiles: µm.liveHostsFiles, + rawSettings: µm.rawSettings }; break; diff --git a/src/js/raw-settings.js b/src/js/raw-settings.js new file mode 100644 index 0000000..ae99d57 --- /dev/null +++ b/src/js/raw-settings.js @@ -0,0 +1,115 @@ +/******************************************************************************* + + uMatrix - a browser extension to block requests. + Copyright (C) 2018 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() { + +/******************************************************************************/ + +var messaging = vAPI.messaging; +var cachedData = ''; +var rawSettingsInput = uDom.nodeFromId('rawSettings'); + +/******************************************************************************/ + +var hashFromRawSettings = function(raw) { + return raw.trim().replace(/\s+/g, '|'); +}; + +/******************************************************************************/ + +// This is to give a visual hint that the content of user blacklist has changed. + +var rawSettingsChanged = (function () { + var timer = null; + + var handler = function() { + timer = null; + var changed = + hashFromRawSettings(rawSettingsInput.value) !== cachedData; + uDom.nodeFromId('rawSettingsApply').disabled = !changed; + }; + + return function() { + if ( timer !== null ) { + clearTimeout(timer); + } + timer = vAPI.setTimeout(handler, 100); + }; +})(); + +/******************************************************************************/ + +function renderRawSettings() { + var onRead = function(raw) { + cachedData = hashFromRawSettings(raw); + var pretty = [], + whitespaces = ' ', + lines = raw.split('\n'), + max = 0, + pos, + i, n = lines.length; + for ( i = 0; i < n; i++ ) { + pos = lines[i].indexOf(' '); + if ( pos > max ) { + max = pos; + } + } + for ( i = 0; i < n; i++ ) { + pos = lines[i].indexOf(' '); + pretty.push(whitespaces.slice(0, max - pos) + lines[i]); + } + rawSettingsInput.value = pretty.join('\n') + '\n'; + rawSettingsChanged(); + rawSettingsInput.focus(); + }; + messaging.send('dashboard', { what: 'readRawSettings' }, onRead); +} + +/******************************************************************************/ + +var applyChanges = function() { + messaging.send( + 'dashboard', + { + what: 'writeRawSettings', + content: rawSettingsInput.value + }, + renderRawSettings + ); +}; + +/******************************************************************************/ + +// Handle user interaction +uDom('#rawSettings').on('input', rawSettingsChanged); +uDom('#rawSettingsApply').on('click', applyChanges); + +renderRawSettings(); + +/******************************************************************************/ + +})(); diff --git a/src/js/start.js b/src/js/start.js index 7095489..c7388b0 100644 --- a/src/js/start.js +++ b/src/js/start.js @@ -33,86 +33,6 @@ var µm = µMatrix; -/******************************************************************************* - - SVG-based icons below were extracted from - fontawesome-webfont.svg v4.7. Excerpt of copyright notice at - the top of the file: - - > Created by FontForge 20120731 at Mon Oct 24 17:37:40 2016 - > By ,,, - > Copyright Dave Gandy 2016. All rights reserved. - - Excerpt of the license information in the fontawesome CSS - file bundled with the package: - - > Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome - > License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - - Font icons: - - glyph-name: "external_link" - -*/ - -µm.defaultLocalUserSettings = { - // data-URI background courtesy of https://github.com/dev-random - // https://github.com/gorhill/uMatrix/issues/429#issuecomment-194548243 - placeholderBackground: [ - 'url("data:image/png;base64,', - 'iVBORw0KGgoAAAANSUhEUgAAAAoAAAAK', - 'CAAAAACoWZBhAAAABGdBTUEAALGPC/xh', - 'BQAAAAJiS0dEAP+Hj8y/AAAAB3RJTUUH', - '3wwIAAgyL/YaPAAAACJJREFUCFtjfMbO', - 'AAQ/gZiFnQPEBAEmGIMIJgtIL8QEgtoA', - 'In4D/96X1KAAAAAldEVYdGRhdGU6Y3Jl', - 'YXRlADIwMTUtMTItMDhUMDA6MDg6NTAr', - 'MDM6MDAasuuJAAAAJXRFWHRkYXRlOm1v', - 'ZGlmeQAyMDE1LTEyLTA4VDAwOjA4OjUw', - 'KzAzOjAwa+9TNQAAAABJRU5ErkJggg==', - '") ', - 'repeat scroll #fff' - ].join(''), - placeholderBorder: '1px solid rgba(0, 0, 0, 0.1)', - placeholderDocument: [ - '', - '', - '', - '', - '', - '{{url}}', - '' - ].join('') -}; -µm.defaultLocalUserSettings.placeholderImage = µm.defaultLocalUserSettings.placeholderBackground; - -var rwLocalUserSettings = { - placeholderBackground: true, - placeholderBorder: true, - placeholderImage: true -}; - /******************************************************************************/ var processCallbackQueue = function(queue, callback) { @@ -135,21 +55,11 @@ var onAllDone = function() { µm.assets.addObserver(µm.assetObserver.bind(µm)); µm.scheduleAssetUpdater(µm.userSettings.autoUpdate ? 7 * 60 * 1000 : 0); - for ( var key in µm.defaultLocalUserSettings ) { - if (µm. defaultLocalUserSettings.hasOwnProperty(key) === false ) { - continue; - } - if ( - vAPI.localStorage.getItem(key) === null || - rwLocalUserSettings.hasOwnProperty(key) === false - ) { - vAPI.localStorage.setItem(key, µm.defaultLocalUserSettings[key]); - } - } - vAPI.cloud.start([ 'myRulesPane' ]); }; +/******************************************************************************/ + var onTabsReady = function(tabs) { var tab; var i = tabs.length; @@ -162,12 +72,17 @@ var onTabsReady = function(tabs) { onAllDone(); }; +/******************************************************************************/ + var onUserSettingsLoaded = function() { µm.loadHostsFiles(); }; +/******************************************************************************/ + var onPSLReady = function() { µm.loadUserSettings(onUserSettingsLoaded); + µm.loadRawSettings(); µm.loadMatrix(); // rhill 2013-11-24: bind behind-the-scene virtual tab/url manually, since the @@ -179,6 +94,8 @@ var onPSLReady = function() { vAPI.tabs.getAll(onTabsReady); }; +/******************************************************************************/ + processCallbackQueue(µm.onBeforeStartQueue, function() { µm.loadPublicSuffixList(onPSLReady); }); diff --git a/src/js/storage.js b/src/js/storage.js index c8b0036..674d059 100644 --- a/src/js/storage.js +++ b/src/js/storage.js @@ -1,7 +1,7 @@ /******************************************************************************* - µMatrix - a Chromium browser extension to black/white list requests. - Copyright (C) 2014 Raymond Hill + uMatrix - a Chromium browser extension to black/white list requests. + Copyright (C) 2014-2018 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 @@ -47,8 +47,6 @@ ); }; -/******************************************************************************/ - µMatrix.loadUserSettings = function(callback) { var µm = this; @@ -69,6 +67,98 @@ /******************************************************************************/ +µMatrix.loadRawSettings = function() { + var µm = this; + + var onLoaded = function(bin) { + if ( !bin || bin.rawSettings instanceof Object === false ) { return; } + for ( var key of Object.keys(bin.rawSettings) ) { + if ( + µm.rawSettings.hasOwnProperty(key) === false || + typeof bin.rawSettings[key] !== typeof µm.rawSettings[key] + ) { + continue; + } + µm.rawSettings[key] = bin.rawSettings[key]; + } + µm.rawSettingsWriteTime = Date.now(); + }; + + vAPI.storage.get('rawSettings', onLoaded); +}; + +µMatrix.saveRawSettings = function(rawSettings, callback) { + var keys = Object.keys(rawSettings); + if ( keys.length === 0 ) { + if ( typeof callback === 'function' ) { + callback(); + } + return; + } + for ( var key of keys ) { + if ( + this.rawSettingsDefault.hasOwnProperty(key) && + typeof rawSettings[key] === typeof this.rawSettingsDefault[key] + ) { + this.rawSettings[key] = rawSettings[key]; + } + } + vAPI.storage.set({ rawSettings: this.rawSettings }, callback); + this.rawSettingsWriteTime = Date.now(); +}; + +µMatrix.rawSettingsFromString = function(raw) { + var result = {}, + lineIter = new this.LineIterator(raw), + line, matches, name, value; + while ( lineIter.eot() === false ) { + line = lineIter.next().trim(); + matches = /^(\S+)(\s+(.+))?$/.exec(line); + if ( matches === null ) { continue; } + name = matches[1]; + if ( this.rawSettingsDefault.hasOwnProperty(name) === false ) { + continue; + } + value = (matches[2] || '').trim(); + switch ( typeof this.rawSettingsDefault[name] ) { + case 'boolean': + if ( value === 'true' ) { + value = true; + } else if ( value === 'false' ) { + value = false; + } + break; + case 'string': + if ( value === '' ) { + value = this.rawSettingsDefault[name]; + } + break; + case 'number': + value = parseInt(value, 10); + if ( isNaN(value) ) { + value = this.rawSettingsDefault[name]; + } + break; + default: + break; + } + if ( this.rawSettings[name] !== value ) { + result[name] = value; + } + } + this.saveRawSettings(result); +}; + +µMatrix.stringFromRawSettings = function() { + var out = []; + for ( var key of Object.keys(this.rawSettings).sort() ) { + out.push(key + ' ' + this.rawSettings[key]); + } + return out.join('\n'); +}; + +/******************************************************************************/ + // save white/blacklist µMatrix.saveMatrix = function() { µMatrix.XAL.keyvalSetOne('userMatrix', this.pMatrix.toString()); diff --git a/src/raw-settings.html b/src/raw-settings.html new file mode 100644 index 0000000..48c60fa --- /dev/null +++ b/src/raw-settings.html @@ -0,0 +1,27 @@ + + + + + + + + + + + + +

+

+

  +

+ + + + + + + + + + +