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 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222
|
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
const table = document.getElementById("table");
function setTextContent(element, content) {
if (content.startsWith("customkeys-")) {
element.setAttribute("data-l10n-id", content);
} else {
element.textContent = content;
}
}
function notifyUpdate() {
window.dispatchEvent(new CustomEvent("CustomKeysUpdate"));
}
async function buildTable() {
const keys = await RPMSendQuery("CustomKeys:GetKeys");
for (const category in keys) {
const tbody = document.createElement("tbody");
table.append(tbody);
let row = document.createElement("tr");
row.className = "category";
tbody.append(row);
let cell = document.createElement("td");
row.append(cell);
cell.setAttribute("colspan", 5);
const heading = document.createElement("h1");
setTextContent(heading, category);
cell.append(heading);
const categoryKeys = keys[category];
for (const keyId in categoryKeys) {
row = document.createElement("tr");
row.className = "key";
tbody.append(row);
row.setAttribute("data-id", keyId);
cell = document.createElement("th");
const key = categoryKeys[keyId];
setTextContent(cell, key.title);
row.append(cell);
cell = document.createElement("td");
cell.textContent = key.shortcut;
row.append(cell);
cell = document.createElement("td");
let button = document.createElement("button");
button.className = "change";
button.setAttribute("data-l10n-id", "customkeys-change");
cell.append(button);
let label = document.createElement("label");
label.className = "newLabel";
let span = document.createElement("span");
span.setAttribute("data-l10n-id", "customkeys-new-key");
label.append(span);
let input = document.createElement("input");
input.className = "new";
label.append(input);
cell.append(label);
row.append(cell);
cell = document.createElement("td");
button = document.createElement("button");
button.className = "clear";
button.setAttribute("data-l10n-id", "customkeys-clear");
cell.append(button);
row.append(cell);
cell = document.createElement("td");
button = document.createElement("button");
button.className = "reset";
button.setAttribute("data-l10n-id", "customkeys-reset");
cell.append(button);
row.append(cell);
updateKey(row, key);
}
}
notifyUpdate();
}
function updateKey(row, data) {
row.children[1].textContent = data.shortcut;
row.classList.toggle("customized", data.isCustomized);
row.classList.toggle("assigned", !!data.shortcut);
}
// Returns false if the assignment should be cancelled.
async function maybeHandleConflict(data) {
for (const row of table.querySelectorAll(".key")) {
if (data.shortcut != row.children[1].textContent) {
continue; // Not a conflict.
}
const conflictId = row.dataset.id;
if (conflictId == data.id) {
// We're trying to assign this key to the shortcut it is already
// assigned to. We don't need to do anything.
return false;
}
const conflictDesc = row.children[0].textContent;
if (
window.confirm(
await document.l10n.formatValue("customkeys-conflict-confirm", {
conflict: conflictDesc,
})
)
) {
// Clear the conflicting key.
const newData = await RPMSendQuery("CustomKeys:ClearKey", conflictId);
updateKey(row, newData);
return true;
}
return false;
}
return true;
}
async function onAction(event) {
const row = event.target.closest("tr");
const keyId = row.dataset.id;
if (event.target.className == "reset") {
Glean.browserCustomkeys.actions.reset.add();
const data = await RPMSendQuery("CustomKeys:GetDefaultKey", keyId);
if (await maybeHandleConflict(data)) {
const newData = await RPMSendQuery("CustomKeys:ResetKey", keyId);
updateKey(row, newData);
notifyUpdate();
}
} else if (event.target.className == "change") {
Glean.browserCustomkeys.actions.change.add();
// The "editing" class will cause the Change button to be replaced by a
// labelled input for the new key.
row.classList.add("editing");
// We need to listen for keys in the parent process because we want to
// intercept reserved keys, which we can't do in the content process.
RPMSendAsyncMessage("CustomKeys:CaptureKey", true);
row.querySelector(".new").focus();
} else if (event.target.className == "clear") {
Glean.browserCustomkeys.actions.clear.add();
const newData = await RPMSendQuery("CustomKeys:ClearKey", keyId);
updateKey(row, newData);
notifyUpdate();
}
}
async function onKey({ data }) {
const input = document.activeElement;
const row = input.closest("tr");
data.id = row.dataset.id;
if (data.isModifier) {
// This is a modifier. Display it, but don't assign yet. We assign when the
// main key is pressed (below).
input.value = data.modifierString;
// Select the input's text so screen readers will report it.
input.select();
return;
}
if (await maybeHandleConflict(data)) {
const newData = await RPMSendQuery("CustomKeys:ChangeKey", data);
updateKey(row, newData);
}
RPMSendAsyncMessage("CustomKeys:CaptureKey", false);
row.classList.remove("editing");
row.querySelector(".change").focus();
notifyUpdate();
}
function onFocusLost(event) {
if (event.target.className == "new") {
// If the input loses focus, cancel editing of the key.
RPMSendAsyncMessage("CustomKeys:CaptureKey", false);
const row = event.target.closest("tr");
row.classList.remove("editing");
// Clear any modifiers that were displayed, ready for the next edit.
event.target.value = "";
}
}
function onSearchInput(event) {
const query = event.target.value.toLowerCase();
for (const row of table.querySelectorAll(".key")) {
row.hidden =
query && !row.children[0].textContent.toLowerCase().includes(query);
}
for (const tbody of table.tBodies) {
// Show a category only if it has at least 1 shown key.
tbody.hidden = !tbody.querySelector(".key:not([hidden])");
}
notifyUpdate();
}
async function onResetAll() {
Glean.browserCustomkeys.actions.reset_all.add();
if (
!window.confirm(
await document.l10n.formatValue("customkeys-reset-all-confirm")
)
) {
return;
}
await RPMSendQuery("CustomKeys:ResetAll");
const keysByCat = await RPMSendQuery("CustomKeys:GetKeys");
const keysById = {};
for (const category in keysByCat) {
const categoryKeys = keysByCat[category];
for (const keyId in categoryKeys) {
keysById[keyId] = categoryKeys[keyId];
}
}
for (const row of table.querySelectorAll(".key")) {
const data = keysById[row.dataset.id];
if (data) {
updateKey(row, data);
}
}
notifyUpdate();
}
buildTable();
table.addEventListener("click", onAction);
RPMAddMessageListener("CustomKeys:CapturedKey", onKey);
table.addEventListener("focusout", onFocusLost);
document.getElementById("search").addEventListener("input", onSearchInput);
document.getElementById("resetAll").addEventListener("click", onResetAll);
Glean.browserCustomkeys.opened.add();
|