aboutsummaryrefslogtreecommitdiff
path: root/ext/js/input/hotkey-help-controller.js
blob: bd30590295b4a8ad57ba6c8abef6ea94e691dbd0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/*
 * Copyright (C) 2023       Yomitan Authors
 * Copyright (C) 2021-2022  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
 * HotkeyUtil
 */

class HotkeyHelpController {
    constructor() {
        this._hotkeyUtil = new HotkeyUtil();
        this._localActionHotseys = new Map();
        this._globalActionHotkeys = new Map();
        this._replacementPattern = /\{0\}/g;
    }

    async prepare() {
        const {platform: {os}} = await yomichan.api.getEnvironmentInfo();
        this._hotkeyUtil.os = os;
        await this._setupGlobalCommands(this._globalActionHotkeys);
    }

    setOptions(options) {
        const hotkeys = options.inputs.hotkeys;
        const hotkeyMap = this._localActionHotseys;
        hotkeyMap.clear();
        for (const {enabled, action, key, modifiers} of hotkeys) {
            if (!enabled || key === null || action === '' || hotkeyMap.has(action)) { continue; }
            hotkeyMap.set(action, this._hotkeyUtil.getInputDisplayValue(key, modifiers));
        }
    }

    setupNode(node) {
        const globalPrexix = 'global:';
        const replacementPattern = this._replacementPattern;
        for (const node2 of node.querySelectorAll('[data-hotkey]')) {
            const data = JSON.parse(node2.dataset.hotkey);
            let [action, attributes, values] = data;
            if (!Array.isArray(attributes)) { attributes = [attributes]; }
            const multipleValues = Array.isArray(values);

            const actionIsGlobal = action.startsWith(globalPrexix);
            if (actionIsGlobal) { action = action.substring(globalPrexix.length); }

            const defaultAttributeValues = this._getDefaultAttributeValues(node2, data, attributes);

            const hotkey = (actionIsGlobal ? this._globalActionHotkeys : this._localActionHotseys).get(action);

            for (let i = 0, ii = attributes.length; i < ii; ++i) {
                const attribute = attributes[i];
                let value = null;
                if (typeof hotkey !== 'undefined') {
                    value = (multipleValues ? values[i] : values);
                    value = value.replace(replacementPattern, hotkey);
                } else {
                    value = defaultAttributeValues[i];
                }

                if (typeof value === 'string') {
                    node2.setAttribute(attribute, value);
                } else {
                    node2.removeAttribute(attribute);
                }
            }
        }
    }

    // Private

    async _setupGlobalCommands(commandMap) {
        const commands = await new Promise((resolve, reject) => {
            if (!(isObject(chrome.commands) && typeof chrome.commands.getAll === 'function')) {
                resolve([]);
                return;
            }

            chrome.commands.getAll((result) => {
                const e = chrome.runtime.lastError;
                if (e) {
                    reject(new Error(e.message));
                } else {
                    resolve(result);
                }
            });
        });

        commandMap.clear();
        for (const {name, shortcut} of commands) {
            if (shortcut.length === 0) { continue; }
            const {key, modifiers} = this._hotkeyUtil.convertCommandToInput(shortcut);
            commandMap.set(name, this._hotkeyUtil.getInputDisplayValue(key, modifiers));
        }
        return commandMap;
    }

    _getDefaultAttributeValues(node, data, attributes) {
        if (data.length > 3) {
            return data[3];
        }

        const defaultAttributeValues = [];
        for (let i = 0, ii = attributes.length; i < ii; ++i) {
            const attribute = attributes[i];
            const value = node.hasAttribute(attribute) ? node.getAttribute(attribute) : null;
            defaultAttributeValues.push(value);
        }
        data[3] = defaultAttributeValues;
        node.dataset.hotkey = JSON.stringify(data);
        return defaultAttributeValues;
    }
}