24 december 2025

Belangrijk: DarkTable herstelt automatisch de standaardsnelkoppelingen wanneer deze start.

Zodat uw aangepaste bezetting niet door elkaar wordt gehaald, moet u deze automatische deactiveren.

  • Ga naar DarkTable Instellingen > Ander.
  • Schakel de optie uit “Herstel toetscombinaties bij opstarten” (indien beschikbaar/actief).

Installatie van het bestand:

  • Afdronk donkertafel
  • Vervang het bestand SneltoetsenRc in uw configuratiemap met de nieuwe versie.
QWERTZ QWERTY AZERTY

Load your shortcutsrc file to begin editing. You can also drag & drop the file anywhere on this page.

Default By Key By Value
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} `, ) .join(“”); }; const renderFilterInfo = () => { if (currentFilterKeys.length > 0) { filterInfo.innerHTML = `Filter active: ${currentFilterKeys.join(” + “)} `; } 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 = ` `; 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(); });

Geef een reactie