Flags profile conditions (#1647)

* Generalize modifier keys

* Optimize bindings

* Add support for flags

* Add clipboard flag

* Update tests

* Add tests
This commit is contained in:
toasted-nutbread 2021-05-01 15:54:31 -04:00 committed by GitHub
parent 8bf6ff92f9
commit c514bbc4fb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 366 additions and 31 deletions

View File

@ -57,6 +57,17 @@ class ProfileConditionsUtil {
['notInclude', this._createSchemaModifierKeysNotInclude.bind(this)] ['notInclude', this._createSchemaModifierKeysNotInclude.bind(this)]
]) ])
} }
],
[
'flags',
{
operators: new Map([
['are', this._createSchemaFlagsAre.bind(this)],
['areNot', this._createSchemaFlagsAreNot.bind(this)],
['include', this._createSchemaFlagsInclude.bind(this)],
['notInclude', this._createSchemaFlagsNotInclude.bind(this)]
])
}
] ]
]); ]);
} }
@ -121,6 +132,10 @@ class ProfileConditionsUtil {
// NOP // NOP
} }
} }
const {flags} = normalizedContext;
if (!Array.isArray(flags)) {
normalizedContext.flags = [];
}
return normalizedContext; return normalizedContext;
} }
@ -222,54 +237,76 @@ class ProfileConditionsUtil {
// modifierKeys schema creation functions // modifierKeys schema creation functions
_createSchemaModifierKeysAre(value) { _createSchemaModifierKeysAre(value) {
return this._createSchemaModifierKeysGeneric(value, true, false); return this._createSchemaArrayCheck('modifierKeys', value, true, false);
} }
_createSchemaModifierKeysAreNot(value) { _createSchemaModifierKeysAreNot(value) {
return { return {
not: [this._createSchemaModifierKeysGeneric(value, true, false)] not: [this._createSchemaArrayCheck('modifierKeys', value, true, false)]
}; };
} }
_createSchemaModifierKeysInclude(value) { _createSchemaModifierKeysInclude(value) {
return this._createSchemaModifierKeysGeneric(value, false, false); return this._createSchemaArrayCheck('modifierKeys', value, false, false);
} }
_createSchemaModifierKeysNotInclude(value) { _createSchemaModifierKeysNotInclude(value) {
return this._createSchemaModifierKeysGeneric(value, false, true); return this._createSchemaArrayCheck('modifierKeys', value, false, true);
} }
_createSchemaModifierKeysGeneric(value, exact, none) { // modifierKeys schema creation functions
_createSchemaFlagsAre(value) {
return this._createSchemaArrayCheck('flags', value, true, false);
}
_createSchemaFlagsAreNot(value) {
return {
not: [this._createSchemaArrayCheck('flags', value, true, false)]
};
}
_createSchemaFlagsInclude(value) {
return this._createSchemaArrayCheck('flags', value, false, false);
}
_createSchemaFlagsNotInclude(value) {
return this._createSchemaArrayCheck('flags', value, false, true);
}
// Generic
_createSchemaArrayCheck(key, value, exact, none) {
const containsList = []; const containsList = [];
for (const modifierKey of this._split(value)) { for (const item of this._split(value)) {
if (modifierKey.length === 0) { continue; } if (item.length === 0) { continue; }
containsList.push({ containsList.push({
contains: { contains: {
const: modifierKey const: item
} }
}); });
} }
const containsListCount = containsList.length; const containsListCount = containsList.length;
const modifierKeysSchema = { const schema = {
type: 'array' type: 'array'
}; };
if (exact) { if (exact) {
modifierKeysSchema.maxItems = containsListCount; schema.maxItems = containsListCount;
} }
if (none) { if (none) {
if (containsListCount > 0) { if (containsListCount > 0) {
modifierKeysSchema.not = containsList; schema.not = containsList;
} }
} else { } else {
modifierKeysSchema.minItems = containsListCount; schema.minItems = containsListCount;
if (containsListCount > 0) { if (containsListCount > 0) {
modifierKeysSchema.allOf = containsList; schema.allOf = containsList;
} }
} }
return { return {
required: ['modifierKeys'], required: [key],
properties: { properties: {
modifierKeys: modifierKeysSchema [key]: schema
} }
}; };
} }

View File

@ -179,12 +179,12 @@ class SearchDisplayController {
e.preventDefault(); e.preventDefault();
e.stopImmediatePropagation(); e.stopImmediatePropagation();
this._display.blurElement(e.currentTarget); this._display.blurElement(e.currentTarget);
this._search(true, true, true); this._search(true, true, true, null);
} }
_onSearch(e) { _onSearch(e) {
e.preventDefault(); e.preventDefault();
this._search(true, true, true); this._search(true, true, true, null);
} }
_onCopy() { _onCopy() {
@ -199,7 +199,7 @@ class SearchDisplayController {
} }
this._queryInput.value = text; this._queryInput.value = text;
this._updateSearchHeight(true); this._updateSearchHeight(true);
this._search(animate, false, autoSearchContent); this._search(animate, false, autoSearchContent, ['clipboard']);
} }
_onWanakanaEnableChange(e) { _onWanakanaEnableChange(e) {
@ -342,11 +342,15 @@ class SearchDisplayController {
}); });
} }
_search(animate, history, lookup) { _search(animate, history, lookup, flags) {
const query = this._queryInput.value; const query = this._queryInput.value;
const depth = this._display.depth; const depth = this._display.depth;
const url = window.location.href; const url = window.location.href;
const documentTitle = document.title; const documentTitle = document.title;
const optionsContext = {depth, url};
if (flags !== null) {
optionsContext.flags = flags;
}
const details = { const details = {
focus: false, focus: false,
history, history,
@ -355,7 +359,7 @@ class SearchDisplayController {
}, },
state: { state: {
focusEntry: 0, focusEntry: 0,
optionsContext: {depth, url}, optionsContext,
url, url,
sentence: {text: query, offset: 0}, sentence: {text: query, offset: 0},
documentTitle documentTitle

View File

@ -30,6 +30,10 @@ class ProfileConditionsUI extends EventDispatcher {
this._eventListeners = new EventListenerCollection(); this._eventListeners = new EventListenerCollection();
this._defaultType = 'popupLevel'; this._defaultType = 'popupLevel';
this._profileIndex = 0; this._profileIndex = 0;
const validateInteger = this._validateInteger.bind(this);
const normalizeInteger = this._normalizeInteger.bind(this);
const validateFlags = this._validateFlags.bind(this);
const normalizeFlags = this._normalizeFlags.bind(this);
this._descriptors = new Map([ this._descriptors = new Map([
[ [
'popupLevel', 'popupLevel',
@ -37,12 +41,12 @@ class ProfileConditionsUI extends EventDispatcher {
displayName: 'Popup Level', displayName: 'Popup Level',
defaultOperator: 'equal', defaultOperator: 'equal',
operators: new Map([ operators: new Map([
['equal', {displayName: '=', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}], ['equal', {displayName: '=', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
['notEqual', {displayName: '\u2260', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}], ['notEqual', {displayName: '\u2260', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
['lessThan', {displayName: '<', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}], ['lessThan', {displayName: '<', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
['greaterThan', {displayName: '>', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}], ['greaterThan', {displayName: '>', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
['lessThanOrEqual', {displayName: '\u2264', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}], ['lessThanOrEqual', {displayName: '\u2264', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}],
['greaterThanOrEqual', {displayName: '\u2265', type: 'integer', defaultValue: '0', validate: this._validateInteger.bind(this), normalize: this._normalizeInteger.bind(this)}] ['greaterThanOrEqual', {displayName: '\u2265', type: 'integer', defaultValue: '0', validate: validateInteger, normalize: normalizeInteger}]
]) ])
} }
], ],
@ -69,8 +73,24 @@ class ProfileConditionsUI extends EventDispatcher {
['notInclude', {displayName: 'Don\'t Include', type: 'modifierKeys', defaultValue: ''}] ['notInclude', {displayName: 'Don\'t Include', type: 'modifierKeys', defaultValue: ''}]
]) ])
} }
],
[
'flags',
{
displayName: 'Flags',
defaultOperator: 'are',
operators: new Map([
['are', {displayName: 'Are', type: 'string', defaultValue: '', validate: validateFlags, normalize: normalizeFlags}],
['areNot', {displayName: 'Are Not', type: 'string', defaultValue: '', validate: validateFlags, normalize: normalizeFlags}],
['include', {displayName: 'Include', type: 'string', defaultValue: '', validate: validateFlags, normalize: normalizeFlags}],
['notInclude', {displayName: 'Don\'t Include', type: 'string', defaultValue: '', validate: validateFlags, normalize: normalizeFlags}]
])
}
] ]
]); ]);
this._validFlags = new Set([
'clipboard'
]);
} }
get settingsController() { get settingsController() {
@ -280,6 +300,20 @@ class ProfileConditionsUI extends EventDispatcher {
return this.splitValue(value).join(', '); return this.splitValue(value).join(', ');
} }
_validateFlags(value) {
const flags = this.splitValue(value);
for (const flag of flags) {
if (!this._validFlags.has(flag)) {
return false;
}
}
return flags.length > 0;
}
_normalizeFlags(value) {
return [...new Set(this.splitValue(value))].join(', ');
}
_triggerConditionGroupCountChanged(count) { _triggerConditionGroupCountChanged(count) {
this.trigger('conditionGroupCountChanged', {count, profileIndex: this._profileIndex}); this.trigger('conditionGroupCountChanged', {count, profileIndex: this._profileIndex});
} }

View File

@ -40,25 +40,25 @@ function testNormalizeContext() {
// Empty // Empty
{ {
context: {}, context: {},
expected: {} expected: {flags: []}
}, },
// Domain normalization // Domain normalization
{ {
context: {url: ''}, context: {url: ''},
expected: {url: ''} expected: {url: '', flags: []}
}, },
{ {
context: {url: 'http://example.com/'}, context: {url: 'http://example.com/'},
expected: {url: 'http://example.com/', domain: 'example.com'} expected: {url: 'http://example.com/', domain: 'example.com', flags: []}
}, },
{ {
context: {url: 'http://example.com:1234/'}, context: {url: 'http://example.com:1234/'},
expected: {url: 'http://example.com:1234/', domain: 'example.com'} expected: {url: 'http://example.com:1234/', domain: 'example.com', flags: []}
}, },
{ {
context: {url: 'http://user@example.com:1234/'}, context: {url: 'http://user@example.com:1234/'},
expected: {url: 'http://user@example.com:1234/', domain: 'example.com'} expected: {url: 'http://user@example.com:1234/', domain: 'example.com', flags: []}
} }
]; ];
@ -611,6 +611,266 @@ function testSchemas() {
] ]
}, },
// flags tests
{
conditionGroups: [
{
conditions: [
{
type: 'flags',
operator: 'are',
value: ''
}
]
}
],
expectedSchema: {
required: ['flags'],
properties: {
flags: {
type: 'array',
maxItems: 0,
minItems: 0
}
}
},
inputs: [
{expected: true, context: {}},
{expected: true, context: {flags: []}},
{expected: false, context: {flags: ['test1']}},
{expected: false, context: {flags: ['test1', 'test2']}},
{expected: false, context: {flags: ['test1', 'test2', 'test3']}}
]
},
{
conditionGroups: [
{
conditions: [
{
type: 'flags',
operator: 'are',
value: 'test1, test2'
}
]
}
],
expectedSchema: {
required: ['flags'],
properties: {
flags: {
type: 'array',
maxItems: 2,
minItems: 2,
allOf: [
{contains: {const: 'test1'}},
{contains: {const: 'test2'}}
]
}
}
},
inputs: [
{expected: false, context: {}},
{expected: false, context: {flags: []}},
{expected: false, context: {flags: ['test1']}},
{expected: true, context: {flags: ['test1', 'test2']}},
{expected: false, context: {flags: ['test1', 'test2', 'test3']}}
]
},
{
conditionGroups: [
{
conditions: [
{
type: 'flags',
operator: 'areNot',
value: ''
}
]
}
],
expectedSchema: {
not: [
{
required: ['flags'],
properties: {
flags: {
type: 'array',
maxItems: 0,
minItems: 0
}
}
}
]
},
inputs: [
{expected: false, context: {}},
{expected: false, context: {flags: []}},
{expected: true, context: {flags: ['test1']}},
{expected: true, context: {flags: ['test1', 'test2']}},
{expected: true, context: {flags: ['test1', 'test2', 'test3']}}
]
},
{
conditionGroups: [
{
conditions: [
{
type: 'flags',
operator: 'areNot',
value: 'test1, test2'
}
]
}
],
expectedSchema: {
not: [
{
required: ['flags'],
properties: {
flags: {
type: 'array',
maxItems: 2,
minItems: 2,
allOf: [
{contains: {const: 'test1'}},
{contains: {const: 'test2'}}
]
}
}
}
]
},
inputs: [
{expected: true, context: {}},
{expected: true, context: {flags: []}},
{expected: true, context: {flags: ['test1']}},
{expected: false, context: {flags: ['test1', 'test2']}},
{expected: true, context: {flags: ['test1', 'test2', 'test3']}}
]
},
{
conditionGroups: [
{
conditions: [
{
type: 'flags',
operator: 'include',
value: ''
}
]
}
],
expectedSchema: {
required: ['flags'],
properties: {
flags: {
type: 'array',
minItems: 0
}
}
},
inputs: [
{expected: true, context: {}},
{expected: true, context: {flags: []}},
{expected: true, context: {flags: ['test1']}},
{expected: true, context: {flags: ['test1', 'test2']}},
{expected: true, context: {flags: ['test1', 'test2', 'test3']}}
]
},
{
conditionGroups: [
{
conditions: [
{
type: 'flags',
operator: 'include',
value: 'test1, test2'
}
]
}
],
expectedSchema: {
required: ['flags'],
properties: {
flags: {
type: 'array',
minItems: 2,
allOf: [
{contains: {const: 'test1'}},
{contains: {const: 'test2'}}
]
}
}
},
inputs: [
{expected: false, context: {}},
{expected: false, context: {flags: []}},
{expected: false, context: {flags: ['test1']}},
{expected: true, context: {flags: ['test1', 'test2']}},
{expected: true, context: {flags: ['test1', 'test2', 'test3']}}
]
},
{
conditionGroups: [
{
conditions: [
{
type: 'flags',
operator: 'notInclude',
value: ''
}
]
}
],
expectedSchema: {
required: ['flags'],
properties: {
flags: {
type: 'array'
}
}
},
inputs: [
{expected: true, context: {}},
{expected: true, context: {flags: []}},
{expected: true, context: {flags: ['test1']}},
{expected: true, context: {flags: ['test1', 'test2']}},
{expected: true, context: {flags: ['test1', 'test2', 'test3']}}
]
},
{
conditionGroups: [
{
conditions: [
{
type: 'flags',
operator: 'notInclude',
value: 'test1, test2'
}
]
}
],
expectedSchema: {
required: ['flags'],
properties: {
flags: {
type: 'array',
not: [
{contains: {const: 'test1'}},
{contains: {const: 'test2'}}
]
}
}
},
inputs: [
{expected: true, context: {}},
{expected: true, context: {flags: []}},
{expected: false, context: {flags: ['test1']}},
{expected: false, context: {flags: ['test1', 'test2']}},
{expected: false, context: {flags: ['test1', 'test2', 'test3']}}
]
},
// Multiple conditions tests // Multiple conditions tests
{ {
conditionGroups: [ conditionGroups: [