22 januari 2026
Belangrijk : darktable herstelt automatisch de sneltoetsen instellingen wanneer het opstart.
Opdat je aangepaste bezetting niet verloren gaat, moet je dit automatische herstel deactiveren.
Ga naar darktable voorkeuren > diversen.
Schakel de optie uit ‘laad standaard sneltoetsen bij opstarten’ (indien beschikbaar/actief).
Installatie van het bestand:
sluit darktable
vervang het bestand shortcutrc in je configuratie map met de nieuwe versie.
:root {
–key-bg: #f0f0f0;
–key-bg-modifier: #e0e0e0;
–key-bg-assigned: #007bff;
–key-bg-selected: #28a745;
–key-border: #ccc;
–key-color: #333;
–key-color-assigned: #fff;
–key-color-selected: #fff;
}
body {
transition:
background-color 0.3s,
color 0.3s;
font-family:
-apple-system, BlinkMacSystemFont, “Segoe UI”, Roboto,
Helvetica, Arial, sans-serif;
}
body.drag-over {
outline: 3px dashed #3a86ff;
outline-offset: -10px;
}
.keyboard-container {
background: #f0f0f0;
padding: 15px;
border-radius: 8px;
margin-bottom: 15px;
display: grid;
grid-template-areas:
“fkeys fkeys fkeys”
“main main special”
“main main arrows”;
grid-template-columns: auto auto auto;
gap: 15px;
}
.keyboard-fkeys {
grid-area: fkeys;
}
.keyboard-main {
grid-area: main;
}
.keyboard-special {
grid-area: special;
}
.keyboard-arrows {
grid-area: arrows;
}
.keyboard-block {
display: flex;
flex-direction: column;
gap: 6px;
}
.key-row {
display: flex;
justify-content: flex-start;
gap: 6px;
}
.key {
border: 1px solid var(–key-border);
background: var(–key-bg);
color: var(–key-color);
border-radius: 5px;
padding: 8px 8px;
cursor: pointer;
position: relative;
transition: all 0.2s;
min-width: 35px;
height: 33px;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
box-sizing: border-box;
}
/* KORRIGIERT: z-index beim Hovern hinzufügen */
.key:hover {
filter: brightness(95%);
z-index: 101;
}
.key.modifier {
background: var(–key-bg-modifier);
}
.key.assigned {
background-color: var(–key-bg-assigned);
color: var(–key-color-assigned);
border-color: var(–key-bg-assigned);
}
.key.selected {
background-color: var(–key-bg-selected);
color: var(–key-color-selected);
border-color: var(–key-bg-selected);
transform: translateY(1px);
}
.key .tooltip-text {
visibility: hidden;
width: max-content;
max-width: 550px;
background-color: #333;
color: #fff;
text-align: left;
border-radius: 6px;
padding: 10px 12px;
position: absolute;
z-index: 100; /* KORRIGIERT: z-index erhöht */
bottom: 125%;
left: 50%;
transform: translateX(-50%);
opacity: 0;
transition: opacity 0.3s;
white-space: pre-wrap;
font-size: 14px;
line-height: 1.4;
pointer-events: none;
}
.key.assigned:hover .tooltip-text {
visibility: visible;
opacity: 1;
}
/* Special Key Sizes */
.key-spacer {
width: 11px;
background: transparent;
border: none;
}
.key-fkey {
min-width: 37px;
}
.key-special {
min-width: 60px;
}
.key-backspace {
width: 115px;
}
.key-tab {
width: 67px;
}
.key-caps {
width: 87px;
}
.key-return {
width: 83px;
}
.key-shift-l {
width: 95px;
}
.key-shift-r {
width: 96px;
}
.key-ctrl {
width: 60px;
}
.key-space {
width: 372px;
}
/* Table adjustments */
.actions-cell {
width: 1%;
white-space: nowrap;
text-align: right;
}
/* Dark Theme */
body.dark-mode {
background-color: #121212;
color: #e0e0e0;
–key-bg: #444;
–key-bg-modifier: #555;
–key-bg-assigned: #3a86ff;
–key-bg-selected: #20c997;
–key-border: #666;
–key-color: #e0e0e0;
}
.dark-mode .card,
.dark-mode .keyboard-container {
background-color: #1e1e1e;
border-color: #333;
}
.dark-mode .table {
color: #e0e0e0;
}
.dark-mode .table > :not(caption) > * > * {
border-bottom-width: 0;
}
.dark-mode thead th {
border-bottom: 1px solid var(–key-border);
}
.dark-mode .table-striped > tbody > tr:nth-of-type(odd) {
background-color: rgba(255, 255, 255, 0.05);
}
.dark-mode .form-control,
.dark-mode .form-select {
background-color: #2a2a2a;
color: #e0e0e0;
border-color: #525252;
}
.dark-mode .btn-primary {
background-color: #3a86ff;
border-color: #3a86ff;
}
.dark-mode .key-spacer {
background: transparent !important;
}
/* Bootstrap replacements */
.container {
max-width: 1000px;
margin: 0 auto;
padding: 0 15px;
}
.mt-5 {
margin-top: 3rem;
}
.d-flex {
display: flex;
}
.justify-content-between {
justify-content: space-between;
}
.align-items-center {
align-items: center;
}
.mb-4 {
margin-bottom: 1.5rem;
}
.btn {
display: inline-block;
padding: 0.375rem 0.75rem;
font-size: 12px;
line-height: 1.5;
border-radius: 0.25rem;
text-align: center;
vertical-align: middle;
cursor: pointer;
border: 1px solid transparent;
text-decoration: none;
user-select: none;
}
.btn-outline-secondary {
color: #6c757d;
border-color: #6c757d;
background-color: transparent;
}
.btn-outline-secondary:hover {
color: #fff;
background-color: #6c757d;
}
.card {
position: relative;
display: flex;
flex-direction: column;
min-width: 0;
word-wrap: break-word;
background-color: #fff;
background-clip: border-box;
border: 1px solid rgba(0, 0, 0, 0.125);
border-radius: 0.25rem;
}
.card-body {
flex: 1 1 auto;
padding: 1rem;
}
.d-none {
display: none !important;
}
.form-control {
display: block;
width: 95%;
padding: 0.375rem 0.75rem;
font-size: 14px;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
appearance: none;
border-radius: 0.25rem;
transition:
border-color 0.15s ease-in-out,
box-shadow 0.15s ease-in-out;
}
.mt-2 {
margin-top: 0.5rem;
}
.text-muted {
color: #6c757d !important;
}
.mb-3 {
margin-bottom: 1rem;
}
.form-label {
margin-bottom: 0.5rem;
}
.m-0 {
margin: 0;
}
.me-2 {
margin-right: 0.5rem;
}
.form-select {
display: block;
width: 100%;
padding: 0.375rem 2.25rem 0.375rem 0.75rem;
font-size: 12px;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-image: url(“data:image/svg+xml,%3csvg xmlns=’http://www.w3.org/2000/svg’ viewBox=’0 0 16 16′ fill=’%23343a40’%3e%3cpath fill-rule=’evenodd’ d=’M1.646 4.646a.5.5 0 0 1 .708 0L8 10.293l5.646-5.647a.5.5 0 0 1 .708.708l-6 6a.5.5 0 0 1-.708 0l-6-6a.5.5 0 0 1 0-.708z’/%3e%3c/svg%3e”);
background-repeat: no-repeat;
background-position: right 0.75rem center;
background-size: 16px 12px;
border: 1px solid #ced4da;
border-radius: 0.25rem;
appearance: none;
}
.d-inline {
display: inline !important;
}
.w-auto {
width: auto !important;
}
.ms-3 {
margin-left: 1rem;
}
.gap-2 {
gap: 0.5rem;
}
.btn-success {
color: #fff;
background-color: #28a745;
border-color: #28a745;
}
.btn-success:hover {
color: #fff;
background-color: #218838;
border-color: #1e7e34;
}
.btn-primary {
color: #fff;
background-color: #007bff;
border-color: #007bff;
}
.btn-primary:hover {
color: #fff;
background-color: #0069d9;
border-color: #0062cc;
}
.table {
width: 100%;
margin-bottom: 1rem;
color: #212529;
}
.table th,
.table td {
padding: 5px;
vertical-align: top;
border-top: 0px solid #dee2e4;
}
.table thead th {
vertical-align: bottom;
border-bottom: 2px solid #dee2e4;
}
.table tbody + tbody {
border-top: 2px solid #dee2e4;
}
.table-striped tbody tr:nth-of-type(odd) {
background-color: rgba(0, 0, 0, 0.05);
}
.text-center {
text-align: center !important;
}
.btn-sm {
padding: 0.25rem 0.5rem;
font-size: 12px;
line-height: 1.5;
border-radius: 0.2rem;
}
.btn-warning {
color: #212529;
background-color: #ffc107;
border-color: #ffc107;
}
.btn-warning:hover {
color: #212529;
background-color: #e0a800;
border-color: #d39e00;
}
.btn-danger {
color: #fff;
background-color: #dc3545;
border-color: #dc3545;
}
.btn-danger:hover {
color: #fff;
background-color: #c82333;
border-color: #bd2130;
}
.btn-secondary {
color: #fff;
background-color: #6c757d;
border-color: #6c757d;
}
.btn-secondary:hover {
color: #fff;
background-color: #5a6268;
border-color: #545b62;
}
Toggle Theme
QWERTZ
QWERTY
AZERTY
Load your shortcutsrc file to begin
editing. You can also drag & drop the file anywhere on
this page.
Filter Keyboard by Context:
All
Global
Lighttable
Darkroom
Search:
Sort by:
Default
By Key
By Value
Download
Add New Shortcut
Shortcut Key
Action (Value)
Context
Actions
const preloadedShortcutsrcContent = “;
// — CONFIGURATION —
const KEYBOARD_LAYOUTS = {
qwertz: {
fkeys: [
[
[“Escape”],
“SPACER”,
[“F1”, “F1”, “key-fkey”],
[“F2”, “F2”, “key-fkey”],
[“F3”, “F3”, “key-fkey”],
[“F4”, “F4”, “key-fkey”],
“SPACER”,
[“F5”, “F5”, “key-fkey”],
[“F6”, “F6”, “key-fkey”],
[“F7”, “F7”, “key-fkey”],
[“F8”, “F8”, “key-fkey”],
“SPACER”,
[“F9”, “F9”, “key-fkey”],
[“F10”, “F10”, “key-fkey”],
[“F11”, “F11”, “key-fkey”],
[“F12”, “F12”, “key-fkey”],
“SPACER”,
],
],
main: [
[
[“^”, “asciicircum”],
[“1”],
[“2”],
[“3”],
[“4”],
[“5”],
[“6”],
[“7”],
[“8”],
[“9”],
[“0”],
[“ß”, “ssharp”],
[“´”, “acute”],
[“BackSpace”, “BackSpace”, “key-backspace”],
],
[
[“Tab”, “Tab”, “key-tab”, “modifier”],
[“q”],
[“w”],
[“e”],
[“r”],
[“t”],
[“z”],
[“u”],
[“i”],
[“o”],
[“p”],
[“Ü”, “udiaeresis”],
[“+”, “plus”],
[“Return”, “Return”, “key-return”],
],
[
[“Caps”, “Caps_Lock”, “key-caps”, “modifier”],
[“a”],
[“s”],
[“d”],
[“f”],
[“g”],
[“h”],
[“j”],
[“k”],
[“l”],
[“Ö”, “odiaeresis”],
[“Ä”, “adiaeresis”],
[“#”, “numbersign”],
],
[
[“Shift”, “shift”, “key-shift-l”, “modifier”],
[“<", "<"],
["y"],
["x"],
["c"],
["v"],
["b"],
["n"],
["m"],
[",", "comma"],
[".", "period"],
["-", "minus"],
["Shift", "shift", "key-shift-r", "modifier"],
],
[
["Ctrl", "control", "key-ctrl", "modifier"],
["Win", "super", "", "modifier"],
["Alt", "alt", "", "modifier"],
["Space", "space", "key-space"],
["AltGr", "alt", "", "modifier"],
["Ctrl", "control", "key-ctrl", "modifier"],
],
],
special: [
[
["Print", "Print", "key-special"],
["Scroll", "Scroll", "key-special"],
["Pause", "Pause", "key-special"],
["None", "None", "key-special"],
],
[
["Ins", "Insert", "key-special"],
["Home", "Home", "key-special"],
["P_Up", "Page_Up", "key-special"],
],
[
["Delete", "Delete", "key-special"],
["End", "End", "key-special"],
["P_Down", "Page_Down", "key-special"],
],
],
arrows: [
[
["Left", "Left", "key-special"],
["Up", "Up", "key-special"],
["Down", "Down", "key-special"],
["Right", "Right", "key-special"],
],
],
},
qwerty: {
fkeys: [
[
["Escape"],
"SPACER",
["F1", "F1", "key-fkey"],
["F2", "F2", "key-fkey"],
["F3", "F3", "key-fkey"],
["F4", "F4", "key-fkey"],
"SPACER",
["F5", "F5", "key-fkey"],
["F6", "F6", "key-fkey"],
["F7", "F7", "key-fkey"],
["F8", "F8", "key-fkey"],
"SPACER",
["F9", "F9", "key-fkey"],
["F10", "F10", "key-fkey"],
["F11", "F11", "key-fkey"],
["F12", "F12", "key-fkey"],
"SPACER",
],
],
main: [
[
["~", "asciitilde"],
["1"],
["2"],
["3"],
["4"],
["5"],
["6"],
["7"],
["8"],
["9"],
["0"],
["-", "minus"],
["=", "equal"],
["BackSpace", "BackSpace", "key-backspace"],
],
[
["Tab", "Tab", "key-tab", "modifier"],
["q"],
["w"],
["e"],
["r"],
["t"],
["y"],
["u"],
["i"],
["o"],
["p"],
["[", "bracketleft"],
["]", "bracketright"],
["\\", "backslash"],
],
[
["Caps", "Caps_Lock", "key-caps", "modifier"],
["a"],
["s"],
["d"],
["f"],
["g"],
["h"],
["j"],
["k"],
["l"],
[";", "semicolon"],
["'", "quote"],
["Enter", "Return", "key-return"],
],
[
["Shift", "shift", "key-shift-l", "modifier"],
[",", "comma"],
[".", "period"],
["/", "slash"],
["z"],
["x"],
["c"],
["v"],
["b"],
["n"],
["m"],
["Shift", "shift", "key-shift-r", "modifier"],
],
[
["Ctrl", "control", "key-ctrl", "modifier"],
["Win", "super", "", "modifier"],
["Alt", "alt", "", "modifier"],
["Space", "space", "key-space"],
["AltGr", "alt", "", "modifier"],
["Ctrl", "control", "key-ctrl", "modifier"],
],
],
special: [
[
["Print", "Print", "key-special"],
["Scroll", "Scroll", "key-special"],
["Pause", "Pause", "key-special"],
["None", "None", "key-special"],
],
[
["Insert", "Insert", "key-special"],
["Home", "Home", "key-special"],
["P_Up", "Page_Up", "key-special"],
],
[
["Del", "Delete", "key-special"],
["End", "End", "key-special"],
["P_Down", "Page_Down", "key-special"],
],
],
arrows: [
[
["Left", "Left", "key-special"],
["Up", "Up", "key-special"],
["Down", "Down", "key-special"],
["Right", "Right", "key-special"],
],
],
},
azerty: {
fkeys: [
[
["Escape"],
"SPACER",
["F1", "F1", "key-fkey"],
["F2", "F2", "key-fkey"],
["F3", "F3", "key-fkey"],
["F4", "F4", "key-fkey"],
"SPACER",
["F5", "F5", "key-fkey"],
["F6", "F6", "key-fkey"],
["F7", "F7", "key-fkey"],
["F8", "F8", "key-fkey"],
"SPACER",
["F9", "F9", "key-fkey"],
["F10", "F10", "key-fkey"],
["F11", "F11", "key-fkey"],
["F12", "F12", "key-fkey"],
"SPACER",
],
],
main: [
[
["²", "asciicircum"],
["1"],
["2"],
["3"],
["4"],
["5"],
["6"],
["7"],
["8"],
["9"],
["0"],
["°", "degree"],
["+", "plus"],
["BackSpace", "BackSpace", "key-backspace"],
],
[
["Tab", "Tab", "key-tab", "modifier"],
["a"],
["z"],
["e"],
["r"],
["t"],
["y"],
["u"],
["i"],
["o"],
["p"],
["^", "asciicircum"],
["$", "dollar"],
["Return", "Return", "key-return"],
],
[
["Caps", "Caps_Lock", "key-caps", "modifier"],
["q"],
["s"],
["d"],
["f"],
["g"],
["h"],
["j"],
["k"],
["l"],
["m"],
["ù", "grave"],
["*", "asterisk"],
],
[
["Shift", "shift", "key-shift-l", "modifier"],
[" {
if (!str) return “”;
return str
.replace(/&/g, “&”)
.replace(//g, “>”)
.replace(/”/g, “"”)
.replace(/’/g, “'”);
};
const escapeAttr = (str) => {
if (!str) return “”;
return str.replace(/”/g, “"”);
};
const normalizeKey = (key) => {
if (!key) return “”;
return key
.trim()
.replace(/^;+|;+$/g, “”)
.replace(/;{2,}/g, “;”);
};
const getContext = (value) => {
if (!value || !value.includes(“/”)) {
return “global”;
}
const [prefix, view] = value.split(“/”);
if (prefix === “views”) {
if (view && view.startsWith(“darkroom”)) return “darkroom”;
if (view && view.startsWith(“lighttable”))
return “lighttable”;
return “lighttable”; // Default for ‘views’ is lighttable
}
if ([“actions”, “iop”, “utility”].includes(prefix)) {
return “darkroom”;
}
return “global”;
};
const parseFileContent = (content) => {
const lines = content.split(“\n”);
const parsedShortcuts = [];
nextId = 0;
lines.forEach((line) => {
line = line.trim();
if (line.startsWith(“#”) || !line) return;
if (line.includes(“=”)) {
const [key, value] = line.split(“=”, 2);
if (key) {
const trimmedValue = value.trim();
parsedShortcuts.push({
id: nextId++,
key: key.trim(),
value: trimmedValue,
context: getContext(trimmedValue),
});
}
}
});
return parsedShortcuts;
};
const processAndRenderContent = (content, sourceName) => {
try {
shortcuts = parseFileContent(content);
fileInfo.textContent = `Loaded from “${sourceName}”. Found ${shortcuts.length} shortcuts.`;
mainContent.classList.remove(“d-none”);
renderAll();
} catch (err) {
console.error(“Error processing file content:”, err);
fileInfo.textContent = `Error: Could not process content from “${sourceName}”. Check console for details.`;
mainContent.classList.add(“d-none”);
}
};
const renderAll = () => {
renderKeyboard();
renderTable();
renderFilterInfo();
};
const getKeyboardMap = () => {
const keyMap = {};
let shortcutsToDisplay = shortcuts;
if (currentKeyboardContext !== “all”) {
shortcutsToDisplay = shortcuts.filter(
(s) => s.context === currentKeyboardContext,
);
}
shortcutsToDisplay.forEach((entry) => {
const parts = entry.key
.toLowerCase()
.split(“;”)
.map((k) => k.trim());
parts.forEach((part) => {
if (!part) return;
if (!keyMap[part]) keyMap[part] = [];
keyMap[part].push(
`[${entry.context}] ${escapeHTML(entry.key)} = ${escapeHTML(entry.value)}`,
);
});
});
return keyMap;
};
// — RENDER FUNCTIONS —
const renderKeyboard = () => {
const keyMap = getKeyboardMap();
keyboardContainer.innerHTML = “”;
const createKeyElement = (keyInfo) => {
if (!keyInfo)
return ‘
‘;
if (keyInfo === “SPACER”)
return ‘
‘;
const [
displayText,
dataKey = displayText.toLowerCase(),
cssClass = “”,
modifierClass = “”,
] = keyInfo;
const keyId = dataKey.toLowerCase();
if (!keyId) return “”;
const isAssigned = keyMap[keyId];
const isSelected = currentFilterKeys.includes(keyId);
const tooltip = isAssigned
? `
${keyMap[keyId].join(“\n”)} `
: “”;
return `
${displayText}${tooltip}
`;
};
Object.entries(KEYBOARD_LAYOUTS[currentLayout]).forEach(
([blockName, layout]) => {
const blockContainer = document.createElement(“div”);
blockContainer.classList.add(
“keyboard-block”,
`keyboard-${blockName}`,
);
layout.forEach((row) => {
const rowContainer = document.createElement(“div”);
rowContainer.classList.add(“key-row”);
row.forEach((keyInfo) => {
rowContainer.innerHTML +=
createKeyElement(keyInfo);
});
blockContainer.appendChild(rowContainer);
});
keyboardContainer.appendChild(blockContainer);
},
);
};
const renderTable = () => {
let displayData = […shortcuts];
if (currentSearchTerm) {
const lowerCaseSearchTerm = currentSearchTerm.toLowerCase();
displayData = displayData.filter(
(entry) =>
entry.key
.toLowerCase()
.includes(lowerCaseSearchTerm) ||
entry.value
.toLowerCase()
.includes(lowerCaseSearchTerm) ||
entry.context
.toLowerCase()
.includes(lowerCaseSearchTerm),
);
}
if (currentFilterKeys.length > 0) {
displayData = displayData.filter((entry) => {
const entryKeys = entry.key
.toLowerCase()
.split(“;”)
.map((k) => k.trim());
return currentFilterKeys.every((filterKey) =>
entryKeys.includes(filterKey),
);
});
}
switch (currentSortBy) {
case “key”:
displayData.sort((a, b) => a.key.localeCompare(b.key));
break;
case “value”:
displayData.sort((a, b) =>
a.value.localeCompare(b.value),
);
break;
}
tableBody.innerHTML =
displayData.length === 0 &&
!document.querySelector(“.add-new-row”)
? ‘
No shortcuts found for the current filter. ‘
: displayData
.map(
(entry) =>
`
${entry.context} Update Delete `,
)
.join(“”);
};
const renderFilterInfo = () => {
if (currentFilterKeys.length > 0) {
filterInfo.innerHTML = `Filter active:
${currentFilterKeys.join(” + “)} Clear Filter `;
} else {
filterInfo.innerHTML = “”;
}
};
// — EVENT HANDLERS —
fileInput.addEventListener(“change”, (event) => {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
processAndRenderContent(e.target.result, file.name);
};
reader.readAsText(file);
});
sortSelect.addEventListener(“change”, (e) => {
currentSortBy = e.target.value;
renderTable();
});
searchInput.addEventListener(“input”, (e) => {
currentSearchTerm = e.target.value;
renderTable();
});
downloadButton.addEventListener(“click”, () => {
const fileContent = shortcuts
.map((entry) => `${entry.key}=${entry.value}`)
.join(“\n”);
const blob = new Blob([fileContent], {
type: “text/plain;charset=utf-8”,
});
const a = document.createElement(“a”);
a.href = URL.createObjectURL(blob);
a.download = “shortcutsrc”;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
});
addShortcutButton.addEventListener(“click”, () => {
if (document.querySelector(“.add-new-row”)) return;
const newRow = document.createElement(“tr”);
newRow.classList.add(“add-new-row”);
newRow.innerHTML = `
– Save Cancel `;
tableBody.prepend(newRow);
});
keyboardContainer.addEventListener(“click”, (e) => {
const keyElement = e.target.closest(“.filter-key”);
if (!keyElement) return;
const filterKey = keyElement.dataset.key;
if (e.shiftKey) {
const index = currentFilterKeys.indexOf(filterKey);
if (index > -1) {
currentFilterKeys.splice(index, 1);
} else {
currentFilterKeys.push(filterKey);
}
} else {
currentFilterKeys =
currentFilterKeys.length === 1 &&
currentFilterKeys[0] === filterKey
? []
: [filterKey];
}
renderAll();
});
contextFilterButtons.addEventListener(“click”, (e) => {
const button = e.target.closest(“button”);
if (!button) return;
currentKeyboardContext = button.dataset.context;
contextFilterButtons
.querySelectorAll(“button”)
.forEach((btn) => {
btn.classList.remove(
“btn-primary”,
“btn-outline-secondary”,
);
if (btn === button) {
btn.classList.add(“btn-primary”);
} else {
btn.classList.add(“btn-outline-secondary”);
}
});
renderKeyboard();
});
filterInfo.addEventListener(“click”, (e) => {
if (e.target.id === “clear-filter-btn”) {
currentFilterKeys = [];
renderAll();
}
});
tableBody.addEventListener(“click”, (e) => {
const row = e.target.closest(“tr”);
if (!row) return;
const handleUpdate = (isNew) => {
const keyInput = row.querySelector(“td:nth-child(1) input”);
const valueInput = row.querySelector(
“td:nth-child(2) input”,
);
const newKey = normalizeKey(keyInput.value);
const newValue = valueInput.value.trim();
if (!newKey || !newValue) {
alert(“Key and Value cannot be empty.”);
return;
}
const newContext = getContext(newValue);
const entryId = isNew ? -1 : parseInt(row.dataset.id, 10);
const conflict = shortcuts.find(
(s) =>
s.id !== entryId &&
s.key === newKey &&
(s.context === newContext ||
s.context === “global” ||
newContext === “global”),
);
if (conflict) {
alert(
`Conflict found! The key ‘${newKey}’ is already assigned for the context ‘${newContext}’ (Action: ${conflict.value}). Global shortcuts cannot be overwritten in a specific context and vice versa with the same key.`,
);
return;
}
if (isNew) {
shortcuts.push({
id: nextId++,
key: newKey,
value: newValue,
context: newContext,
});
} else {
const entry = shortcuts.find((s) => s.id === entryId);
if (entry) {
entry.key = newKey;
entry.value = newValue;
entry.context = newContext;
}
}
renderAll();
};
if (e.target.classList.contains(“save-new-btn”))
handleUpdate(true);
if (e.target.classList.contains(“update-btn”))
handleUpdate(false);
if (e.target.classList.contains(“cancel-new-btn”)) row.remove();
if (e.target.classList.contains(“delete-btn”)) {
const entryId = parseInt(row.dataset.id, 10);
const entryIndex = shortcuts.findIndex(
(s) => s.id === entryId,
);
if (
entryIndex > -1 &&
confirm(
`Really delete shortcut “${shortcuts[entryIndex].key}”?`,
)
) {
shortcuts.splice(entryIndex, 1);
renderAll();
}
}
});
const loadFromUrl = async () => {
const urlParams = new URLSearchParams(window.location.search);
const srcUrl = urlParams.get(“src”);
if (srcUrl) {
const fileInputCard = fileInput.closest(“.card”);
if (fileInputCard) {
fileInputCard.classList.add(“d-none”);
}
try {
fileInfo.textContent = `Loading from URL: ${srcUrl}`;
const response = await fetch(srcUrl);
if (!response.ok) {
throw new Error(
`HTTP error! status: ${response.status}`,
);
}
const content = await response.text();
processAndRenderContent(content, srcUrl);
} catch (err) {
console.error(
“Error fetching or processing file from URL:”,
err,
);
fileInfo.textContent = `Error: Could not load file from URL. ${err.message}`;
mainContent.classList.add(“d-none”);
if (fileInputCard) {
fileInputCard.classList.remove(“d-none”);
}
}
}
};
document.body.addEventListener(“dragover”, (event) => {
event.preventDefault();
document.body.classList.add(“drag-over”);
});
document.body.addEventListener(“dragleave”, () => {
document.body.classList.remove(“drag-over”);
});
document.body.addEventListener(“drop”, (event) => {
event.preventDefault();
document.body.classList.remove(“drag-over”);
const file = event.dataTransfer.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
processAndRenderContent(e.target.result, file.name);
};
reader.readAsText(file);
});
// — INITIALIZATION —
document.addEventListener(“DOMContentLoaded”, () => {
if (
preloadedShortcutsrcContent &&
preloadedShortcutsrcContent.trim() !== “”
) {
const fileInputCard = fileInput.closest(“.card”);
if (fileInputCard) {
fileInputCard.classList.add(“d-none”);
}
processAndRenderContent(
preloadedShortcutsrcContent,
“pre-loaded variable”,
);
} else {
loadFromUrl();
}
// Standard-Theme auf “dark” setzen
document.body.classList.add(“dark-mode”);
localStorage.setItem(“theme”, “dark”);
// QWERTZ als Standard-Tastaturbelegung
keyboardLayoutSelect.value = “qwertz”;
currentLayout = “qwertz”;
renderAll();
});
// — THEME —
document
.getElementById(“theme-toggle”)
.addEventListener(“click”, () => {
document.body.classList.toggle(“dark-mode”);
localStorage.setItem(
“theme”,
document.body.classList.contains(“dark-mode”)
? “dark”
: “light”,
);
});
if (localStorage.getItem(“theme”) === “dark”) {
document.body.classList.add(“dark-mode”);
}
// — KEYBOARD LAYOUT —
keyboardLayoutSelect.addEventListener(“change”, (e) => {
currentLayout = e.target.value;
renderAll();
});