Browse Source

integrate CodeMirror's MergeView to the 'My rules' pane

pull/2/head
Raymond Hill 7 years ago
parent
commit
d825b5562e
No known key found for this signature in database GPG Key ID: 25E1490B761470C2
  1. 4
      src/about.html
  2. 29
      src/css/codemirror.css
  3. 3
      src/css/dashboard-common.css
  4. 161
      src/css/user-rules.css
  5. 179
      src/js/matrix.js
  6. 38
      src/js/messaging.js
  7. 507
      src/js/user-rules.js
  8. 21
      src/lib/codemirror/LICENSE
  9. 35
      src/lib/codemirror/README.md
  10. 119
      src/lib/codemirror/addon/merge/merge.css
  11. 1002
      src/lib/codemirror/addon/merge/merge.js
  12. 72
      src/lib/codemirror/addon/selection/active-line.js
  13. 346
      src/lib/codemirror/lib/codemirror.css
  14. 9660
      src/lib/codemirror/lib/codemirror.js
  15. 34
      src/lib/diff/README.md
  16. 207
      src/lib/diff/swatinem_diff.js
  17. 41
      src/user-rules.html

4
src/about.html

@ -29,6 +29,10 @@ ul {
<li><span data-i18n="aboutIssueContributors"></span> <a href="https://github.com/gorhill/uMatrix/issues?q=is%3Aissue">uMatrix</a>, <a href="https://github.com/gorhill/httpswitchboard/issues?q=is%3Aissue">HTTP Switchboard</a> <li><span data-i18n="aboutIssueContributors"></span> <a href="https://github.com/gorhill/uMatrix/issues?q=is%3Aissue">uMatrix</a>, <a href="https://github.com/gorhill/httpswitchboard/issues?q=is%3Aissue">HTTP Switchboard</a>
<li><span data-i18n="aboutTranslationContributors"></span> <a href="https://github.com/gorhill/uMatrix/wiki/Translation-work-contributors">Crowdin</a> <li><span data-i18n="aboutTranslationContributors"></span> <a href="https://github.com/gorhill/uMatrix/wiki/Translation-work-contributors">Crowdin</a>
</ul> </ul>
<li><a href="https://github.com/bestiejs/punycode.js" target="_blank">Punycode.js</a> by <a href="https://github.com/mathiasbynens">Mathias Bynens</a>
<li><a href="https://fontawesome.com/" target="_blank">Font Awesome</a> by <a href="https://github.com/davegandy">Dave Gandy</a>
<li><a href="https://codemirror.net/" target="_blank">CodeMirror</a> by <a href="https://github.com/marijnh">Marijn Haverbeke</a>
<li><a href="https://github.com/Swatinem/diff" target="_blank">An implementation of Myers' diff algorithm</a> by <a href="https://github.com/Swatinem">Arpad Borsos</a>
</ul> </ul>
<h2 data-i18n="aboutUserDataHeader"></h2> <h2 data-i18n="aboutUserDataHeader"></h2>

29
src/css/codemirror.css

@ -0,0 +1,29 @@
.codeMirrorContainer {
font-size: 12px;
line-height: 1.25;
overflow: hidden;
position: relative;
}
.CodeMirror {
border: 1px solid #ddd;
box-sizing: border-box;
flex-grow: 1;
height: 100%;
width: 100%;
}
/* For when panels are used */
.codeMirrorContainer > div:not([class^="CodeMirror"]) {
display: flex;
flex-direction: column;
height: 100%;
}
.CodeMirror-merge-l-deleted {
background-image: none;
font-weight: bold;
}
.CodeMirror-merge-l-inserted {
background-image: none;
font-weight: bold;
}

3
src/css/dashboard-common.css

@ -1,8 +1,9 @@
body { body {
background-color: #fff; background-color: #fff;
box-sizing: border-box;
color: #000; color: #000;
margin: 0; margin: 0;
padding: 0 0.5em 0 0.5em;
padding: 0 0.5em 0.5em 0.5em;
font: 14px/1.4 sans-serif; font: 14px/1.4 sans-serif;
} }
body > *:first-child { body > *:first-child {

161
src/css/user-rules.css

@ -1,8 +1,6 @@
div > p:first-child {
margin-top: 0;
}
div > p:last-child {
margin-bottom: 0;
body {
height: 100vh;
overflow: hidden;
} }
#diff { #diff {
border: 0; border: 0;
@ -10,25 +8,32 @@ div > p:last-child {
padding: 0; padding: 0;
white-space: nowrap; white-space: nowrap;
} }
#diff > .pane {
#diff .tools > * {
margin-bottom: 0.5em;
}
#diff .ruleActions {
border: 0; border: 0;
box-sizing: box-border;
box-sizing: border-box;
display: inline-block; display: inline-block;
margin: 0;
padding: 0; padding: 0;
position: relative;
text-align: center;
vertical-align: top; vertical-align: top;
width: calc(50% - 2px);
width: 50%;
}
#diff .ruleActions h3 {
font-weight: normal;
margin: 0.5em 0;
} }
#diff > .pane > div {
padding: 0 0 1em 0;
#ruleFilter {
direction: ltr;
text-align: center; text-align: center;
} }
#diff > .pane > div > span {
float: left;
#ruleFilter .fa {
color: #888;
} }
body[dir="ltr"] #revertButton:after {
content: '\2009\f061';
#revertButton:after,
#commitButton:before {
font-family: FontAwesome; font-family: FontAwesome;
font-style: normal; font-style: normal;
font-weight: normal; font-weight: normal;
@ -36,126 +41,52 @@ body[dir="ltr"] #revertButton:after {
vertical-align: baseline; vertical-align: baseline;
display: inline-block; display: inline-block;
} }
body[dir="ltr"] #revertButton:after {
content: '\2009\f061';
}
body[dir="rtl"] #revertButton:after { body[dir="rtl"] #revertButton:after {
content: '\2009\f060'; content: '\2009\f060';
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
line-height: 1;
vertical-align: baseline;
display: inline-block;
} }
body[dir="ltr"] #commitButton:before { body[dir="ltr"] #commitButton:before {
content: '\f060\2009'; content: '\f060\2009';
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
line-height: 1;
vertical-align: baseline;
display: inline-block;
} }
body[dir="rtl"] #commitButton:before { body[dir="rtl"] #commitButton:before {
content: '\f061\2009'; content: '\f061\2009';
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
line-height: 1;
vertical-align: baseline;
display: inline-block;
} }
#revertButton, #revertButton,
#commitButton, #commitButton,
#diff.edit #editEnterButton {
#editSaveButton,
#diff.editing #exportButton,
#diff.editing #importButton {
opacity: 0.25; opacity: 0.25;
pointer-events: none; pointer-events: none;
} }
#editStopButton,
#editCancelButton {
display: none;
}
#diff.dirty:not(.edit) #revertButton,
#diff.dirty:not(.edit) #commitButton {
#diff.dirty:not(.editing) #revertButton,
#diff.dirty:not(.editing) #commitButton,
#diff.editing #editSaveButton {
opacity: 1; opacity: 1;
pointer-events: auto; pointer-events: auto;
} }
#diff.edit #editStopButton,
#diff.edit #editCancelButton {
display: initial;
}
#diff.edit #importButton,
#diff.edit #exportButton {
display: none;
.codeMirrorContainer {
box-sizing: border-box;
padding: 0 0 0.5em 0;
} }
#diff ul {
border: 0;
border-top: 1px solid #eee;
list-style-type: none;
margin: 0;
overflow: hidden;
padding: 1em 0 0 0;
}
#diff ul,
#diff textarea {
font: 12px/1.8 monospace;
}
#diff.edit .right ul {
visibility: hidden;
.CodeMirror-merge, .CodeMirror-merge-pane, .CodeMirror-merge .CodeMirror {
box-sizing: border-box;
height: 100%;
} }
#diff .left {
padding: 0 0 0 0;
#diff.editing .CodeMirror-merge-copy,
#diff.editing .CodeMirror-merge-copy-reverse {
display: none;
} }
#diff .right > ul {
#diff.editing .CodeMirror-merge-left .CodeMirror {
color: #888; color: #888;
} }
#diff li {
background-color: white;
direction: ltr;
padding: 0;
text-align: left;
white-space: nowrap;
}
#diff li:nth-of-type(2n+0) {
background-color: #eee;
}
#diff .right li {
cursor: pointer;
}
#diff .right li:hover {
background-color: #ffc;
color: #000;
}
#diff .right li.notLeft {
color: #000;
#diff.editing .CodeMirror-merge-editor .CodeMirror {
background-color: #ffe;
} }
#diff .right li.notLeft:hover {
text-decoration: line-through;
}
#diff .right li.notRight {
color: #000;
}
#diff .right li.toRemove {
color: #000;
text-decoration: line-through;
}
#diff textarea {
border: 0;
border-top: 1px solid #eee;
direction: ltr;
height: 100%;
body[dir="rtl"] .CodeMirror-merge-pane-rightmost {
right: unset;
left: 0; left: 0;
margin: 0;
overflow: hidden;
overflow-y: auto;
padding: 1em 0 0 0;
position: absolute;
resize: none;
visibility: hidden;
white-space: pre;
width: 100%;
}
#diff.edit textarea {
visibility: visible;
}
.hidden {
display: none;
}
}

