Add support creating profile usage conditions
This commit is contained in:
parent
e3fb9603e2
commit
8c4fb28a30
@ -16,11 +16,13 @@
|
||||
<script src="/bg/js/api.js"></script>
|
||||
<script src="/bg/js/audio.js"></script>
|
||||
<script src="/bg/js/backend-api-forwarder.js"></script>
|
||||
<script src="/bg/js/conditions.js"></script>
|
||||
<script src="/bg/js/database.js"></script>
|
||||
<script src="/bg/js/deinflector.js"></script>
|
||||
<script src="/bg/js/dictionary.js"></script>
|
||||
<script src="/bg/js/handlebars.js"></script>
|
||||
<script src="/bg/js/options.js"></script>
|
||||
<script src="/bg/js/profile-conditions.js"></script>
|
||||
<script src="/bg/js/request.js"></script>
|
||||
<script src="/bg/js/templates.js"></script>
|
||||
<script src="/bg/js/translator.js"></script>
|
||||
|
317
ext/bg/js/conditions-ui.js
Normal file
317
ext/bg/js/conditions-ui.js
Normal file
@ -0,0 +1,317 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Alex Yatskov <alex@foosoft.net>
|
||||
* Author: Alex Yatskov <alex@foosoft.net>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
class ConditionsUI {
|
||||
static instantiateTemplate(templateSelector) {
|
||||
const template = document.querySelector(templateSelector);
|
||||
const content = document.importNode(template.content, true);
|
||||
return $(content.firstChild);
|
||||
}
|
||||
}
|
||||
|
||||
ConditionsUI.Container = class Container {
|
||||
constructor(conditionDescriptors, conditionNameDefault, conditionGroups, container, addButton) {
|
||||
this.children = [];
|
||||
this.conditionDescriptors = conditionDescriptors;
|
||||
this.conditionNameDefault = conditionNameDefault;
|
||||
this.conditionGroups = conditionGroups;
|
||||
this.container = container;
|
||||
this.addButton = addButton;
|
||||
|
||||
this.container.empty();
|
||||
|
||||
for (const conditionGroup of conditionGroups) {
|
||||
this.children.push(new ConditionsUI.ConditionGroup(this, conditionGroup));
|
||||
}
|
||||
|
||||
this.addButton.on('click', () => this.onAddConditionGroup());
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
for (const child of this.children) {
|
||||
child.cleanup();
|
||||
}
|
||||
|
||||
this.addButton.off('click');
|
||||
this.container.empty();
|
||||
}
|
||||
|
||||
save() {
|
||||
// Override
|
||||
}
|
||||
|
||||
remove(child) {
|
||||
const index = this.children.indexOf(child);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
child.cleanup();
|
||||
this.children.splice(index, 1);
|
||||
this.conditionGroups.splice(index, 1);
|
||||
}
|
||||
|
||||
onAddConditionGroup() {
|
||||
const conditionGroup = {
|
||||
conditions: [this.createDefaultCondition(this.conditionNameDefault)]
|
||||
};
|
||||
this.conditionGroups.push(conditionGroup);
|
||||
this.save();
|
||||
this.children.push(new ConditionsUI.ConditionGroup(this, conditionGroup));
|
||||
}
|
||||
|
||||
createDefaultCondition(type) {
|
||||
let operator = '';
|
||||
let value = '';
|
||||
if (this.conditionDescriptors.hasOwnProperty(type)) {
|
||||
const conditionDescriptor = this.conditionDescriptors[type];
|
||||
operator = conditionDescriptor.defaultOperator;
|
||||
({value} = this.getOperatorDefaultValue(type, operator));
|
||||
if (typeof value === 'undefined') {
|
||||
value = '';
|
||||
}
|
||||
}
|
||||
return {type, operator, value};
|
||||
}
|
||||
|
||||
getOperatorDefaultValue(type, operator) {
|
||||
if (this.conditionDescriptors.hasOwnProperty(type)) {
|
||||
const conditionDescriptor = this.conditionDescriptors[type];
|
||||
if (conditionDescriptor.operators.hasOwnProperty(operator)) {
|
||||
const operatorDescriptor = conditionDescriptor.operators[operator];
|
||||
if (operatorDescriptor.hasOwnProperty('defaultValue')) {
|
||||
return {value: operatorDescriptor.defaultValue, fromOperator: true};
|
||||
}
|
||||
}
|
||||
if (conditionDescriptor.hasOwnProperty('defaultValue')) {
|
||||
return {value: conditionDescriptor.defaultValue, fromOperator: false};
|
||||
}
|
||||
}
|
||||
return {fromOperator: false};
|
||||
}
|
||||
};
|
||||
|
||||
ConditionsUI.ConditionGroup = class ConditionGroup {
|
||||
constructor(parent, conditionGroup) {
|
||||
this.parent = parent;
|
||||
this.children = [];
|
||||
this.conditionGroup = conditionGroup;
|
||||
this.container = $('<div>').addClass('condition-group').appendTo(parent.container);
|
||||
this.options = ConditionsUI.instantiateTemplate('#condition-group-options-template').appendTo(parent.container);
|
||||
this.separator = ConditionsUI.instantiateTemplate('#condition-group-separator-template').appendTo(parent.container);
|
||||
this.addButton = this.options.find('.condition-add');
|
||||
|
||||
for (const condition of conditionGroup.conditions) {
|
||||
this.children.push(new ConditionsUI.Condition(this, condition));
|
||||
}
|
||||
|
||||
this.addButton.on('click', () => this.onAddCondition());
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
for (const child of this.children) {
|
||||
child.cleanup();
|
||||
}
|
||||
|
||||
this.addButton.off('click');
|
||||
this.container.remove();
|
||||
this.options.remove();
|
||||
this.separator.remove();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.parent.save();
|
||||
}
|
||||
|
||||
remove(child) {
|
||||
const index = this.children.indexOf(child);
|
||||
if (index < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
child.cleanup();
|
||||
this.children.splice(index, 1);
|
||||
this.conditionGroup.conditions.splice(index, 1);
|
||||
|
||||
if (this.children.length === 0) {
|
||||
this.parent.remove(this, false);
|
||||
}
|
||||
}
|
||||
|
||||
onAddCondition() {
|
||||
const condition = this.parent.createDefaultCondition(this.parent.conditionNameDefault);
|
||||
this.conditionGroup.conditions.push(condition);
|
||||
this.children.push(new ConditionsUI.Condition(this, condition));
|
||||
}
|
||||
};
|
||||
|
||||
ConditionsUI.Condition = class Condition {
|
||||
constructor(parent, condition) {
|
||||
this.parent = parent;
|
||||
this.condition = condition;
|
||||
this.container = ConditionsUI.instantiateTemplate('#condition-template').appendTo(parent.container);
|
||||
this.input = this.container.find('input');
|
||||
this.typeSelect = this.container.find('.condition-type');
|
||||
this.operatorSelect = this.container.find('.condition-operator');
|
||||
this.removeButton = this.container.find('.condition-remove');
|
||||
|
||||
this.updateTypes();
|
||||
this.updateOperators();
|
||||
this.updateInput();
|
||||
|
||||
this.input.on('change', () => this.onInputChanged());
|
||||
this.typeSelect.on('change', () => this.onConditionTypeChanged());
|
||||
this.operatorSelect.on('change', () => this.onConditionOperatorChanged());
|
||||
this.removeButton.on('click', () => this.onRemoveClicked());
|
||||
}
|
||||
|
||||
cleanup() {
|
||||
this.input.off('change');
|
||||
this.typeSelect.off('change');
|
||||
this.operatorSelect.off('change');
|
||||
this.removeButton.off('click');
|
||||
this.container.remove();
|
||||
}
|
||||
|
||||
save() {
|
||||
this.parent.save();
|
||||
}
|
||||
|
||||
updateTypes() {
|
||||
const conditionDescriptors = this.parent.parent.conditionDescriptors;
|
||||
const optionGroup = this.typeSelect.find('optgroup');
|
||||
optionGroup.empty();
|
||||
for (const type of Object.keys(conditionDescriptors)) {
|
||||
const conditionDescriptor = conditionDescriptors[type];
|
||||
$('<option>').val(type).text(conditionDescriptor.name).appendTo(optionGroup);
|
||||
}
|
||||
this.typeSelect.val(this.condition.type);
|
||||
}
|
||||
|
||||
updateOperators() {
|
||||
const conditionDescriptors = this.parent.parent.conditionDescriptors;
|
||||
const optionGroup = this.operatorSelect.find('optgroup');
|
||||
optionGroup.empty();
|
||||
|
||||
const type = this.condition.type;
|
||||
if (conditionDescriptors.hasOwnProperty(type)) {
|
||||
const conditionDescriptor = conditionDescriptors[type];
|
||||
const operators = conditionDescriptor.operators;
|
||||
for (const operatorName of Object.keys(operators)) {
|
||||
const operatorDescriptor = operators[operatorName];
|
||||
$('<option>').val(operatorName).text(operatorDescriptor.name).appendTo(optionGroup);
|
||||
}
|
||||
}
|
||||
|
||||
this.operatorSelect.val(this.condition.operator);
|
||||
}
|
||||
|
||||
updateInput() {
|
||||
const conditionDescriptors = this.parent.parent.conditionDescriptors;
|
||||
const {type, operator} = this.condition;
|
||||
const props = {
|
||||
placeholder: '',
|
||||
type: 'text'
|
||||
};
|
||||
|
||||
const objects = [];
|
||||
if (conditionDescriptors.hasOwnProperty(type)) {
|
||||
const conditionDescriptor = conditionDescriptors[type];
|
||||
objects.push(conditionDescriptor);
|
||||
if (conditionDescriptor.operators.hasOwnProperty(operator)) {
|
||||
const operatorDescriptor = conditionDescriptor.operators[operator];
|
||||
objects.push(operatorDescriptor);
|
||||
}
|
||||
}
|
||||
|
||||
for (const object of objects) {
|
||||
if (object.hasOwnProperty('placeholder')) {
|
||||
props.placeholder = object.placeholder;
|
||||
}
|
||||
if (object.type === 'number') {
|
||||
props.type = 'number';
|
||||
for (const prop of ['step', 'min', 'max']) {
|
||||
if (object.hasOwnProperty(prop)) {
|
||||
props[prop] = object[prop];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const prop in props) {
|
||||
this.input.prop(prop, props[prop]);
|
||||
}
|
||||
|
||||
const {valid} = this.validateValue(this.condition.value);
|
||||
this.input.toggleClass('is-invalid', !valid);
|
||||
this.input.val(this.condition.value);
|
||||
}
|
||||
|
||||
validateValue(value) {
|
||||
const conditionDescriptors = this.parent.parent.conditionDescriptors;
|
||||
let valid = true;
|
||||
try {
|
||||
value = conditionsNormalizeOptionValue(
|
||||
conditionDescriptors,
|
||||
this.condition.type,
|
||||
this.condition.operator,
|
||||
value
|
||||
);
|
||||
} catch (e) {
|
||||
valid = false;
|
||||
}
|
||||
return {valid, value};
|
||||
}
|
||||
|
||||
onInputChanged() {
|
||||
const {valid, value} = this.validateValue(this.input.val());
|
||||
this.input.toggleClass('is-invalid', !valid);
|
||||
this.input.val(value);
|
||||
this.condition.value = value;
|
||||
this.save();
|
||||
}
|
||||
|
||||
onConditionTypeChanged() {
|
||||
const type = this.typeSelect.val();
|
||||
const {operator, value} = this.parent.parent.createDefaultCondition(type);
|
||||
this.condition.type = type;
|
||||
this.condition.operator = operator;
|
||||
this.condition.value = value;
|
||||
this.save();
|
||||
this.updateOperators();
|
||||
this.updateInput();
|
||||
}
|
||||
|
||||
onConditionOperatorChanged() {
|
||||
const type = this.condition.type;
|
||||
const operator = this.operatorSelect.val();
|
||||
const {value, fromOperator} = this.parent.parent.getOperatorDefaultValue(type, operator);
|
||||
this.condition.operator = operator;
|
||||
if (fromOperator) {
|
||||
this.condition.value = value;
|
||||
}
|
||||
this.save();
|
||||
this.updateInput();
|
||||
}
|
||||
|
||||
onRemoveClicked() {
|
||||
this.parent.remove(this);
|
||||
this.save();
|
||||
}
|
||||
};
|
117
ext/bg/js/conditions.js
Normal file
117
ext/bg/js/conditions.js
Normal file
@ -0,0 +1,117 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Alex Yatskov <alex@foosoft.net>
|
||||
* Author: Alex Yatskov <alex@foosoft.net>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
function conditionsValidateOptionValue(object, value) {
|
||||
if (object.hasOwnProperty('validate') && !object.validate(value)) {
|
||||
throw new Error('Invalid value for condition');
|
||||
}
|
||||
|
||||
if (object.hasOwnProperty('transform')) {
|
||||
value = object.transform(value);
|
||||
|
||||
if (object.hasOwnProperty('validateTransformed') && !object.validateTransformed(value)) {
|
||||
throw new Error('Invalid value for condition');
|
||||
}
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
function conditionsNormalizeOptionValue(descriptors, type, operator, optionValue) {
|
||||
if (!descriptors.hasOwnProperty(type)) {
|
||||
throw new Error('Invalid type');
|
||||
}
|
||||
|
||||
const conditionDescriptor = descriptors[type];
|
||||
if (!conditionDescriptor.operators.hasOwnProperty(operator)) {
|
||||
throw new Error('Invalid operator');
|
||||
}
|
||||
|
||||
const operatorDescriptor = conditionDescriptor.operators[operator];
|
||||
|
||||
let transformedValue = conditionsValidateOptionValue(conditionDescriptor, optionValue);
|
||||
transformedValue = conditionsValidateOptionValue(operatorDescriptor, transformedValue);
|
||||
|
||||
if (operatorDescriptor.hasOwnProperty('transformReverse')) {
|
||||
transformedValue = operatorDescriptor.transformReverse(transformedValue);
|
||||
}
|
||||
return transformedValue;
|
||||
}
|
||||
|
||||
function conditionsTestValueThrowing(descriptors, type, operator, optionValue, value) {
|
||||
if (!descriptors.hasOwnProperty(type)) {
|
||||
throw new Error('Invalid type');
|
||||
}
|
||||
|
||||
const conditionDescriptor = descriptors[type];
|
||||
if (!conditionDescriptor.operators.hasOwnProperty(operator)) {
|
||||
throw new Error('Invalid operator');
|
||||
}
|
||||
|
||||
const operatorDescriptor = conditionDescriptor.operators[operator];
|
||||
if (operatorDescriptor.hasOwnProperty('transform')) {
|
||||
if (operatorDescriptor.hasOwnProperty('transformCache')) {
|
||||
const key = `${optionValue}`;
|
||||
const transformCache = operatorDescriptor.transformCache;
|
||||
if (transformCache.hasOwnProperty(key)) {
|
||||
optionValue = transformCache[key];
|
||||
} else {
|
||||
optionValue = operatorDescriptor.transform(optionValue);
|
||||
transformCache[key] = optionValue;
|
||||
}
|
||||
} else {
|
||||
optionValue = operatorDescriptor.transform(optionValue);
|
||||
}
|
||||
}
|
||||
|
||||
return operatorDescriptor.test(value, optionValue);
|
||||
}
|
||||
|
||||
function conditionsTestValue(descriptors, type, operator, optionValue, value) {
|
||||
try {
|
||||
return conditionsTestValueThrowing(descriptors, type, operator, optionValue, value);
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function conditionsClearCaches(descriptors) {
|
||||
for (const type in descriptors) {
|
||||
if (!descriptors.hasOwnProperty(type)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const conditionDescriptor = descriptors[type];
|
||||
if (conditionDescriptor.hasOwnProperty('transformCache')) {
|
||||
conditionDescriptor.transformCache = {};
|
||||
}
|
||||
|
||||
const operatorDescriptors = conditionDescriptor.operators;
|
||||
for (const operator in operatorDescriptors) {
|
||||
if (!operatorDescriptors.hasOwnProperty(operator)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const operatorDescriptor = operatorDescriptors[operator];
|
||||
if (operatorDescriptor.hasOwnProperty('transformCache')) {
|
||||
operatorDescriptor.transformCache = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -329,6 +329,22 @@ function profileOptionsUpdateVersion(options) {
|
||||
|
||||
/*
|
||||
* Global options
|
||||
*
|
||||
* Each profile has an array named "conditionGroups", which is an array of condition groups
|
||||
* which enable the contextual selection of profiles. The structure of the array is as follows:
|
||||
* [
|
||||
* {
|
||||
* conditions: [
|
||||
* {
|
||||
* type: "string",
|
||||
* operator: "string",
|
||||
* value: "string"
|
||||
* },
|
||||
* // ...
|
||||
* ]
|
||||
* },
|
||||
* // ...
|
||||
* ]
|
||||
*/
|
||||
|
||||
const optionsVersionUpdates = [];
|
||||
@ -351,7 +367,8 @@ function optionsUpdateVersion(options, defaultProfileOptions) {
|
||||
if (profiles.length === 0) {
|
||||
profiles.push({
|
||||
name: 'Default',
|
||||
options: defaultProfileOptions
|
||||
options: defaultProfileOptions,
|
||||
conditionGroups: []
|
||||
});
|
||||
}
|
||||
|
||||
@ -369,6 +386,9 @@ function optionsUpdateVersion(options, defaultProfileOptions) {
|
||||
|
||||
// Update profile options
|
||||
for (const profile of profiles) {
|
||||
if (!Array.isArray(profile.conditionGroups)) {
|
||||
profile.conditionGroups = [];
|
||||
}
|
||||
profile.options = profileOptionsUpdateVersion(profile.options);
|
||||
}
|
||||
|
||||
|
85
ext/bg/js/profile-conditions.js
Normal file
85
ext/bg/js/profile-conditions.js
Normal file
@ -0,0 +1,85 @@
|
||||
/*
|
||||
* Copyright (C) 2019 Alex Yatskov <alex@foosoft.net>
|
||||
* Author: Alex Yatskov <alex@foosoft.net>
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
|
||||
const profileConditionsDescriptor = {
|
||||
popupLevel: {
|
||||
name: 'Popup Level',
|
||||
description: 'Use profile depending on the level of the popup.',
|
||||
placeholder: 'Number',
|
||||
type: 'number',
|
||||
step: 1,
|
||||
defaultValue: 0,
|
||||
defaultOperator: 'equal',
|
||||
transform: (optionValue) => parseInt(optionValue, 10),
|
||||
transformReverse: (transformedOptionValue) => `${transformedOptionValue}`,
|
||||
validateTransformed: (transformedOptionValue) => Number.isFinite(transformedOptionValue),
|
||||
operators: {
|
||||
equal: {
|
||||
name: '=',
|
||||
test: (value, optionValue) => (value === optionValue)
|
||||
},
|
||||
notEqual: {
|
||||
name: '\u2260',
|
||||
test: (value, optionValue) => (value !== optionValue)
|
||||
},
|
||||
lessThan: {
|
||||
name: '<',
|
||||
test: (value, optionValue) => (value < optionValue)
|
||||
},
|
||||
greaterThan: {
|
||||
name: '>',
|
||||
test: (value, optionValue) => (value > optionValue)
|
||||
},
|
||||
lessThanOrEqual: {
|
||||
name: '\u2264',
|
||||
test: (value, optionValue) => (value <= optionValue)
|
||||
},
|
||||
greaterThanOrEqual: {
|
||||
name: '\u2265',
|
||||
test: (value, optionValue) => (value >= optionValue)
|
||||
}
|
||||
}
|
||||
},
|
||||
url: {
|
||||
name: 'URL',
|
||||
description: 'Use profile depending on the URL of the current website.',
|
||||
defaultOperator: 'matchDomain',
|
||||
operators: {
|
||||
matchDomain: {
|
||||
name: 'Matches Domain',
|
||||
placeholder: 'Comma separated list of domains',
|
||||
defaultValue: 'example.com',
|
||||
transformCache: {},
|
||||
transform: (optionValue) => optionValue.split(/[,;\s]+/).map(v => v.trim().toLowerCase()).filter(v => v.length > 0),
|
||||
transformReverse: (transformedOptionValue) => transformedOptionValue.join(', '),
|
||||
validateTransformed: (transformedOptionValue) => (transformedOptionValue.length > 0),
|
||||
test: (value, transformedOptionValue) => (transformedOptionValue.indexOf(new URL(value).hostname.toLowerCase()) >= 0)
|
||||
},
|
||||
matchRegExp: {
|
||||
name: 'Matches RegExp',
|
||||
placeholder: 'Regular expression',
|
||||
defaultValue: 'example\\.com',
|
||||
transformCache: {},
|
||||
transform: (optionValue) => new RegExp(optionValue, 'i'),
|
||||
transformReverse: (transformedOptionValue) => transformedOptionValue.source,
|
||||
test: (value, transformedOptionValue) => (transformedOptionValue !== null && transformedOptionValue.test(value))
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
@ -17,6 +17,7 @@
|
||||
*/
|
||||
|
||||
let currentProfileIndex = 0;
|
||||
let profileConditionsContainer = null;
|
||||
|
||||
function getOptionsContext() {
|
||||
return {
|
||||
@ -81,6 +82,19 @@ async function profileFormWrite(optionsFull) {
|
||||
$('#profile-move-down').prop('disabled', currentProfileIndex >= optionsFull.profiles.length - 1);
|
||||
|
||||
$('#profile-name').val(profile.name);
|
||||
|
||||
if (profileConditionsContainer !== null) {
|
||||
profileConditionsContainer.cleanup();
|
||||
}
|
||||
|
||||
profileConditionsContainer = new ConditionsUI.Container(
|
||||
profileConditionsDescriptor,
|
||||
'popupLevel',
|
||||
profile.conditionGroups,
|
||||
$('#profile-condition-groups'),
|
||||
$('#profile-add-condition-group')
|
||||
);
|
||||
profileConditionsContainer.save = () => apiOptionsSave();
|
||||
}
|
||||
|
||||
function profileOptionsPopulateSelect(select, profiles, currentValue, ignoreIndices) {
|
||||
|
@ -34,6 +34,55 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.form-control.is-invalid {
|
||||
border-color: #f00000;
|
||||
}
|
||||
|
||||
.condition>.condition-prefix:after {
|
||||
content: "IF";
|
||||
}
|
||||
.condition:nth-child(n+2)>.condition-prefix:after {
|
||||
content: "AND";
|
||||
}
|
||||
|
||||
.input-group .condition-prefix,
|
||||
.input-group .condition-group-separator-label {
|
||||
width: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
.input-group .condition-group-separator-label {
|
||||
padding: 6px 12px;
|
||||
font-weight: bold;
|
||||
display: inline-block;
|
||||
}
|
||||
.input-group .condition-type,
|
||||
.input-group .condition-operator {
|
||||
width: auto;
|
||||
text-align: center;
|
||||
text-align-last: center;
|
||||
}
|
||||
|
||||
.condition-group>.condition>div:first-child {
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
.condition-group>.condition:nth-child(n+2)>div:first-child {
|
||||
border-top-left-radius: 0;
|
||||
}
|
||||
.condition-group>.condition:nth-child(n+2)>div:last-child>button {
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
.condition-group>.condition:nth-last-child(n+2)>div:last-child>button {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
.condition-group-options>.condition-add {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.condition-groups>*:last-of-type {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#custom-popup-css {
|
||||
width: 100%;
|
||||
min-height: 34px;
|
||||
@ -71,7 +120,7 @@
|
||||
<h3>Profiles</h3>
|
||||
|
||||
<p class="help-block">
|
||||
Profiles allow you to create multiple configurations and quickly switch between them.
|
||||
Profiles allow you to create multiple configurations and quickly switch between them or use them in different contexts.
|
||||
</p>
|
||||
|
||||
<div class="form-group">
|
||||
@ -100,6 +149,27 @@
|
||||
<input type="text" id="profile-name" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label>Usage conditions</label>
|
||||
|
||||
<p class="help-block">
|
||||
Usage conditions can be assigned such that certain profiles are automatically used in different contexts.
|
||||
For example, when <a href="#popup-content-scanning">Popup Content Scanning</a> is enabled, different profiles can be used
|
||||
depending on the level of the popup.
|
||||
</p>
|
||||
|
||||
<p class="help-block">
|
||||
Conditions are organized into groups which represent how the conditions are checked.
|
||||
If all of the conditions in any group are met, then the profile will automatically be used for that context.
|
||||
If no conditions are specified, the profile will only be used if it is selected as the <strong>Active profile</strong>.
|
||||
</p>
|
||||
|
||||
<div class="condition-groups" id="profile-condition-groups"></div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<button class="btn btn-default" id="profile-add-condition-group">Add Condition Group</button>
|
||||
</div>
|
||||
|
||||
<div class="modal fade" tabindex="-1" role="dialog" id="profile-copy-modal">
|
||||
<div class="modal-dialog modal-dialog-centered">
|
||||
<div class="modal-content">
|
||||
@ -136,6 +206,20 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template id="condition-template"><div class="input-group condition">
|
||||
<div class="input-group-addon condition-prefix"></div>
|
||||
<div class="input-group-btn"><select class="form-control btn btn-default condition-type"><optgroup label="Type"></optgroup></select></div>
|
||||
<div class="input-group-btn"><select class="form-control btn btn-default condition-operator"><optgroup label="Operator"></optgroup></select></div>
|
||||
<input type="text" class="form-control" />
|
||||
<div class="input-group-btn"><button class="btn btn-danger condition-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></div>
|
||||
</div></template>
|
||||
<template id="condition-group-separator-template"><div class="input-group">
|
||||
<div class="condition-group-separator-label">OR</div>
|
||||
</div></template>
|
||||
<template id="condition-group-options-template"><div class="condition-group-options">
|
||||
<button class="btn btn-default condition-add"><span class="glyphicon glyphicon-plus"></span></button>
|
||||
</div></template>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@ -563,9 +647,12 @@
|
||||
|
||||
<script src="/bg/js/anki.js"></script>
|
||||
<script src="/bg/js/api.js"></script>
|
||||
<script src="/bg/js/conditions.js"></script>
|
||||
<script src="/bg/js/conditions-ui.js"></script>
|
||||
<script src="/bg/js/dictionary.js"></script>
|
||||
<script src="/bg/js/handlebars.js"></script>
|
||||
<script src="/bg/js/options.js"></script>
|
||||
<script src="/bg/js/profile-conditions.js"></script>
|
||||
<script src="/bg/js/templates.js"></script>
|
||||
<script src="/bg/js/util.js"></script>
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user