diff options
Diffstat (limited to 'ext')
| -rw-r--r-- | ext/bg/css/settings.css | 90 | ||||
| -rw-r--r-- | ext/bg/data/options-schema.json | 34 | ||||
| -rw-r--r-- | ext/bg/js/options.js | 19 | ||||
| -rw-r--r-- | ext/bg/js/settings/keyboard-mouse-input-field.js | 2 | ||||
| -rw-r--r-- | ext/bg/js/settings/main.js | 23 | ||||
| -rw-r--r-- | ext/bg/js/settings/profile-conditions-ui.js | 2 | ||||
| -rw-r--r-- | ext/bg/js/settings/scan-inputs-controller.js | 178 | ||||
| -rw-r--r-- | ext/bg/settings.html | 34 | ||||
| -rw-r--r-- | ext/fg/js/frontend.js | 7 | ||||
| -rw-r--r-- | ext/mixed/js/display.js | 3 | ||||
| -rw-r--r-- | ext/mixed/js/document-util.js | 25 | ||||
| -rw-r--r-- | ext/mixed/js/text-scanner.js | 88 | 
12 files changed, 417 insertions, 88 deletions
| diff --git a/ext/bg/css/settings.css b/ext/bg/css/settings.css index 2dc71d91..6d32c9c5 100644 --- a/ext/bg/css/settings.css +++ b/ext/bg/css/settings.css @@ -46,6 +46,21 @@ html:root:not([data-options-general-result-output-mode=merge]) #dict-main-group      border-color: #f00000;  } +.mouse-button { +    padding-left: 10px; +    padding-right: 10px; +} +.mouse-button[hidden] { +    display: none; +} +.mouse-button-icon { +    width: 20px; +    height: 20px; +    display: block; +    background: url(/mixed/img/mouse.svg) no-repeat center center; +    background-size: 20px 20px; +} +  .condition {      display: flex;      -flex-wrap: wrap; @@ -113,21 +128,80 @@ html:root:not([data-options-general-result-output-mode=merge]) #dict-main-group      display: none;  } -.condition-mouse-button[hidden] { -    display: none; +.scan-input-list { +    counter-reset: scan-input-id; +} +.scan-input-table { +    width: 100%; +} +.scan-input-list:not(:empty)+#scan-input-add { +    border-top-left-radius: 0; +    border-top-right-radius: 0; +} +.scan-input-index-cell { +    position: relative; +    min-width: 40px; +} +.scan-input-index { +    position: absolute; +    left: 0; +    top: 0; +    right: 0; +    bottom: 0; +    font-size: 14px; +    color: #555; +    background-color: #eee; +    border: 1px solid #ccc; +    border-top-left-radius: 4px; +    display: flex; +    justify-content: center; +    align-items: center; +} +.scan-input-index:after { +    display: block; +    counter-increment: scan-input-id; +    content: counter(scan-input-id); +} +.scan-input-prefix { +    padding: 6px 12px; +    font-size: 14px; +    color: #555; +    text-align: center; +    background-color: #eee; +    border: 1px solid #ccc; +    width: 100%; +} +.scan-input-input-cell { +    width: 100%; +} +.scan-input-input-cell>input { +    border-radius: 0; +} +.scan-input-mouse-button-cell>button { +    border-radius: 0; +} +.scan-input-remove-button-cell>button { +    border-top-left-radius: 0; +    border-bottom-left-radius: 0; +} +.scan-input:nth-child(n+2) .scan-input-index { +    border-top-left-radius: 0; +} +.scan-input:last-child tr:last-of-type .scan-input-mouse-button-cell>button { +    border-bottom-right-radius: 4px;  } -.audio-source-list { -    counter-reset: audio-source-id; +.generic-input-list { +    counter-reset: generic-input-id;  } -.audio-source-list .audio-source-prefix { +.generic-input-list .generic-input-prefix {      flex: 0 0 auto;      width: 39px;      text-align: center;  } -.audio-source-list .audio-source-prefix:after { -    counter-increment: audio-source-id; -    content: counter(audio-source-id); +.generic-input-list .generic-input-prefix:after { +    counter-increment: generic-input-id; +    content: counter(generic-input-id);  }  #custom-popup-css, diff --git a/ext/bg/data/options-schema.json b/ext/bg/data/options-schema.json index 52d96db9..17fe096c 100644 --- a/ext/bg/data/options-schema.json +++ b/ext/bg/data/options-schema.json @@ -318,7 +318,7 @@                              "scanning": {                                  "type": "object",                                  "required": [ -                                    "middleMouse", +                                    "inputs",                                      "touchInputEnabled",                                      "selectText",                                      "alphanumeric", @@ -336,9 +336,30 @@                                      "layoutAwareScan"                                  ],                                  "properties": { -                                    "middleMouse": { -                                        "type": "boolean", -                                        "default": true +                                    "inputs": { +                                        "type": "array", +                                        "default": [ +                                            { +                                                "include": "shift", +                                                "exclude": "" +                                            } +                                        ], +                                        "items": { +                                            "required": [ +                                                "include", +                                                "exclude" +                                            ], +                                            "properties": { +                                                "include": { +                                                    "type": "string", +                                                    "default": "shift" +                                                }, +                                                "exclude": { +                                                    "type": "string", +                                                    "default": "" +                                                } +                                            } +                                        }                                      },                                      "touchInputEnabled": {                                          "type": "boolean", @@ -371,11 +392,6 @@                                          "minimum": 1,                                          "default": 10                                      }, -                                    "modifier": { -                                        "type": "string", -                                        "enum": ["none", "alt", "ctrl", "shift", "meta"], -                                        "default": "shift" -                                    },                                      "deepDomScan": {                                          "type": "boolean",                                          "default": false diff --git a/ext/bg/js/options.js b/ext/bg/js/options.js index 398fb95c..ab32bb11 100644 --- a/ext/bg/js/options.js +++ b/ext/bg/js/options.js @@ -474,6 +474,7 @@ class OptionsUtil {          //  Added usePopupWindow.          //  Updated handlebars templates to include "clipboard-image" definition.          //  Added hideDelay. +        //  Added inputs to profileOptions.scanning.          for (const {conditionGroups} of options.profiles) {              for (const {conditions} of conditionGroups) {                  for (const condition of conditions) { @@ -489,6 +490,24 @@ class OptionsUtil {          for (const {options: profileOptions} of options.profiles) {              profileOptions.general.usePopupWindow = false;              profileOptions.scanning.hideDelay = 0; + +            const {modifier, middleMouse} = profileOptions.scanning; +            const scanningInputs = []; +            switch (modifier) { +                case 'alt': +                case 'ctrl': +                case 'shift': +                case 'meta': +                    scanningInputs.push(modifier); +                    break; +                case 'none': +                    scanningInputs.push(''); +                    break; +            } +            if (middleMouse) { +                scanningInputs.push('mouse2'); +            } +            profileOptions.scanning.inputs = scanningInputs;          }          await this._addFieldTemplatesToOptions(options, '/bg/data/anki-field-templates-upgrade-v4.handlebars');          return options; diff --git a/ext/bg/js/settings/keyboard-mouse-input-field.js b/ext/bg/js/settings/keyboard-mouse-input-field.js index b1530c1b..4ba0d3ed 100644 --- a/ext/bg/js/settings/keyboard-mouse-input-field.js +++ b/ext/bg/js/settings/keyboard-mouse-input-field.js @@ -172,7 +172,7 @@ class KeyboardMouseInputField extends EventDispatcher {      _onMouseButtonMouseDown(e) {          e.preventDefault(); -        this._addInputs([`mouse${e.button}`]); +        this._addInputs(DocumentUtil.getActiveButtons(e));      }      _onMouseButtonMouseUp(e) { diff --git a/ext/bg/js/settings/main.js b/ext/bg/js/settings/main.js index 7fa8e502..4932586b 100644 --- a/ext/bg/js/settings/main.js +++ b/ext/bg/js/settings/main.js @@ -22,10 +22,10 @@   * ClipboardPopupsController   * DictionaryController   * DictionaryImportController - * DocumentUtil   * GenericSettingController   * PopupPreviewController   * ProfileController + * ScanInputsController   * SettingsBackup   * SettingsController   * StorageController @@ -40,23 +40,6 @@ function showExtensionInformation() {      node.textContent = `${manifest.name} v${manifest.version}`;  } -async function settingsPopulateModifierKeys() { -    const scanModifierKeySelect = document.querySelector('#scan-modifier-key'); -    scanModifierKeySelect.textContent = ''; - -    const {platform: {os}} = await api.getEnvironmentInfo(); -    const modifierKeys = [ -        ['none', 'None'], -        ...DocumentUtil.getModifierKeys(os) -    ]; -    for (const [value, name] of modifierKeys) { -        const option = document.createElement('option'); -        option.value = value; -        option.textContent = name; -        scanModifierKeySelect.appendChild(option); -    } -} -  async function setupEnvironmentInfo() {      const {browser, platform} = await api.getEnvironmentInfo();      document.documentElement.dataset.browser = browser; @@ -71,7 +54,6 @@ async function setupEnvironmentInfo() {          setupEnvironmentInfo();          showExtensionInformation(); -        settingsPopulateModifierKeys();          const optionsFull = await api.optionsGetFull(); @@ -111,6 +93,9 @@ async function setupEnvironmentInfo() {          const settingsBackup = new SettingsBackup(settingsController);          settingsBackup.prepare(); +        const scanInputsController = new ScanInputsController(settingsController); +        scanInputsController.prepare(); +          yomichan.ready();      } catch (e) {          yomichan.logError(e); diff --git a/ext/bg/js/settings/profile-conditions-ui.js b/ext/bg/js/settings/profile-conditions-ui.js index d88f932b..419c35b3 100644 --- a/ext/bg/js/settings/profile-conditions-ui.js +++ b/ext/bg/js/settings/profile-conditions-ui.js @@ -441,7 +441,7 @@ class ProfileConditionUI {          this._operatorOptionContainer = this._operatorInput.querySelector('optgroup');          this._valueInput = this._node.querySelector('.condition-input-inner');          this._removeButton = this._node.querySelector('.condition-remove'); -        this._mouseButton = this._node.querySelector('.condition-mouse-button'); +        this._mouseButton = this._node.querySelector('.mouse-button');          const operatorDetails = this._getOperatorDetails(type, operator);          this._updateTypes(type); diff --git a/ext/bg/js/settings/scan-inputs-controller.js b/ext/bg/js/settings/scan-inputs-controller.js new file mode 100644 index 00000000..3b3945ff --- /dev/null +++ b/ext/bg/js/settings/scan-inputs-controller.js @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2020  Yomichan Authors + * + * 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 <https://www.gnu.org/licenses/>. + */ + +/* global + * KeyboardMouseInputField + * api + */ + +class ScanInputsController { +    constructor(settingsController) { +        this._settingsController = settingsController; +        this._os = null; +        this._container = null; +        this._addButton = null; +        this._entries = []; +    } + +    async prepare() { +        const {platform: {os}} = await api.getEnvironmentInfo(); +        this._os = os; + +        this._container = document.querySelector('#scan-input-list'); +        this._addButton = document.querySelector('#scan-input-add'); + +        this._addButton.addEventListener('click', this._onAddButtonClick.bind(this), false); +        this._settingsController.on('optionsChanged', this._onOptionsChanged.bind(this)); + +        const options = await this._settingsController.getOptions(); +        this._onOptionsChanged({options}); +    } + +    removeInput(index) { +        if (index < 0 || index >= this._entries.length) { return false; } +        const input = this._entries[index]; +        input.cleanup(); +        this._entries.splice(index, 1); +        for (let i = index, ii = this._entries.length; i < ii; ++i) { +            this._entries[i].index = i; +        } +        this._settingsController.modifyProfileSettings([{ +            action: 'splice', +            path: 'scanning.inputs', +            start: index, +            deleteCount: 1, +            items: [] +        }]); +    } + +    setProperty(index, property, value) { +        const path = `scanning.inputs[${index}].${property}`; +        this._settingsController.setProfileSetting(path, value); +    } + +    // Private + +    _onOptionsChanged({options}) { +        const {inputs} = options.scanning; + +        for (let i = this._entries.length - 1; i >= 0; --i) { +            this._entries[i].cleanup(); +        } +        this._entries.length = 0; + +        for (let i = 0, ii = inputs.length; i < ii; ++i) { +            const {include, exclude} = inputs[i]; +            this._addOption(i, include, exclude); +        } +    } + +    _onAddButtonClick(e) { +        e.preventDefault(); + +        const index = this._entries.length; +        const include = ''; +        const exclude = ''; +        this._addOption(index, include, exclude); +        this._settingsController.modifyProfileSettings([{ +            action: 'splice', +            path: 'scanning.inputs', +            start: index, +            deleteCount: 0, +            items: [{include, exclude}] +        }]); +    } + +    _addOption(index, include, exclude) { +        const field = new ScanInputField(this, index, this._os); +        this._entries.push(field); +        field.prepare(this._container, include, exclude); +    } +} + +class ScanInputField { +    constructor(parent, index, os) { +        this._parent = parent; +        this._index = index; +        this._os = os; +        this._node = null; +        this._includeInputField = null; +        this._excludeInputField = null; +        this._eventListeners = new EventListenerCollection(); +    } + +    get index() { +        return this._index; +    } + +    set index(value) { +        this._index = value; +    } + +    prepare(container, include, exclude) { +        const node = this._instantiateTemplate('#scan-input-template'); +        const includeInputNode = node.querySelector('.scan-input-include .scan-input-field'); +        const includeMouseButton = node.querySelector('.scan-input-include .mouse-button'); +        const excludeInputNode = node.querySelector('.scan-input-exclude .scan-input-field'); +        const excludeMouseButton = node.querySelector('.scan-input-exclude .mouse-button'); +        const removeButton = node.querySelector('.scan-input-remove'); + +        this._node = node; +        container.appendChild(node); + +        this._includeInputField = new KeyboardMouseInputField(includeInputNode, includeMouseButton, this._os); +        this._excludeInputField = new KeyboardMouseInputField(excludeInputNode, excludeMouseButton, this._os); +        this._includeInputField.prepare(include, 'modifierInputs'); +        this._excludeInputField.prepare(exclude, 'modifierInputs'); + +        this._eventListeners.on(this._includeInputField, 'change', this._onIncludeValueChange.bind(this)); +        this._eventListeners.on(this._excludeInputField, 'change', this._onExcludeValueChange.bind(this)); +        this._eventListeners.addEventListener(removeButton, 'click', this._onRemoveClick.bind(this)); +    } + +    cleanup() { +        this._eventListeners.removeAllEventListeners(); +        if (this._includeInputField !== null) { +            this._includeInputField.cleanup(); +            this._includeInputField = null; +        } +        if (this._node !== null) { +            const parent = this._node.parentNode; +            if (parent !== null) { parent.removeChild(this._node); } +            this._node = null; +        } +    } + +    _onIncludeValueChange({value}) { +        this._parent.setProperty(this._index, 'include', value); +    } + +    _onExcludeValueChange({value}) { +        this._parent.setProperty(this._index, 'exclude', value); +    } + +    _onRemoveClick(e) { +        e.preventDefault(); +        this._parent.removeInput(this._index); +    } + +    _instantiateTemplate(templateSelector) { +        const template = document.querySelector(templateSelector); +        const content = document.importNode(template.content, true); +        return content.firstChild; +    } +} diff --git a/ext/bg/settings.html b/ext/bg/settings.html index 3fa14f49..ae89ca1f 100644 --- a/ext/bg/settings.html +++ b/ext/bg/settings.html @@ -125,7 +125,7 @@                      <div class="input-group-btn"><select class="form-control btn btn-default condition-operator"><optgroup label="Operator"></optgroup></select></div>                      <div class="condition-line-break"></div>                      <div class="condition-input"><input type="text" class="form-control condition-input-inner"></div> -                    <div class="input-group-btn"><button class="btn btn-default condition-mouse-button" title="Mouse button" style="padding-left: 10px; padding-right: 10px;" hidden><span style="width: 20px; height: 20px; display: block; background: url(/mixed/img/mouse.svg) no-repeat center center; background-size: 20px 20px;"></span></button><button class="btn btn-danger condition-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></div> +                    <div class="input-group-btn"><button class="btn btn-default mouse-button" title="Mouse button"><span class="mouse-button-icon"></span></button><button class="btn btn-danger condition-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></div>                  </div></template>              </div> @@ -363,13 +363,13 @@                  <div class="form-group ignore-form-changes">                      <label>Audio playback sources</label> -                    <div class="audio-source-list"></div> +                    <div class="audio-source-list generic-input-list"></div>                      <div class="input-group audio-source-options">                          <button class="btn btn-default audio-source-add" title="Add audio playback source"><span class="glyphicon glyphicon-plus"></span></button>                      </div>                      <template id="audio-source-template"><div class="input-group audio-source"> -                        <div class="input-group-addon audio-source-prefix"></div> +                        <div class="input-group-addon generic-input-prefix"></div>                          <select class="form-control audio-source-select">                              <option value="jpod101">JapanesePod101</option>                              <option value="jpod101-alternate">JapanesePod101 (Alternate)</option> @@ -387,10 +387,6 @@                  <h3>Scanning Options</h3>                  <div class="checkbox"> -                    <label><input type="checkbox" id="middle-mouse-button-scan" data-setting="scanning.middleMouse"> Middle mouse button scans</label> -                </div> - -                <div class="checkbox">                      <label><input type="checkbox" id="touch-input-enabled" data-setting="scanning.touchInputEnabled"> Touch input enabled</label>                  </div> @@ -433,8 +429,27 @@                  </div>                  <div class="form-group"> -                    <label for="scan-modifier-key">Scan modifier key</label> -                    <select class="form-control" id="scan-modifier-key" data-setting="scanning.modifier"></select> +                    <label>Scan inputs</label> +                    <div class="scan-input-list" id="scan-input-list"></div> +                    <button class="btn btn-default" id="scan-input-add" title="Add scan input"><span class="glyphicon glyphicon-plus"></span></button> + +                    <template id="scan-input-template"><div class="scan-input"> +                        <table class="scan-input-table"><tbody> +                            <tr class="scan-input-include"> +                                <td class="scan-input-index-cell" rowspan="2"><div class="scan-input-index"></div></td> +                                <td class="scan-input-prefix-cell"><div class="scan-input-prefix">Include</div></td> +                                <td class="scan-input-input-cell"><input type="text" class="form-control scan-input-field" placeholder="No inputs"></td> +                                <td class="scan-input-mouse-button-cell"><button class="btn btn-default mouse-button" title="Mouse button"><span class="mouse-button-icon"></span></button></td> +                                <td class="scan-input-remove-button-cell"><button class="btn btn-danger scan-input-remove" title="Remove"><span class="glyphicon glyphicon-remove"></span></button></td> +                            </tr> +                            <tr class="scan-input-exclude"> +                                <td class="scan-input-prefix-cell"><div class="scan-input-prefix">Exclude</div></td> +                                <td class="scan-input-input-cell"><input type="text" class="form-control scan-input-field" placeholder="No inputs"></td> +                                <td class="scan-input-mouse-button-cell"><button class="btn btn-default mouse-button" title="Mouse button"><span class="mouse-button-icon"></span></button></td> +                                <td class="scan-input-empty-cell"></td> +                            </tr> +                        </tbody></table> +                    </div></template>                  </div>              </div> @@ -1176,6 +1191,7 @@          <script src="/bg/js/settings/popup-preview.js"></script>          <script src="/bg/js/settings/profiles.js"></script>          <script src="/bg/js/settings/profile-conditions-ui.js"></script> +        <script src="/bg/js/settings/scan-inputs-controller.js"></script>          <script src="/bg/js/settings/settings-controller.js"></script>          <script src="/bg/js/settings/storage.js"></script>          <script src="/mixed/js/dictionary-data-util.js"></script> diff --git a/ext/fg/js/frontend.js b/ext/fg/js/frontend.js index 3ddf0d25..8189b8ad 100644 --- a/ext/fg/js/frontend.js +++ b/ext/fg/js/frontend.js @@ -254,12 +254,12 @@ class Frontend {          await this.updateOptions();      } -    _onSearched({type, definitions, sentence, input: {cause}, textSource, optionsContext, error}) { +    _onSearched({type, definitions, sentence, input: {cause, empty}, textSource, optionsContext, error}) {          const scanningOptions = this._options.scanning;          if (error !== null) {              if (yomichan.isExtensionUnloaded) { -                if (textSource !== null && scanningOptions.modifier !== 'none') { +                if (textSource !== null && !empty) {                      this._showExtensionUnloaded(textSource);                  }              } else { @@ -321,10 +321,9 @@ class Frontend {          await this._updatePopup();          this._textScanner.setOptions({ +            inputs: scanningOptions.inputs,              deepContentScan: scanningOptions.deepDomScan,              selectText: scanningOptions.selectText, -            modifier: scanningOptions.modifier, -            useMiddleMouse: scanningOptions.middleMouse,              delay: scanningOptions.delay,              touchInputEnabled: scanningOptions.touchInputEnabled,              scanLength: scanningOptions.length, diff --git a/ext/mixed/js/display.js b/ext/mixed/js/display.js index 70b3895a..ea6b52c0 100644 --- a/ext/mixed/js/display.js +++ b/ext/mixed/js/display.js @@ -239,10 +239,9 @@ class Display extends EventDispatcher {              selectedParser: options.parsing.selectedParser,              termSpacing: options.parsing.termSpacing,              scanning: { +                inputs: scanning.inputs,                  deepContentScan: scanning.deepDomScan,                  selectText: scanning.selectText, -                modifier: scanning.modifier, -                useMiddleMouse: scanning.middleMouse,                  delay: scanning.delay,                  touchInputEnabled: scanning.touchInputEnabled,                  scanLength: scanning.length, diff --git a/ext/mixed/js/document-util.js b/ext/mixed/js/document-util.js index 0b72ff9a..58b0c759 100644 --- a/ext/mixed/js/document-util.js +++ b/ext/mixed/js/document-util.js @@ -188,6 +188,18 @@ class DocumentUtil {          return modifiers;      } +    static getActiveModifiersAndButtons(event) { +        const modifiers = this.getActiveModifiers(event); +        this._getActiveButtons(event, modifiers); +        return modifiers; +    } + +    static getActiveButtons(event) { +        const buttons = new Set(); +        this._getActiveButtons(event, buttons); +        return buttons; +    } +      static getKeyFromEvent(event) {          const key = event.key;          return (typeof key === 'string' ? (key.length === 1 ? key.toUpperCase() : key) : ''); @@ -299,6 +311,19 @@ class DocumentUtil {          return !(browser === 'firefox' || browser === 'firefox-mobile') || os === 'mac';      } +    static _getActiveButtons(event, set) { +        const {buttons} = event; +        if (typeof buttons === 'number') { +            for (let i = 0; i < 6; ++i) { +                const buttonFlag = (1 << i); +                if ((buttons & buttonFlag) !== 0) { +                    set.add(`mouse${i}`); +                } +            } +        } +        return set; +    } +      // Private      _setImposterStyle(style, propertyName, value) { diff --git a/ext/mixed/js/text-scanner.js b/ext/mixed/js/text-scanner.js index f3e99577..6eafc82c 100644 --- a/ext/mixed/js/text-scanner.js +++ b/ext/mixed/js/text-scanner.js @@ -43,13 +43,12 @@ class TextScanner extends EventDispatcher {          this._deepContentScan = false;          this._selectText = false; -        this._modifier = 'none'; -        this._useMiddleMouse = false;          this._delay = 0;          this._touchInputEnabled = false;          this._scanLength = 1;          this._sentenceExtent = 1;          this._layoutAwareScan = false; +        this._inputs = [];          this._enabled = false;          this._eventListeners = new EventListenerCollection(); @@ -94,19 +93,19 @@ class TextScanner extends EventDispatcher {          }      } -    setOptions({deepContentScan, selectText, modifier, useMiddleMouse, delay, touchInputEnabled, scanLength, sentenceExtent, layoutAwareScan}) { +    setOptions({inputs, deepContentScan, selectText, delay, touchInputEnabled, scanLength, sentenceExtent, layoutAwareScan}) { +        if (Array.isArray(inputs)) { +            this._inputs = inputs.map(({include, exclude}) => ({ +                include: this._getInputArray(include), +                exclude: this._getInputArray(exclude) +            })); +        }          if (typeof deepContentScan === 'boolean') {              this._deepContentScan = deepContentScan;          }          if (typeof selectText === 'boolean') {              this._selectText = selectText;          } -        if (typeof modifier === 'string') { -            this._modifier = modifier; -        } -        if (typeof useMiddleMouse === 'boolean') { -            this._useMiddleMouse = useMiddleMouse; -        }          if (typeof delay === 'number') {              this._delay = delay;          } @@ -184,7 +183,7 @@ class TextScanner extends EventDispatcher {      }      async search(textSource) { -        return await this._search(textSource, {cause: 'script'}); +        return await this._search(textSource, {cause: 'script', index: -1, empty: false});      }      // Private @@ -242,17 +241,14 @@ class TextScanner extends EventDispatcher {              return;          } -        const modifiers = DocumentUtil.getActiveModifiers(e); +        const modifiers = DocumentUtil.getActiveModifiersAndButtons(e);          this.trigger('activeModifiersChanged', {modifiers}); -        if (!( -            this._isScanningModifierPressed(this._modifier, e) || -            (this._useMiddleMouse && DocumentUtil.isMouseButtonDown(e, 'auxiliary')) -        )) { -            return; -        } +        const inputInfo = this._getMatchingInputGroup(modifiers); +        if (inputInfo === null) { return; } -        this._searchAtFromMouse(e.clientX, e.clientY); +        const {index, empty} = inputInfo; +        this._searchAtFromMouse(e.clientX, e.clientY, index, empty);      }      _onMouseDown(e) { @@ -276,7 +272,7 @@ class TextScanner extends EventDispatcher {      _onClick(e) {          if (this._searchOnClick) { -            this._searchAt(e.clientX, e.clientY, {cause: 'click'}); +            this._searchAt(e.clientX, e.clientY, {cause: 'click', index: -1, empty: false});          }          if (this._preventNextClick) { @@ -349,7 +345,7 @@ class TextScanner extends EventDispatcher {              return;          } -        this._searchAt(primaryTouch.clientX, primaryTouch.clientY, {cause: 'touchMove'}); +        this._searchAt(primaryTouch.clientX, primaryTouch.clientY, {cause: 'touchMove', index: -1, empty: false});          e.preventDefault(); // Disable scroll      } @@ -406,17 +402,6 @@ class TextScanner extends EventDispatcher {          ];      } -    _isScanningModifierPressed(scanningModifier, mouseEvent) { -        switch (scanningModifier) { -            case 'alt': return mouseEvent.altKey; -            case 'ctrl': return mouseEvent.ctrlKey; -            case 'shift': return mouseEvent.shiftKey; -            case 'meta': return mouseEvent.metaKey; -            case 'none': return true; -            default: return false; -        } -    } -      _getTouch(touchList, identifier) {          for (const touch of touchList) {              if (touch.identifier === identifier) { @@ -498,17 +483,17 @@ class TextScanner extends EventDispatcher {          }      } -    async _searchAtFromMouse(x, y) { +    async _searchAtFromMouse(x, y, inputIndex, inputEmpty) {          if (this._pendingLookup) { return; } -        if (this._modifier === 'none') { +        if (inputEmpty) {              if (!await this._scanTimerWait()) {                  // Aborted                  return;              }          } -        await this._searchAt(x, y, {cause: 'mouse'}); +        await this._searchAt(x, y, {cause: 'mouse', index: inputIndex, empty: inputEmpty});      }      async _searchAtFromTouchStart(x, y) { @@ -516,7 +501,7 @@ class TextScanner extends EventDispatcher {          const textSourceCurrentPrevious = this._textSourceCurrent !== null ? this._textSourceCurrent.clone() : null; -        await this._searchAt(x, y, {cause: 'touchStart'}); +        await this._searchAt(x, y, {cause: 'touchStart', index: -1, empty: false});          if (              this._textSourceCurrent !== null && @@ -527,4 +512,37 @@ class TextScanner extends EventDispatcher {              this._preventNextMouseDown = true;          }      } + +    _getMatchingInputGroup(modifiers) { +        let fallback = null; +        for (let i = 0, ii = this._inputs.length; i < ii; ++i) { +            const input = this._inputs[i]; +            const {include, exclude} = input; +            if (this._setHasAll(modifiers, include) && (exclude.length === 0 || !this._setHasAll(modifiers, exclude))) { +                if (include.length > 0) { +                    return {index: i, empty: false, input}; +                } else if (fallback === null) { +                    fallback = {index: i, empty: true, input}; +                } +            } +        } +        return fallback; +    } + +    _setHasAll(set, values) { +        for (const value of values) { +            if (!set.has(value)) { +                return false; +            } +        } +        return true; +    } + +    _getInputArray(value) { +        return ( +            typeof value === 'string' ? +            value.split(/[,;\s]+/).map((v) => v.trim().toLowerCase()).filter((v) => v.length > 0) : +            [] +        ); +    }  } |