179
src/js/matrix.js

@ -141,6 +141,18 @@ var isIPAddress = function(hostname) {
/******************************************************************************/ /******************************************************************************/
var punycodeIf = function(hn) {
return reNotASCII.test(hn) ? punycode.toASCII(hn) : hn;
};
var unpunycodeIf = function(hn) {
return hn.indexOf('xn--') !== -1 ? punycode.toUnicode(hn) : hn;
};
var reNotASCII = /[^\x20-\x7F]/;
/******************************************************************************/
var toBroaderHostname = function(hostname) { var toBroaderHostname = function(hostname) {
if ( hostname === '*' ) { return ''; } if ( hostname === '*' ) { return ''; }
if ( isIPAddress(hostname) ) { if ( isIPAddress(hostname) ) {
@ -591,6 +603,51 @@ Matrix.prototype.extractAllSourceHostnames = (function() {
/******************************************************************************/ /******************************************************************************/
// https://github.com/gorhill/uMatrix/issues/759
// Backward compatibility: 'plugin' => 'media'
Matrix.prototype.partsFromLine = function(line) {
let fields = line.split(/\s+/);
if ( fields.length < 3 ) { return; }
// Switches
if ( this.reSwitchRule.test(fields[0]) ) {
fields[0] = fields[0].slice(0, -1);
if ( switchBitOffsets.has(fields[0]) === false ) { return; }
fields[1] = punycodeIf(fields[1]);
fields[2] = nameToSwitchStateMap.get(fields[2]);
if ( fields[2] === undefined ) { return; }
fields.length = 3;
return fields;
}
// Rules
if ( fields.length < 4 ) { return; }
fields[0] = punycodeIf(fields[0]);
fields[1] = punycodeIf(fields[1]);
if ( fields[2] === 'plugin' ) { fields[2] = 'media'; }
if ( typeBitOffsets.get(fields[2]) === undefined ) { return; }
if ( nameToStateMap.hasOwnProperty(fields[3]) === false ) { return; }
fields[3] = nameToStateMap[fields[3]];
fields.length = 4;
return fields;
};
Matrix.prototype.reSwitchRule = /^[0-9a-z-]+:$/;
/******************************************************************************/
Matrix.prototype.fromArray = function(lines, append) {
let matrix = append === true ? this : new Matrix();
for ( let line of lines ) {
matrix.addFromLine(line);
}
if ( append !== true ) {
this.assign(matrix);
}
this.modifiedTime = Date.now();
};
Matrix.prototype.toArray = function() { Matrix.prototype.toArray = function() {
let out = []; let out = [];
for ( let rule of this.rules.keys() ) { for ( let rule of this.rules.keys() ) {
@ -600,8 +657,8 @@ Matrix.prototype.toArray = function() {
let val = this.evaluateCell(srcHostname, desHostname, type); let val = this.evaluateCell(srcHostname, desHostname, type);
if ( val === 0 ) { continue; } if ( val === 0 ) { continue; }
out.push( out.push(
punycode.toUnicode(srcHostname) + ' ' +
punycode.toUnicode(desHostname) + ' ' +
unpunycodeIf(srcHostname) + ' ' +
unpunycodeIf(desHostname) + ' ' +
type + ' ' + type + ' ' +
stateToNameMap.get(val) stateToNameMap.get(val)
); );
@ -623,25 +680,6 @@ Matrix.prototype.toArray = function() {
/******************************************************************************/ /******************************************************************************/
Matrix.prototype.fromArray = function(lines, append) {
let matrix = append === true ? this : new Matrix();
for ( let line of lines ) {
matrix.fromLine(line);
}
if ( append !== true ) {
this.assign(matrix);
}
this.modifiedTime = Date.now();
};
/******************************************************************************/
Matrix.prototype.toString = function() {
return this.toArray().join('\n');
};
/******************************************************************************/
Matrix.prototype.fromString = function(text, append) { Matrix.prototype.fromString = function(text, append) {
let matrix = append === true ? this : new Matrix(); let matrix = append === true ? this : new Matrix();
let textEnd = text.length; let textEnd = text.length;
@ -657,14 +695,12 @@ Matrix.prototype.fromString = function(text, append) {
} }
let line = text.slice(lineBeg, lineEnd).trim(); let line = text.slice(lineBeg, lineEnd).trim();
lineBeg = lineEnd + 1; lineBeg = lineEnd + 1;
let pos = line.indexOf('# '); let pos = line.indexOf('# ');
if ( pos !== -1 ) { if ( pos !== -1 ) {
line = line.slice(0, pos).trim(); line = line.slice(0, pos).trim();
} }
if ( line === '' ) { continue; } if ( line === '' ) { continue; }
matrix.fromLine(line);
matrix.addFromLine(line);
} }
if ( append !== true ) { if ( append !== true ) {
@ -674,77 +710,38 @@ Matrix.prototype.fromString = function(text, append) {
this.modifiedTime = Date.now(); this.modifiedTime = Date.now();
}; };
/******************************************************************************/
// https://github.com/gorhill/uMatrix/issues/759
// Backward compatibility: 'plugin' => 'media'
Matrix.prototype.toString = function() {
return this.toArray().join('\n');
};
Matrix.prototype.fromLine = function(line) {
let fields = line.split(/\s+/);
if ( fields.length < 3 ) { return false; }
let field0 = fields[0];
/******************************************************************************/
Matrix.prototype.addFromLine = function(line) {
let fields = this.partsFromLine(line);
if ( fields !== undefined ) {
// Switches // Switches
if ( this.reSwitchRule.test(field0) ) {
let switchName = field0.slice(0, -1);
let srcHostname = punycode.toASCII(fields[1]);
let state = fields[2];
if (
switchBitOffsets.has(switchName) === false ||
nameToSwitchStateMap.has(state) === false
) {
return false;
if ( fields.length === 3 ) {
return this.setSwitch(fields[0], fields[1], fields[2]);
} }
this.setSwitch(
switchName,
srcHostname,
nameToSwitchStateMap.get(state)
);
return true;
}
// Rules // Rules
if ( fields.length < 4 ) { return false; }
let srcHostname = punycode.toASCII(fields[0]);
let desHostname = punycode.toASCII(fields[1]);
let type = fields[2];
if ( type !== undefined ) {
if ( type === 'plugin' ) {
type = 'media';
} else if ( typeBitOffsets.has(type) === false ) {
return false;
if ( fields.length === 4 ) {
return this.setCell(fields[0], fields[1], fields[2], fields[3]);
} }
} else {
type = '*';
} }
};
let state = fields[3];
if ( state !== undefined ) {
if ( nameToStateMap.hasOwnProperty(state) === false ) {
return false;
Matrix.prototype.removeFromLine = function(line) {
let fields = this.partsFromLine(line);
if ( fields !== undefined ) {
// Switches
if ( fields.length === 3 ) {
return this.setSwitch(fields[0], fields[1], 0);
}
// Rules
if ( fields.length === 4 ) {
return this.setCell(fields[0], fields[1], fields[2], 0);
} }
state = nameToStateMap[state];
} else {
state = 2;
} }
this.setCell(srcHostname, desHostname, type, state);
return true;
};
Matrix.prototype.reSwitchRule = /^[0-9a-z-]+:$/;
/******************************************************************************/
Matrix.prototype.toSelfie = function() {
return {
version: selfieVersion,
switches: Array.from(this.switches),
rules: Array.from(this.rules)
};
}; };
/******************************************************************************/ /******************************************************************************/
@ -757,6 +754,14 @@ Matrix.prototype.fromSelfie = function(selfie) {
return true; return true;
}; };
Matrix.prototype.toSelfie = function() {
return {
version: selfieVersion,
switches: Array.from(this.switches),
rules: Array.from(this.rules)
};
};
/******************************************************************************/ /******************************************************************************/
Matrix.prototype.diff = function(other, srcHostname, desHostnames) { Matrix.prototype.diff = function(other, srcHostname, desHostnames) {
@ -790,9 +795,7 @@ Matrix.prototype.diff = function(other, srcHostname, desHostnames) {
} }
} }
srcHostname = toBroaderHostname(srcHostname); srcHostname = toBroaderHostname(srcHostname);
if ( srcHostname === '' ) {
break;
}
if ( srcHostname === '' ) { break; }
} }
return out; return out;
}; };

38
src/js/messaging.js

@ -677,6 +677,22 @@ vAPI.messaging.listen('cloud-ui.js', onMessage);
var µm = µMatrix; var µm = µMatrix;
var modifyRuleset = function(details) {
let ruleset = details.permanent ? µm.pMatrix : µm.tMatrix,
modifiedTime = ruleset.modifiedTime;
let toRemove = new Set(details.toRemove.trim().split(/\s*[\n\r]+\s*/));
for ( let rule of toRemove ) {
ruleset.removeFromLine(rule);
}
let toAdd = new Set(details.toAdd.trim().split(/\s*[\n\r]+\s*/));
for ( let rule of toAdd ) {
ruleset.addFromLine(rule);
}
if ( details.permanent && ruleset.modifiedTime !== modifiedTime ) {
µm.saveMatrix();
}
};
/******************************************************************************/ /******************************************************************************/
var onMessage = function(request, sender, callback) { var onMessage = function(request, sender, callback) {
@ -691,24 +707,14 @@ var onMessage = function(request, sender, callback) {
var response; var response;
switch ( request.what ) { switch ( request.what ) {
case 'getUserRules':
response = {
temporaryRules: µm.tMatrix.toString(),
permanentRules: µm.pMatrix.toString()
};
break;
case 'modifyRuleset':
modifyRuleset(request);
/* falls through */
case 'setUserRules':
if ( typeof request.temporaryRules === 'string' ) {
µm.tMatrix.fromString(request.temporaryRules);
}
if ( typeof request.permanentRules === 'string' ) {
µm.pMatrix.fromString(request.permanentRules);
µm.saveMatrix();
}
case 'getRuleset':
response = { response = {
temporaryRules: µm.tMatrix.toString(),
permanentRules: µm.pMatrix.toString()
temporaryRules: µm.tMatrix.toArray(),
permanentRules: µm.pMatrix.toArray()
}; };
break; break;

507
src/js/user-rules.js

@ -1,7 +1,7 @@
/******************************************************************************* /*******************************************************************************
uMatrix - a Chromium browser extension to block requests. uMatrix - a Chromium browser extension to block 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 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 it under the terms of the GNU General Public License as published by
@ -19,7 +19,7 @@
Home: https://github.com/gorhill/uMatrix Home: https://github.com/gorhill/uMatrix
*/ */
/* global uDom */
/* global diff_match_patch, CodeMirror, uDom */
'use strict'; 'use strict';
@ -29,11 +29,174 @@
/******************************************************************************/ /******************************************************************************/
// Switches before, rules after
// Move to dashboard-common.js if needed
(function() {
if ( document.querySelector('.vfill-available') === null ) { return; }
var timer;
var resize = function() {
timer = undefined;
let prect = document.body.getBoundingClientRect();
let child = document.querySelector('.vfill-available');
let crect = child.getBoundingClientRect();
let height = Math.max(prect.bottom - crect.top, 80);
child.style.height = height + 'px';
};
resize();
window.addEventListener('resize', function() {
if ( timer === undefined ) {
timer = vAPI.setTimeout(resize, 66);
}
});
})();
/******************************************************************************/
var mergeView = new CodeMirror.MergeView(
document.querySelector('.codeMirrorMergeContainer'),
{
allowEditingOriginals: true,
connect: 'align',
inputStyle: 'contenteditable',
lineNumbers: true,
lineWrapping: false,
origLeft: '',
revertButtons: true,
value: ''
}
);
mergeView.editor().setOption('styleActiveLine', true);
mergeView.editor().setOption('lineNumbers', false);
mergeView.leftOriginal().setOption('readOnly', 'nocursor');
var unfilteredRules = {
orig: { doc: mergeView.leftOriginal(), rules: [] },
edit: { doc: mergeView.editor(), rules: [] }
};
var cleanEditToken = 0;
var cleanEditText = '';
var differ;
/******************************************************************************/
// Borrowed from...
// https://github.com/codemirror/CodeMirror/blob/3e1bb5fff682f8f6cbfaef0e56c61d62403d4798/addon/search/search.js#L22
// ... and modified as needed.
var updateOverlay = (function() {
var reFilter;
var mode = {
token: function(stream) {
if ( reFilter !== undefined ) {
reFilter.lastIndex = stream.pos;
var match = reFilter.exec(stream.string);
if ( match !== null ) {
if ( match.index === stream.pos ) {
stream.pos += match[0].length || 1;
return 'searching';
}
stream.pos = match.index;
return;
}
}
stream.skipToEnd();
}
};
return function(filter) {
reFilter = typeof filter === 'string' && filter !== '' ?
new RegExp(filter.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi') :
undefined;
return mode;
};
})();
/******************************************************************************/
// Incrementally update text in a CodeMirror editor for best user experience:
// - Scroll position preserved
// - Minimum amount of text updated
var rulesToDoc = function(clearHistory) {
for ( var key in unfilteredRules ) {
if ( unfilteredRules.hasOwnProperty(key) === false ) { continue; }
var doc = unfilteredRules[key].doc;
var rules = filterRules(key);
if ( doc.lineCount() === 1 && doc.getValue() === '' || rules.length === 0 ) {
doc.setValue(rules.length !== 0 ? rules.join('\n') : '');
continue;
}
if ( differ === undefined ) { differ = new diff_match_patch(); }
var beforeText = doc.getValue();
var afterText = rules.join('\n');
var diffs = differ.diff_main(beforeText, afterText);
doc.startOperation();
var i = diffs.length,
iedit = beforeText.length;
while ( i-- ) {
var diff = diffs[i];
if ( diff[0] === 0 ) {
iedit -= diff[1].length;
continue;
}
var end = doc.posFromIndex(iedit);
if ( diff[0] === 1 ) {
doc.replaceRange(diff[1], end, end);
continue;
}
/* diff[0] === -1 */
iedit -= diff[1].length;
var beg = doc.posFromIndex(iedit);
doc.replaceRange('', beg, end);
}
doc.endOperation();
}
cleanEditText = mergeView.editor().getValue().trim();
cleanEditToken = mergeView.editor().changeGeneration();
if ( clearHistory ) {
mergeView.editor().clearHistory();
}
};
/******************************************************************************/
var filterRules = function(key) {
var rules = unfilteredRules[key].rules;
var filter = uDom('#ruleFilter input').val();
if ( filter !== '' ) {
rules = rules.slice();
var i = rules.length;
while ( i-- ) {
if ( rules[i].indexOf(filter) === -1 ) {
rules.splice(i, 1);
}
}
}
return rules;
};
/******************************************************************************/
var renderRules = (function() {
var firstVisit = true;
return function(details) {
unfilteredRules.orig.rules = details.permanentRules.sort(directiveSort);
unfilteredRules.edit.rules = details.temporaryRules.sort(directiveSort);
rulesToDoc(firstVisit);
if ( firstVisit ) {
firstVisit = false;
mergeView.editor().execCommand('goNextDiff');
}
onTextChanged(true);
};
})();
// Switches before, rules after
var directiveSort = function(a, b) { var directiveSort = function(a, b) {
var aIsSwitch = a.indexOf(':') !== -1;
var bIsSwitch = b.indexOf(':') !== -1;
var aIsSwitch = a.indexOf(': ') !== -1;
var bIsSwitch = b.indexOf(': ') !== -1;
if ( aIsSwitch === bIsSwitch ) { if ( aIsSwitch === bIsSwitch ) {
return a.localeCompare(b); return a.localeCompare(b);
} }
@ -42,72 +205,49 @@ var directiveSort = function(a, b) {
/******************************************************************************/ /******************************************************************************/
var processUserRules = function(response) {
var rules, rule, i;
var allRules = {};
var permanentRules = {};
var temporaryRules = {};
var onLeft, onRight;
var applyDiff = function(permanent, toAdd, toRemove) {
vAPI.messaging.send(
'user-rules.js',
{
what: 'modifyRuleset',
permanent: permanent,
toAdd: toAdd,
toRemove: toRemove
},
renderRules
);
};
rules = response.permanentRules.split(/\n+/);
i = rules.length;
while ( i-- ) {
rule = rules[i].trim();
if ( rule.length !== 0 ) {
permanentRules[rule] = allRules[rule] = true;
}
/******************************************************************************/
// CodeMirror quirk: sometimes fromStart.ch and/or toStart.ch is undefined.
// When this happens, use 0.
mergeView.options.revertChunk = function(
mv,
from, fromStart, fromEnd,
to, toStart, toEnd
) {
// https://github.com/gorhill/uBlock/issues/3611
if ( document.body.getAttribute('dir') === 'rtl' ) {
var tmp;
tmp = from; from = to; to = tmp;
tmp = fromStart; fromStart = toStart; toStart = tmp;
tmp = fromEnd; fromEnd = toEnd; toEnd = tmp;
} }
rules = response.temporaryRules.split(/\n+/);
i = rules.length;
while ( i-- ) {
rule = rules[i].trim();
if ( rule.length !== 0 ) {
temporaryRules[rule] = allRules[rule] = true;
}
}
var permanentList = document.createDocumentFragment(),
temporaryList = document.createDocumentFragment(),
li;
rules = Object.keys(allRules).sort(directiveSort);
for ( i = 0; i < rules.length; i++ ) {
rule = rules[i];
onLeft = permanentRules.hasOwnProperty(rule);
onRight = temporaryRules.hasOwnProperty(rule);
if ( onLeft && onRight ) {
li = document.createElement('li');
li.textContent = rule;
permanentList.appendChild(li);
li = document.createElement('li');
li.textContent = rule;
temporaryList.appendChild(li);
} else if ( onLeft ) {
li = document.createElement('li');
li.textContent = rule;
permanentList.appendChild(li);
li = document.createElement('li');
li.textContent = rule;
li.className = 'notRight toRemove';
temporaryList.appendChild(li);
} else if ( onRight ) {
li = document.createElement('li');
li.textContent = '\xA0';
permanentList.appendChild(li);
li = document.createElement('li');
li.textContent = rule;
li.className = 'notLeft';
temporaryList.appendChild(li);
}
}
// TODO: build incrementally.
uDom('#diff > .left > ul > li').remove();
document.querySelector('#diff > .left > ul').appendChild(permanentList);
uDom('#diff > .right > ul > li').remove();
document.querySelector('#diff > .right > ul').appendChild(temporaryList);
uDom('#diff').toggleClass('dirty', response.temporaryRules !== response.permanentRules);
if ( typeof fromStart.ch !== 'number' ) { fromStart.ch = 0; }
if ( fromEnd.ch !== 0 ) { fromEnd.line += 1; }
var toAdd = from.getRange(
{ line: fromStart.line, ch: 0 },
{ line: fromEnd.line, ch: 0 }
);
if ( typeof toStart.ch !== 'number' ) { toStart.ch = 0; }
if ( toEnd.ch !== 0 ) { toEnd.line += 1; }
var toRemove = to.getRange(
{ line: toStart.line, ch: 0 },
{ line: toEnd.line, ch: 0 }
);
applyDiff(from === mv.editor(), toAdd, toRemove);
}; };
/******************************************************************************/ /******************************************************************************/
@ -117,9 +257,7 @@ var processUserRules = function(response) {
var fromRequestPolicy = function(content) { var fromRequestPolicy = function(content) {
var matches = /\[origins-to-destinations\]([^\[]+)/.exec(content); var matches = /\[origins-to-destinations\]([^\[]+)/.exec(content);
if ( matches === null || matches.length !== 2 ) {
return;
}
if ( matches === null || matches.length !== 2 ) { return; }
return matches[1].trim() return matches[1].trim()
.replace(/\|/g, ' ') .replace(/\|/g, ' ')
.replace(/\n/g, ' * allow\n'); .replace(/\n/g, ' * allow\n');
@ -153,12 +291,8 @@ var fromNoScript = function(content) {
var directive, matches; var directive, matches;
while ( i-- ) { while ( i-- ) {
directive = directives[i].trim(); directive = directives[i].trim();
if ( directive === '' ) {
continue;
}
if ( reBad.test(directive) ) {
continue;
}
if ( directive === '' ) { continue; }
if ( reBad.test(directive) ) { continue; }
matches = reURL.exec(directive); matches = reURL.exec(directive);
if ( matches !== null ) { if ( matches !== null ) {
directive = matches[1]; directive = matches[1];
@ -185,16 +319,10 @@ var handleImportFilePicker = function() {
} }
} }
if ( this.result === '' ) { return; } if ( this.result === '' ) { return; }
var request = {
'what': 'setUserRules',
'temporaryRules': rulesFromHTML('#diff .right li') + '\n' + result
};
vAPI.messaging.send('user-rules.js', request, processUserRules);
applyDiff(false, result, '');
}; };
var file = this.files[0]; var file = this.files[0];
if ( file === undefined || file.name === '' ) {
return;
}
if ( file === undefined || file.name === '' ) { return; }
if ( file.type.indexOf('text') !== 0 && file.type !== 'application/json') { if ( file.type.indexOf('text') !== 0 && file.type !== 'application/json') {
return; return;
} }
@ -218,121 +346,174 @@ var startImportFilePicker = function() {
function exportUserRulesToFile() { function exportUserRulesToFile() {
vAPI.download({ vAPI.download({
'url': 'data:text/plain,' + encodeURIComponent(rulesFromHTML('#diff .left li') + '\n'),
'filename': uDom('[data-i18n="userRulesDefaultFileName"]').text()
url: 'data:text/plain,' + encodeURIComponent(
mergeView.leftOriginal().getValue().trim() + '\n'
),
filename: uDom('[data-i18n="userRulesDefaultFileName"]').text()
}); });
} }
/******************************************************************************/ /******************************************************************************/
var rulesFromHTML = function(selector) {
var rules = [];
var lis = uDom(selector);
var li;
for ( var i = 0; i < lis.length; i++ ) {
li = lis.at(i);
if ( li.hasClassName('toRemove') ) {
rules.push('');
} else {
rules.push(li.text());
var onFilterChanged = (function() {
var timer,
overlay = null,
last = '';
var process = function() {
timer = undefined;
if ( mergeView.editor().isClean(cleanEditToken) === false ) { return; }
var filter = uDom('#ruleFilter input').val();
if ( filter === last ) { return; }
last = filter;
if ( overlay !== null ) {
mergeView.leftOriginal().removeOverlay(overlay);
mergeView.editor().removeOverlay(overlay);
overlay = null;
} }
if ( filter !== '' ) {
overlay = updateOverlay(filter);
mergeView.leftOriginal().addOverlay(overlay);
mergeView.editor().addOverlay(overlay);
} }
return rules.join('\n');
};
/******************************************************************************/
rulesToDoc(true);
};
var revertHandler = function() {
var request = {
'what': 'setUserRules',
'temporaryRules': rulesFromHTML('#diff .left li')
return function() {
if ( timer !== undefined ) { clearTimeout(timer); }
timer = vAPI.setTimeout(process, 773);
}; };
vAPI.messaging.send('user-rules.js', request, processUserRules);
};
})();
/******************************************************************************/ /******************************************************************************/
var commitHandler = function() {
var request = {
'what': 'setUserRules',
'permanentRules': rulesFromHTML('#diff .right li')
};
vAPI.messaging.send('user-rules.js', request, processUserRules);
};
var onTextChanged = (function() {
var timer;
/******************************************************************************/
var process = function(now) {
timer = undefined;
var isClean = mergeView.editor().isClean(cleanEditToken);
var diff = document.getElementById('diff');
if (
now &&
isClean === false &&
mergeView.editor().getValue().trim() === cleanEditText
) {
cleanEditToken = mergeView.editor().changeGeneration();
isClean = true;
}
diff.classList.toggle('editing', isClean === false);
diff.classList.toggle('dirty', mergeView.leftChunks().length !== 0);
var input = document.querySelector('#ruleFilter input');
if ( isClean ) {
input.removeAttribute('disabled');
CodeMirror.commands.save = undefined;
} else {
input.setAttribute('disabled', '');
CodeMirror.commands.save = editSaveHandler;
}
};
var editStartHandler = function() {
uDom('#diff .right textarea').val(rulesFromHTML('#diff .right li'));
var parent = uDom(this).ancestors('#diff');
parent.toggleClass('edit', true);
};
return function(now) {
if ( timer !== undefined ) { clearTimeout(timer); }
timer = now ? process(now) : vAPI.setTimeout(process, 57);
};
})();
/******************************************************************************/ /******************************************************************************/
var editStopHandler = function() {
var parent = uDom(this).ancestors('#diff');
parent.toggleClass('edit', false);
var request = {
'what': 'setUserRules',
'temporaryRules': uDom('#diff .right textarea').val()
};
vAPI.messaging.send('user-rules.js', request, processUserRules);
var revertAllHandler = function() {
var toAdd = [], toRemove = [];
var left = mergeView.leftOriginal(),
edit = mergeView.editor();
for ( var chunk of mergeView.leftChunks() ) {
var addedLines = left.getRange(
{ line: chunk.origFrom, ch: 0 },
{ line: chunk.origTo, ch: 0 }
);
var removedLines = edit.getRange(
{ line: chunk.editFrom, ch: 0 },
{ line: chunk.editTo, ch: 0 }
);
toAdd.push(addedLines.trim());
toRemove.push(removedLines.trim());
}
applyDiff(false, toAdd.join('\n'), toRemove.join('\n'));
}; };
/******************************************************************************/ /******************************************************************************/
var editCancelHandler = function() {
var parent = uDom(this).ancestors('#diff');
parent.toggleClass('edit', false);
var commitAllHandler = function() {
var toAdd = [], toRemove = [];
var left = mergeView.leftOriginal(),
edit = mergeView.editor();
for ( var chunk of mergeView.leftChunks() ) {
var addedLines = edit.getRange(
{ line: chunk.editFrom, ch: 0 },
{ line: chunk.editTo, ch: 0 }
);
var removedLines = left.getRange(
{ line: chunk.origFrom, ch: 0 },
{ line: chunk.origTo, ch: 0 }
);
toAdd.push(addedLines.trim());
toRemove.push(removedLines.trim());
}
applyDiff(true, toAdd.join('\n'), toRemove.join('\n'));
}; };
/******************************************************************************/ /******************************************************************************/
var temporaryRulesToggler = function() {
var li = uDom(this);
li.toggleClass('toRemove');
var request = {
'what': 'setUserRules',
'temporaryRules': rulesFromHTML('#diff .right li')
};
vAPI.messaging.send('user-rules.js', request, processUserRules);
var editSaveHandler = function() {
var editor = mergeView.editor();
var editText = editor.getValue().trim();
if ( editText === cleanEditText ) {
onTextChanged(true);
return;
}
if ( differ === undefined ) { differ = new diff_match_patch(); }
var toAdd = [], toRemove = [];
var diffs = differ.diff_main(cleanEditText, editText);
for ( var diff of diffs ) {
if ( diff[0] === 1 ) {
toAdd.push(diff[1]);
} else if ( diff[0] === -1 ) {
toRemove.push(diff[1]);
}
}
applyDiff(false, toAdd.join(''), toRemove.join(''));
}; };
/******************************************************************************/ /******************************************************************************/
self.cloud.onPush = function() { self.cloud.onPush = function() {
return rulesFromHTML('#diff .left li');
return mergeView.leftOriginal().getValue().trim();
}; };
self.cloud.onPull = function(data, append) { self.cloud.onPull = function(data, append) {
if ( typeof data !== 'string' ) { return; } if ( typeof data !== 'string' ) { return; }
if ( append ) {
data = rulesFromHTML('#diff .right li') + '\n' + data;
}
var request = {
'what': 'setUserRules',
'temporaryRules': data
};
vAPI.messaging.send('user-rules.js', request, processUserRules);
applyDiff(
false,
data,
append ? '' : mergeView.editor().getValue().trim()
);
}; };
/******************************************************************************/ /******************************************************************************/
uDom.onLoad(function() {
// Handle user interaction
uDom('#importButton').on('click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker);
uDom('#exportButton').on('click', exportUserRulesToFile);
uDom('#revertButton').on('click', revertHandler);
uDom('#commitButton').on('click', commitHandler);
uDom('#editEnterButton').on('click', editStartHandler);
uDom('#editStopButton').on('click', editStopHandler);
uDom('#editCancelButton').on('click', editCancelHandler);
uDom('#diff > .right > ul').on('click', 'li', temporaryRulesToggler);
vAPI.messaging.send('user-rules.js', { what: 'getUserRules' }, processUserRules);
});
// Handle user interaction
uDom('#exportButton').on('click', exportUserRulesToFile);
uDom('#revertButton').on('click', revertAllHandler);
uDom('#commitButton').on('click', commitAllHandler);
uDom('#importButton').on('click', startImportFilePicker);
uDom('#importFilePicker').on('change', handleImportFilePicker);
uDom('#editSaveButton').on('click', editSaveHandler);
uDom('#ruleFilter input').on('input', onFilterChanged);
// https://groups.google.com/forum/#!topic/codemirror/UQkTrt078Vs
mergeView.editor().on('updateDiff', function() { onTextChanged(); });
vAPI.messaging.send('user-rules.js', { what: 'getRuleset' }, renderRules);
/******************************************************************************/ /******************************************************************************/

21
src/lib/codemirror/LICENSE

@ -0,0 +1,21 @@
MIT License
Copyright (C) 2017 by Marijn Haverbeke <marijnh@gmail.com> and others
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

35
src/lib/codemirror/README.md

@ -0,0 +1,35 @@
# CodeMirror
[![Build Status](https://travis-ci.org/codemirror/CodeMirror.svg)](https://travis-ci.org/codemirror/CodeMirror)
[![NPM version](https://img.shields.io/npm/v/codemirror.svg)](https://www.npmjs.org/package/codemirror)
[![Join the chat at https://gitter.im/codemirror/CodeMirror](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/codemirror/CodeMirror)
[Funding status: ![maintainer happiness](https://marijnhaverbeke.nl/fund/status_s.png?again)](https://marijnhaverbeke.nl/fund/)
CodeMirror is a versatile text editor implemented in JavaScript for
the browser. It is specialized for editing code, and comes with over
100 language modes and various addons that implement more advanced
editing functionality. Every language comes with fully-featured code
and syntax highlighting to help with reading and editing complex code.
A rich programming API and a CSS theming system are available for
customizing CodeMirror to fit your application, and extending it with
new functionality.
You can find more information (and the
[manual](http://codemirror.net/doc/manual.html)) on the [project
page](http://codemirror.net). For questions and discussion, use the
[discussion forum](https://discuss.codemirror.net/).
See
[CONTRIBUTING.md](https://github.com/codemirror/CodeMirror/blob/master/CONTRIBUTING.md)
for contributing guidelines.
The CodeMirror community aims to be welcoming to everybody. We use the
[Contributor Covenant
(1.1)](http://contributor-covenant.org/version/1/1/0/) as our code of
conduct.
### Quickstart
To build the project, make sure you have Node.js installed (at least version 6)
and then `npm install`. To run, just open `index.html` in your
browser (you don't need to run a webserver). Run the tests with `npm test`.

119
src/lib/codemirror/addon/merge/merge.css

@ -0,0 +1,119 @@
.CodeMirror-merge {
position: relative;
border: 1px solid #ddd;
white-space: pre;
}
.CodeMirror-merge, .CodeMirror-merge .CodeMirror {
height: 350px;
}
.CodeMirror-merge-2pane .CodeMirror-merge-pane { width: 47%; }
.CodeMirror-merge-2pane .CodeMirror-merge-gap { width: 6%; }
.CodeMirror-merge-3pane .CodeMirror-merge-pane { width: 31%; }
.CodeMirror-merge-3pane .CodeMirror-merge-gap { width: 3.5%; }
.CodeMirror-merge-pane {
display: inline-block;
white-space: normal;
vertical-align: top;
}
.CodeMirror-merge-pane-rightmost {
position: absolute;
right: 0px;
z-index: 1;
}
.CodeMirror-merge-gap {
z-index: 2;
display: inline-block;
height: 100%;
-moz-box-sizing: border-box;
box-sizing: border-box;
overflow: hidden;
border-left: 1px solid #ddd;
border-right: 1px solid #ddd;
position: relative;
background: #f8f8f8;
}
.CodeMirror-merge-scrolllock-wrap {
position: absolute;
bottom: 0; left: 50%;
}
.CodeMirror-merge-scrolllock {
position: relative;
left: -50%;
cursor: pointer;
color: #555;
line-height: 1;
}
.CodeMirror-merge-scrolllock:after {
content: "\21db\00a0\00a0\21da";
}
.CodeMirror-merge-scrolllock.CodeMirror-merge-scrolllock-enabled:after {
content: "\21db\21da";
}
.CodeMirror-merge-copybuttons-left, .CodeMirror-merge-copybuttons-right {
position: absolute;
left: 0; top: 0;
right: 0; bottom: 0;
line-height: 1;
}
.CodeMirror-merge-copy {
position: absolute;
cursor: pointer;
color: #44c;
z-index: 3;
}
.CodeMirror-merge-copy-reverse {
position: absolute;
cursor: pointer;
color: #44c;
}
.CodeMirror-merge-copybuttons-left .CodeMirror-merge-copy { left: 2px; }
.CodeMirror-merge-copybuttons-right .CodeMirror-merge-copy { right: 2px; }
.CodeMirror-merge-r-inserted, .CodeMirror-merge-l-inserted {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12MwuCXy3+CWyH8GBgYGJgYkAABZbAQ9ELXurwAAAABJRU5ErkJggg==);
background-position: bottom left;
background-repeat: repeat-x;
}
.CodeMirror-merge-r-deleted, .CodeMirror-merge-l-deleted {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAMAAAACCAYAAACddGYaAAAAGUlEQVQI12M4Kyb2/6yY2H8GBgYGJgYkAABURgPz6Ks7wQAAAABJRU5ErkJggg==);
background-position: bottom left;
background-repeat: repeat-x;
}
.CodeMirror-merge-r-chunk { background: #ffffe0; }
.CodeMirror-merge-r-chunk-start { border-top: 1px solid #ee8; }
.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #ee8; }
.CodeMirror-merge-r-connect { fill: #ffffe0; stroke: #ee8; stroke-width: 1px; }
.CodeMirror-merge-l-chunk { background: #eef; }
.CodeMirror-merge-l-chunk-start { border-top: 1px solid #88e; }
.CodeMirror-merge-l-chunk-end { border-bottom: 1px solid #88e; }
.CodeMirror-merge-l-connect { fill: #eef; stroke: #88e; stroke-width: 1px; }
.CodeMirror-merge-l-chunk.CodeMirror-merge-r-chunk { background: #dfd; }
.CodeMirror-merge-l-chunk-start.CodeMirror-merge-r-chunk-start { border-top: 1px solid #4e4; }
.CodeMirror-merge-l-chunk-end.CodeMirror-merge-r-chunk-end { border-bottom: 1px solid #4e4; }
.CodeMirror-merge-collapsed-widget:before {
content: "(...)";
}
.CodeMirror-merge-collapsed-widget {
cursor: pointer;
color: #88b;
background: #eef;
border: 1px solid #ddf;
font-size: 90%;
padding: 0 3px;
border-radius: 4px;
}
.CodeMirror-merge-collapsed-line .CodeMirror-gutter-elt { display: none; }

1002
src/lib/codemirror/addon/merge/merge.js
File diff suppressed because it is too large
View File

72
src/lib/codemirror/addon/selection/active-line.js

@ -0,0 +1,72 @@
// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: http://codemirror.net/LICENSE
(function(mod) {
if (typeof exports == "object" && typeof module == "object") // CommonJS
mod(require("../../lib/codemirror"));
else if (typeof define == "function" && define.amd) // AMD
define(["../../lib/codemirror"], mod);
else // Plain browser env
mod(CodeMirror);
})(function(CodeMirror) {
"use strict";
var WRAP_CLASS = "CodeMirror-activeline";
var BACK_CLASS = "CodeMirror-activeline-background";
var GUTT_CLASS = "CodeMirror-activeline-gutter";
CodeMirror.defineOption("styleActiveLine", false, function(cm, val, old) {
var prev = old == CodeMirror.Init ? false : old;
if (val == prev) return
if (prev) {
cm.off("beforeSelectionChange", selectionChange);
clearActiveLines(cm);
delete cm.state.activeLines;
}
if (val) {
cm.state.activeLines = [];
updateActiveLines(cm, cm.listSelections());
cm.on("beforeSelectionChange", selectionChange);
}
});
function clearActiveLines(cm) {
for (var i = 0; i < cm.state.activeLines.length; i++) {
cm.removeLineClass(cm.state.activeLines[i], "wrap", WRAP_CLASS);
cm.removeLineClass(cm.state.activeLines[i], "background", BACK_CLASS);
cm.removeLineClass(cm.state.activeLines[i], "gutter", GUTT_CLASS);
}
}
function sameArray(a, b) {
if (a.length != b.length) return false;
for (var i = 0; i < a.length; i++)
if (a[i] != b[i]) return false;
return true;
}
function updateActiveLines(cm, ranges) {
var active = [];
for (var i = 0; i < ranges.length; i++) {
var range = ranges[i];
var option = cm.getOption("styleActiveLine");
if (typeof option == "object" && option.nonEmpty ? range.anchor.line != range.head.line : !range.empty())
continue
var line = cm.getLineHandleVisualStart(range.head.line);
if (active[active.length - 1] != line) active.push(line);
}
if (sameArray(cm.state.activeLines, active)) return;
cm.operation(function() {
clearActiveLines(cm);
for (var i = 0; i < active.length; i++) {
cm.addLineClass(active[i], "wrap", WRAP_CLASS);
cm.addLineClass(active[i], "background", BACK_CLASS);
cm.addLineClass(active[i], "gutter", GUTT_CLASS);
}
cm.state.activeLines = active;
});
}
function selectionChange(cm, sel) {
updateActiveLines(cm, sel.ranges);
}
});

346
src/lib/codemirror/lib/codemirror.css

@ -0,0 +1,346 @@
/* BASICS */
.CodeMirror {
/* Set height, width, borders, and global font properties here */
font-family: monospace;
height: 300px;
color: black;
direction: ltr;
}
/* PADDING */
.CodeMirror-lines {
padding: 4px 0; /* Vertical padding around content */
}
.CodeMirror pre {
padding: 0 4px; /* Horizontal padding of content */
}
.CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
background-color: white; /* The little square between H and V scrollbars */
}
/* GUTTER */
.CodeMirror-gutters {
border-right: 1px solid #ddd;
background-color: #f7f7f7;
white-space: nowrap;
}
.CodeMirror-linenumbers {}
.CodeMirror-linenumber {
padding: 0 3px 0 5px;
min-width: 20px;
text-align: right;
color: #999;
white-space: nowrap;
}
.CodeMirror-guttermarker { color: black; }
.CodeMirror-guttermarker-subtle { color: #999; }
/* CURSOR */
.CodeMirror-cursor {
border-left: 1px solid black;
border-right: none;
width: 0;
}
/* Shown when moving in bi-directional text */
.CodeMirror div.CodeMirror-secondarycursor {
border-left: 1px solid silver;
}
.cm-fat-cursor .CodeMirror-cursor {
width: auto;
border: 0 !important;
background: #7e7;
}
.cm-fat-cursor div.CodeMirror-cursors {
z-index: 1;
}
.cm-fat-cursor-mark {
background-color: rgba(20, 255, 20, 0.5);
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
}
.cm-animate-fat-cursor {
width: auto;
border: 0;
-webkit-animation: blink 1.06s steps(1) infinite;
-moz-animation: blink 1.06s steps(1) infinite;
animation: blink 1.06s steps(1) infinite;
background-color: #7e7;
}
@-moz-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@-webkit-keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
@keyframes blink {
0% {}
50% { background-color: transparent; }
100% {}
}
/* Can style cursor different in overwrite (non-insert) mode */
.CodeMirror-overwrite .CodeMirror-cursor {}
.cm-tab { display: inline-block; text-decoration: inherit; }
.CodeMirror-rulers {
position: absolute;
left: 0; right: 0; top: -50px; bottom: -20px;
overflow: hidden;
}
.CodeMirror-ruler {
border-left: 1px solid #ccc;
top: 0; bottom: 0;
position: absolute;
}
/* DEFAULT THEME */
.cm-s-default .cm-header {color: blue;}
.cm-s-default .cm-quote {color: #090;}
.cm-negative {color: #d44;}
.cm-positive {color: #292;}
.cm-header, .cm-strong {font-weight: bold;}
.cm-em {font-style: italic;}
.cm-link {text-decoration: underline;}
.cm-strikethrough {text-decoration: line-through;}
.cm-s-default .cm-keyword {color: #708;}
.cm-s-default .cm-atom {color: #219;}
.cm-s-default .cm-number {color: #164;}
.cm-s-default .cm-def {color: #00f;}
.cm-s-default .cm-variable,
.cm-s-default .cm-punctuation,
.cm-s-default .cm-property,
.cm-s-default .cm-operator {}
.cm-s-default .cm-variable-2 {color: #05a;}
.cm-s-default .cm-variable-3, .cm-s-default .cm-type {color: #085;}
.cm-s-default .cm-comment {color: #a50;}
.cm-s-default .cm-string {color: #a11;}
.cm-s-default .cm-string-2 {color: #f50;}
.cm-s-default .cm-meta {color: #555;}
.cm-s-default .cm-qualifier {color: #555;}
.cm-s-default .cm-builtin {color: #30a;}
.cm-s-default .cm-bracket {color: #997;}
.cm-s-default .cm-tag {color: #170;}
.cm-s-default .cm-attribute {color: #00c;}
.cm-s-default .cm-hr {color: #999;}
.cm-s-default .cm-link {color: #00c;}
.cm-s-default .cm-error {color: #f00;}
.cm-invalidchar {color: #f00;}
.CodeMirror-composing { border-bottom: 2px solid; }
/* Default styles for common addons */
div.CodeMirror span.CodeMirror-matchingbracket {color: #0b0;}
div.CodeMirror span.CodeMirror-nonmatchingbracket {color: #a22;}
.CodeMirror-matchingtag { background: rgba(255, 150, 0, .3); }
.CodeMirror-activeline-background {background: #e8f2ff;}
/* STOP */
/* The rest of this file contains styles related to the mechanics of
the editor. You probably shouldn't touch them. */
.CodeMirror {
position: relative;
overflow: hidden;
background: white;
}
.CodeMirror-scroll {
overflow: scroll !important; /* Things will break if this is overridden */
/* 30px is the magic margin used to hide the element's real scrollbars */
/* See overflow: hidden in .CodeMirror */
margin-bottom: -30px; margin-right: -30px;
padding-bottom: 30px;
height: 100%;
outline: none; /* Prevent dragging from highlighting the element */
position: relative;
}
.CodeMirror-sizer {
position: relative;
border-right: 30px solid transparent;
}
/* The fake, visible scrollbars. Used to force redraw during scrolling
before actual scrolling happens, thus preventing shaking and
flickering artifacts. */
.CodeMirror-vscrollbar, .CodeMirror-hscrollbar, .CodeMirror-scrollbar-filler, .CodeMirror-gutter-filler {
position: absolute;
z-index: 6;
display: none;
}
.CodeMirror-vscrollbar {
right: 0; top: 0;
overflow-x: hidden;
overflow-y: scroll;
}
.CodeMirror-hscrollbar {
bottom: 0; left: 0;
overflow-y: hidden;
overflow-x: scroll;
}
.CodeMirror-scrollbar-filler {
right: 0; bottom: 0;
}
.CodeMirror-gutter-filler {
left: 0; bottom: 0;
}
.CodeMirror-gutters {
position: absolute; left: 0; top: 0;
min-height: 100%;
z-index: 3;
}
.CodeMirror-gutter {
white-space: normal;
height: 100%;
display: inline-block;
vertical-align: top;
margin-bottom: -30px;
}
.CodeMirror-gutter-wrapper {
position: absolute;
z-index: 4;
background: none !important;
border: none !important;
}
.CodeMirror-gutter-background {
position: absolute;
top: 0; bottom: 0;
z-index: 4;
}
.CodeMirror-gutter-elt {
position: absolute;
cursor: default;
z-index: 4;
}
.CodeMirror-gutter-wrapper ::selection { background-color: transparent }
.CodeMirror-gutter-wrapper ::-moz-selection { background-color: transparent }
.CodeMirror-lines {
cursor: text;
min-height: 1px; /* prevents collapsing before first draw */
}
.CodeMirror pre {
/* Reset some styles that the rest of the page might have set */
-moz-border-radius: 0; -webkit-border-radius: 0; border-radius: 0;
border-width: 0;
background: transparent;
font-family: inherit;
font-size: inherit;
margin: 0;
white-space: pre;
word-wrap: normal;
line-height: inherit;
color: inherit;
z-index: 2;
position: relative;
overflow: visible;
-webkit-tap-highlight-color: transparent;
-webkit-font-variant-ligatures: contextual;
font-variant-ligatures: contextual;
}
.CodeMirror-wrap pre {
word-wrap: break-word;
white-space: pre-wrap;
word-break: normal;
}
.CodeMirror-linebackground {
position: absolute;
left: 0; right: 0; top: 0; bottom: 0;
z-index: 0;
}
.CodeMirror-linewidget {
position: relative;
z-index: 2;
padding: 0.1px; /* Force widget margins to stay inside of the container */
}
.CodeMirror-widget {}
.CodeMirror-rtl pre { direction: rtl; }
.CodeMirror-code {
outline: none;
}
/* Force content-box sizing for the elements where we expect it */
.CodeMirror-scroll,
.CodeMirror-sizer,
.CodeMirror-gutter,
.CodeMirror-gutters,
.CodeMirror-linenumber {
-moz-box-sizing: content-box;
box-sizing: content-box;
}
.CodeMirror-measure {
position: absolute;
width: 100%;
height: 0;
overflow: hidden;
visibility: hidden;
}
.CodeMirror-cursor {
position: absolute;
pointer-events: none;
}
.CodeMirror-measure pre { position: static; }
div.CodeMirror-cursors {
visibility: hidden;
position: relative;
z-index: 3;
}
div.CodeMirror-dragcursors {
visibility: visible;
}
.CodeMirror-focused div.CodeMirror-cursors {
visibility: visible;
}
.CodeMirror-selected { background: #d9d9d9; }
.CodeMirror-focused .CodeMirror-selected { background: #d7d4f0; }
.CodeMirror-crosshair { cursor: crosshair; }
.CodeMirror-line::selection, .CodeMirror-line > span::selection, .CodeMirror-line > span > span::selection { background: #d7d4f0; }
.CodeMirror-line::-moz-selection, .CodeMirror-line > span::-moz-selection, .CodeMirror-line > span > span::-moz-selection { background: #d7d4f0; }
.cm-searching {
background-color: #ffa;
background-color: rgba(255, 255, 0, .4);
}
/* Used to force a border model for a node */
.cm-force-border { padding-right: .1px; }
@media print {
/* Hide the cursor when printing */
.CodeMirror div.CodeMirror-cursors {
visibility: hidden;
}
}
/* See issue #2901 */
.cm-tab-wrap-hack:after { content: ''; }
/* Help users use markselection to safely style text background */
span.CodeMirror-selectedtext { background: none; }

9660
src/lib/codemirror/lib/codemirror.js
File diff suppressed because it is too large
View File

34
src/lib/diff/README.md

@ -0,0 +1,34 @@
# diff
implementation of myers diff algorithm
[![Build Status](https://travis-ci.org/Swatinem/diff.png?branch=master)](https://travis-ci.org/Swatinem/diff)
[![Coverage Status](https://coveralls.io/repos/Swatinem/diff/badge.png?branch=master)](https://coveralls.io/r/Swatinem/diff)
[![Dependency Status](https://gemnasium.com/Swatinem/diff.png)](https://gemnasium.com/Swatinem/diff)
This uses the [*An O(ND) Difference Algorithm and Its Variations*](http://www.xmailserver.org/diff2.pdf)
Also see http://simplygenius.net/Article/DiffTutorial2 and
http://www.mathertel.de/Diff/ViewSrc.aspx for more inspiration
## Installation
$ npm install diff
$ component install Swatinem/diff
## Usage
### diff(a, b, [eql(a, b)])
Given two arrays (or array-likes, such as strings) `a` and `b` and an optional
equal function `eql`, this will return an array with the following operations:
* *nop* the element is in both arrays
* *ins* the element is only in array `b` and will be inserted
* *del* the element in only in array `a` and will be removed
* *rep* the element from `a` will be replaced by the element from `b`.
This is essentially the same as a del+ins
## License
LGPLv3

207
src/lib/diff/swatinem_diff.js

@ -0,0 +1,207 @@
/*******************************************************************************
Key portions of code below was borrowed from:
https://github.com/Swatinem/diff
License is LGPL3 (thanks!) as per:
https://github.com/Swatinem/diff/blob/b58391504759/README.md
I chose to pick this implementation over
https://github.com/google/diff-match-patch as suggested by CodeMirror
because:
- Code is clean and simple to read -- useful when unfamiliar with the diff
algorithm, this makes changing the code easier if/when needed.
- Smaller -- diff_match_patch comes with an extended API most of which is
of no use to the current project.
- diff_match_patch uncompressed: 74.7 KB
- Swatinem's diff uncompressed: 3.66 KB
- I can easily adapt Swatinem's diff to deal with arrays of strings, which
is best suited for the current project -- it natively work with arrays.
I removed portions of code which are of no use for the current project.
I modified the diff script generator (Diff.prototype.editscript) since I
need to generate a script which is compatible with the output of the
diff_match_patch, as expected by CodeMirror.
**/
'use strict';
(function(context) {
// CodeMirror expect these globals:
context.DIFF_INSERT = 1;
context.DIFF_DELETE = -1;
context.DIFF_EQUAL = 0;
context.diff_match_patch = function(){};
context.diff_match_patch.prototype.diff_main = function(a, b) {
if ( a === b ) { return [ [ 0, a ] ]; }
var aa = a.match(/\n|[^\n]+\n?/g) || [];
var bb = b.match(/\n|[^\n]+\n?/g) || [];
var d = new Diff(aa, bb, eqlDefault);
return d.editscript();
};
function eqlDefault(a, b) { return a === b; }
function Diff(a, b, eql) {
this.a = a;
this.b = b;
this.eql = eql;
this.moda = Array.apply(null, new Array(a.length)).map(true.valueOf, false);
this.modb = Array.apply(null, new Array(b.length)).map(true.valueOf, false);
// just to save some allocations:
this.down = {};
this.up = {};
this.lcs(0, a.length, 0, b.length);
}
Diff.prototype.editscript = function Diff_editscript() {
var moda = this.moda, modb = this.modb;
var astart = 0, aend = moda.length;
var bstart = 0, bend = modb.length;
var result = [];
while (astart < aend || bstart < bend) {
if (astart < aend && bstart < bend) {
if (!moda[astart] && !modb[bstart]) {
result.push([ 0, this.a[astart] ]);
astart++; bstart++;
continue;
} else if (moda[astart] && modb[bstart]) {
result.push([ -1, this.a[astart] ]);
result.push([ 1, this.b[bstart] ]);
astart++; bstart++;
continue;
}
}
if (astart < aend && (bstart >= bend || moda[astart])) {
result.push([ -1, this.a[astart] ]);
astart++;
}
if (bstart < bend && (astart >= aend || modb[bstart])) {
result.push([ 1, this.b[bstart] ]);
bstart++;
}
}
return result;
};
Diff.prototype.lcs = function Diff_lcs(astart, aend, bstart, bend) {
var a = this.a, b = this.b, eql = this.eql;
// separate common head
while (astart < aend && bstart < bend && eql(a[astart], b[bstart])) {
astart++; bstart++;
}
// separate common tail
while (astart < aend && bstart < bend && eql(a[aend - 1], b[bend - 1])) {
aend--; bend--;
}
if (astart === aend) {
// only insertions
while (bstart < bend) {
this.modb[bstart] = true;
bstart++;
}
} else if (bend === bstart) {
// only deletions
while (astart < aend) {
this.moda[astart] = true;
astart++;
}
} else {
var snake = this.snake(astart, aend, bstart, bend);
this.lcs(astart, snake.x, bstart, snake.y);
this.lcs(snake.u, aend, snake.v, bend);
}
};
Diff.prototype.snake = function Diff_snake(astart, aend, bstart, bend) {
var a = this.a, b = this.b, eql = this.eql;
var N = aend - astart,
M = bend - bstart;
var kdown = astart - bstart;
var kup = aend - bend;
var delta = N - M;
var deltaOdd = delta & 1;
var down = this.down;
down[kdown + 1] = astart;
var up = this.up;
up[kup - 1] = aend;
var Dmax = (N + M + 1) / 2;
for (var D = 0; D <= Dmax; D++) {
var k, x, y;
// forward path
for (k = kdown - D; k <= kdown + D; k += 2) {
if (k === kdown - D) {
x = down[k + 1]; // down
} else {
x = down[k - 1] + 1; // right
if ((k < kdown + D) && (down[k + 1] >= x)) {
x = down[k + 1]; // down
}
}
y = x - k;
while (x < aend && y < bend && eql(a[x], b[y])) {
x++; y++; // diagonal
}
down[k] = x;
if (deltaOdd && (kup - D < k) && (k < kup + D) &&
up[k] <= down[k]) {
return {
x: down[k],
y: down[k] - k,
u: up[k],
v: up[k] - k,
};
}
}
// reverse path
for (k = kup - D; k <= kup + D; k += 2) {
if (k === kup + D) {
x = up[k - 1]; // up
} else {
x = up[k + 1] - 1; // left
if ((k > kup - D) && (up[k - 1] < x)) {
x = up[k - 1]; // up
}
}
y = x - k;
while (x > astart && y > bstart && eql(a[x - 1], b[y - 1])) {
x--; y--; // diagonal
}
up[k] = x;
if (!deltaOdd && (kdown - D <= k) && (k <= kdown + D) &&
up[k] <= down[k]) {
return {
x: down[k],
y: down[k] - k,
u: up[k],
v: up[k] - k,
};
}
}
}
};
return Diff;
})(self);

41
src/user-rules.html

@ -1,12 +1,17 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>uMatrix — Your rules</title> <title>uMatrix — Your rules</title>
<link rel="stylesheet" type="text/css" href="lib/codemirror/lib/codemirror.css">
<link rel="stylesheet" type="text/css" href="lib/codemirror/addon/merge/merge.css">
<link rel="stylesheet" type="text/css" href="css/common.css"> <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/dashboard-common.css">
<link rel="stylesheet" type="text/css" href="css/cloud-ui.css"> <link rel="stylesheet" type="text/css" href="css/cloud-ui.css">
<link rel="stylesheet" type="text/css" href="css/user-rules.css"> <link rel="stylesheet" type="text/css" href="css/user-rules.css">
<link rel="stylesheet" type="text/css" href="css/codemirror.css">
</head> </head>
<body> <body>
@ -15,30 +20,32 @@
<!-- <p data-i18n="userRulesFormatHint"></p> --> <!-- <p data-i18n="userRulesFormatHint"></p> -->
<div id="diff"> <div id="diff">
<div class="pane left">
<div>
<h2 data-i18n="userRulesPermanentHeader"></h2>
<div class="tools">
<div class="ruleActions">
<h3 data-i18n="userRulesPermanentHeader"></h3>
<button type="button" id="exportButton" data-i18n="userRulesExport"></button> <button type="button" id="exportButton" data-i18n="userRulesExport"></button>
<button type="button" id="revertButton" data-i18n="userRulesRevert"></button> <button type="button" id="revertButton" data-i18n="userRulesRevert"></button>
</div> </div>
<ul></ul>
</div>
<div class="pane right">
<div>
<h2 data-i18n="userRulesTemporaryHeader"></h2>
<div class="ruleActions">
<h3 data-i18n="userRulesTemporaryHeader"></h3>
<button type="button" id="commitButton" data-i18n="userRulesCommit"></button> <button type="button" id="commitButton" data-i18n="userRulesCommit"></button>
<button type="button" id="editEnterButton" data-i18n="userRulesEdit"></button>
<button type="button" id="editStopButton" data-i18n="userRulesEditSave"></button>
<button type="button" id="editCancelButton" data-i18n="userRulesEditDicard"></button>
<button type="button" id="importButton" data-i18n="userRulesImport"></button> <button type="button" id="importButton" data-i18n="userRulesImport"></button>
<button type="button" id="editSaveButton" data-i18n="userRulesEditSave"></button>
</div> </div>
<textarea spellcheck="false"></textarea>
<ul></ul>
</div>
<div id="ruleFilter"><span class="fa">&#xf0b0;</span>&ensp;<input type="text" size="32"></div>
</div> </div>
<div class="codeMirrorContainer codeMirrorMergeContainer vfill-available"></div>
</div>
<div style="display: none;">
<input id="importFilePicker" type="file" accept="text/plain">
<span data-i18n="userRulesDefaultFileName"></span>
</div>
<input class="hidden" id="importFilePicker" type="file" accept="text/plain">
<span class="hidden" data-i18n="userRulesDefaultFileName"></span>
<script src="lib/diff/swatinem_diff.js"></script>
<script src="lib/codemirror/lib/codemirror.js"></script>
<script src="lib/codemirror/addon/merge/merge.js"></script>
<script src="lib/codemirror/addon/selection/active-line.js"></script>
<script src="js/vapi-common.js"></script> <script src="js/vapi-common.js"></script>
<script src="js/vapi-client.js"></script> <script src="js/vapi-client.js"></script>

Loading…
Cancel
Save