mirror of
https://github.com/Jermolene/TiddlyWiki5
synced 2024-12-27 18:40:28 +00:00
f33c7e2aef
* allow global keyboard shortcuts to override all other shortcuts by providing a special field * rework the global shortcuts taking priority * replace bool option with options object in KeyboardManager's keydown handler * update keyboard shortcut documentation to add information about the new priority setting * add support for priority global keyboard shortcuts to code mirror * update the feature's docs to point out it was/will be introduced in 5.2.4 * rollback unnecessary change
388 lines
10 KiB
JavaScript
388 lines
10 KiB
JavaScript
/*\
|
|
title: $:/core/modules/keyboard.js
|
|
type: application/javascript
|
|
module-type: global
|
|
|
|
Keyboard handling utilities
|
|
|
|
\*/
|
|
(function(){
|
|
|
|
/*jslint node: true, browser: true */
|
|
/*global $tw: false */
|
|
"use strict";
|
|
|
|
var namedKeys = {
|
|
"cancel": 3,
|
|
"help": 6,
|
|
"backspace": 8,
|
|
"tab": 9,
|
|
"clear": 12,
|
|
"return": 13,
|
|
"enter": 13,
|
|
"pause": 19,
|
|
"escape": 27,
|
|
"space": 32,
|
|
"page_up": 33,
|
|
"page_down": 34,
|
|
"end": 35,
|
|
"home": 36,
|
|
"left": 37,
|
|
"up": 38,
|
|
"right": 39,
|
|
"down": 40,
|
|
"printscreen": 44,
|
|
"insert": 45,
|
|
"delete": 46,
|
|
"0": 48,
|
|
"1": 49,
|
|
"2": 50,
|
|
"3": 51,
|
|
"4": 52,
|
|
"5": 53,
|
|
"6": 54,
|
|
"7": 55,
|
|
"8": 56,
|
|
"9": 57,
|
|
"firefoxsemicolon": 59,
|
|
"firefoxequals": 61,
|
|
"a": 65,
|
|
"b": 66,
|
|
"c": 67,
|
|
"d": 68,
|
|
"e": 69,
|
|
"f": 70,
|
|
"g": 71,
|
|
"h": 72,
|
|
"i": 73,
|
|
"j": 74,
|
|
"k": 75,
|
|
"l": 76,
|
|
"m": 77,
|
|
"n": 78,
|
|
"o": 79,
|
|
"p": 80,
|
|
"q": 81,
|
|
"r": 82,
|
|
"s": 83,
|
|
"t": 84,
|
|
"u": 85,
|
|
"v": 86,
|
|
"w": 87,
|
|
"x": 88,
|
|
"y": 89,
|
|
"z": 90,
|
|
"numpad0": 96,
|
|
"numpad1": 97,
|
|
"numpad2": 98,
|
|
"numpad3": 99,
|
|
"numpad4": 100,
|
|
"numpad5": 101,
|
|
"numpad6": 102,
|
|
"numpad7": 103,
|
|
"numpad8": 104,
|
|
"numpad9": 105,
|
|
"multiply": 106,
|
|
"add": 107,
|
|
"separator": 108,
|
|
"subtract": 109,
|
|
"decimal": 110,
|
|
"divide": 111,
|
|
"f1": 112,
|
|
"f2": 113,
|
|
"f3": 114,
|
|
"f4": 115,
|
|
"f5": 116,
|
|
"f6": 117,
|
|
"f7": 118,
|
|
"f8": 119,
|
|
"f9": 120,
|
|
"f10": 121,
|
|
"f11": 122,
|
|
"f12": 123,
|
|
"f13": 124,
|
|
"f14": 125,
|
|
"f15": 126,
|
|
"f16": 127,
|
|
"f17": 128,
|
|
"f18": 129,
|
|
"f19": 130,
|
|
"f20": 131,
|
|
"f21": 132,
|
|
"f22": 133,
|
|
"f23": 134,
|
|
"f24": 135,
|
|
"firefoxminus": 173,
|
|
"semicolon": 186,
|
|
"equals": 187,
|
|
"comma": 188,
|
|
"dash": 189,
|
|
"period": 190,
|
|
"slash": 191,
|
|
"backquote": 192,
|
|
"openbracket": 219,
|
|
"backslash": 220,
|
|
"closebracket": 221,
|
|
"quote": 222
|
|
};
|
|
|
|
function KeyboardManager(options) {
|
|
var self = this;
|
|
options = options || "";
|
|
// Save the named key hashmap
|
|
this.namedKeys = namedKeys;
|
|
// Create a reverse mapping of code to keyname
|
|
this.keyNames = [];
|
|
$tw.utils.each(namedKeys,function(keyCode,name) {
|
|
self.keyNames[keyCode] = name.substr(0,1).toUpperCase() + name.substr(1);
|
|
});
|
|
// Save the platform-specific name of the "meta" key
|
|
this.metaKeyName = $tw.platform.isMac ? "cmd-" : "win-";
|
|
this.shortcutKeysList = [], // Stores the shortcut-key descriptors
|
|
this.shortcutActionList = [], // Stores the corresponding action strings
|
|
this.shortcutParsedList = []; // Stores the parsed key descriptors
|
|
this.shortcutPriorityList = []; // Stores the parsed shortcut priority
|
|
this.lookupNames = ["shortcuts"];
|
|
this.lookupNames.push($tw.platform.isMac ? "shortcuts-mac" : "shortcuts-not-mac")
|
|
this.lookupNames.push($tw.platform.isWindows ? "shortcuts-windows" : "shortcuts-not-windows");
|
|
this.lookupNames.push($tw.platform.isLinux ? "shortcuts-linux" : "shortcuts-not-linux");
|
|
this.updateShortcutLists(this.getShortcutTiddlerList());
|
|
$tw.wiki.addEventListener("change",function(changes) {
|
|
self.handleShortcutChanges(changes);
|
|
});
|
|
}
|
|
|
|
/*
|
|
Return an array of keycodes for the modifier keys ctrl, shift, alt, meta
|
|
*/
|
|
KeyboardManager.prototype.getModifierKeys = function() {
|
|
return [
|
|
16, // Shift
|
|
17, // Ctrl
|
|
18, // Alt
|
|
20, // CAPS LOCK
|
|
91, // Meta (left)
|
|
93, // Meta (right)
|
|
224 // Meta (Firefox)
|
|
]
|
|
};
|
|
|
|
/*
|
|
Parses a key descriptor into the structure:
|
|
{
|
|
keyCode: numeric keycode
|
|
shiftKey: boolean
|
|
altKey: boolean
|
|
ctrlKey: boolean
|
|
metaKey: boolean
|
|
}
|
|
Key descriptors have the following format:
|
|
ctrl+enter
|
|
ctrl+shift+alt+A
|
|
*/
|
|
KeyboardManager.prototype.parseKeyDescriptor = function(keyDescriptor,options) {
|
|
var components = keyDescriptor.split(/\+|\-/),
|
|
info = {
|
|
keyCode: 0,
|
|
shiftKey: false,
|
|
altKey: false,
|
|
ctrlKey: false,
|
|
metaKey: false
|
|
};
|
|
for(var t=0; t<components.length; t++) {
|
|
var s = components[t].toLowerCase(),
|
|
c = s.charCodeAt(0);
|
|
// Look for modifier keys
|
|
if(s === "ctrl") {
|
|
info.ctrlKey = true;
|
|
} else if(s === "shift") {
|
|
info.shiftKey = true;
|
|
} else if(s === "alt") {
|
|
info.altKey = true;
|
|
} else if(s === "meta" || s === "cmd" || s === "win") {
|
|
info.metaKey = true;
|
|
}
|
|
// Replace named keys with their code
|
|
if(this.namedKeys[s]) {
|
|
info.keyCode = this.namedKeys[s];
|
|
}
|
|
}
|
|
if(options.keyDescriptor) {
|
|
info.keyDescriptor = options.keyDescriptor;
|
|
}
|
|
if(info.keyCode) {
|
|
return info;
|
|
} else {
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/*
|
|
Parse a list of key descriptors into an array of keyInfo objects. The key descriptors can be passed as an array of strings or a space separated string
|
|
*/
|
|
KeyboardManager.prototype.parseKeyDescriptors = function(keyDescriptors,options) {
|
|
var self = this;
|
|
options = options || {};
|
|
options.stack = options.stack || [];
|
|
var wiki = options.wiki || $tw.wiki;
|
|
if(typeof keyDescriptors === "string" && keyDescriptors === "") {
|
|
return [];
|
|
}
|
|
if(!$tw.utils.isArray(keyDescriptors)) {
|
|
keyDescriptors = keyDescriptors.split(" ");
|
|
}
|
|
var result = [];
|
|
$tw.utils.each(keyDescriptors,function(keyDescriptor) {
|
|
// Look for a named shortcut
|
|
if(keyDescriptor.substr(0,2) === "((" && keyDescriptor.substr(-2,2) === "))") {
|
|
if(options.stack.indexOf(keyDescriptor) === -1) {
|
|
options.stack.push(keyDescriptor);
|
|
var name = keyDescriptor.substring(2,keyDescriptor.length - 2),
|
|
lookupName = function(configName) {
|
|
var keyDescriptors = wiki.getTiddlerText("$:/config/" + configName + "/" + name);
|
|
if(keyDescriptors) {
|
|
options.keyDescriptor = keyDescriptor;
|
|
result.push.apply(result,self.parseKeyDescriptors(keyDescriptors,options));
|
|
}
|
|
};
|
|
$tw.utils.each(self.lookupNames,function(platformDescriptor) {
|
|
lookupName(platformDescriptor);
|
|
});
|
|
}
|
|
} else {
|
|
result.push(self.parseKeyDescriptor(keyDescriptor,options));
|
|
}
|
|
});
|
|
return result;
|
|
};
|
|
|
|
KeyboardManager.prototype.getPrintableShortcuts = function(keyInfoArray) {
|
|
var self = this,
|
|
result = [];
|
|
$tw.utils.each(keyInfoArray,function(keyInfo) {
|
|
if(keyInfo) {
|
|
result.push((keyInfo.ctrlKey ? "ctrl-" : "") +
|
|
(keyInfo.shiftKey ? "shift-" : "") +
|
|
(keyInfo.altKey ? "alt-" : "") +
|
|
(keyInfo.metaKey ? self.metaKeyName : "") +
|
|
(self.keyNames[keyInfo.keyCode]));
|
|
}
|
|
});
|
|
return result;
|
|
}
|
|
|
|
KeyboardManager.prototype.checkKeyDescriptor = function(event,keyInfo) {
|
|
return keyInfo &&
|
|
event.keyCode === keyInfo.keyCode &&
|
|
event.shiftKey === keyInfo.shiftKey &&
|
|
event.altKey === keyInfo.altKey &&
|
|
event.ctrlKey === keyInfo.ctrlKey &&
|
|
event.metaKey === keyInfo.metaKey;
|
|
};
|
|
|
|
KeyboardManager.prototype.checkKeyDescriptors = function(event,keyInfoArray) {
|
|
return (this.getMatchingKeyDescriptor(event,keyInfoArray) !== null);
|
|
};
|
|
|
|
KeyboardManager.prototype.getMatchingKeyDescriptor = function(event,keyInfoArray) {
|
|
for(var t=0; t<keyInfoArray.length; t++) {
|
|
if(this.checkKeyDescriptor(event,keyInfoArray[t])) {
|
|
return keyInfoArray[t];
|
|
}
|
|
}
|
|
return null;
|
|
};
|
|
|
|
KeyboardManager.prototype.getEventModifierKeyDescriptor = function(event) {
|
|
return event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey ? "ctrl" :
|
|
event.shiftKey && !event.ctrlKey && !event.altKey && !event.metaKey ? "shift" :
|
|
event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey ? "ctrl-shift" :
|
|
event.altKey && !event.shiftKey && !event.ctrlKey && !event.metaKey ? "alt" :
|
|
event.altKey && event.shiftKey && !event.ctrlKey && !event.metaKey ? "alt-shift" :
|
|
event.altKey && event.ctrlKey && !event.shiftKey && !event.metaKey ? "ctrl-alt" :
|
|
event.altKey && event.shiftKey && event.ctrlKey && !event.metaKey ? "ctrl-alt-shift" :
|
|
event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey ? "meta" :
|
|
event.metaKey && event.ctrlKey && !event.shiftKey && !event.altKey ? "meta-ctrl" :
|
|
event.metaKey && event.ctrlKey && event.shiftKey && !event.altKey ? "meta-ctrl-shift" :
|
|
event.metaKey && event.ctrlKey && event.shiftKey && event.altKey ? "meta-ctrl-alt-shift" : "normal";
|
|
};
|
|
|
|
KeyboardManager.prototype.getShortcutTiddlerList = function() {
|
|
return $tw.wiki.getTiddlersWithTag("$:/tags/KeyboardShortcut");
|
|
};
|
|
|
|
KeyboardManager.prototype.updateShortcutLists = function(tiddlerList) {
|
|
this.shortcutTiddlers = tiddlerList;
|
|
for(var i=0; i<tiddlerList.length; i++) {
|
|
var title = tiddlerList[i],
|
|
tiddlerFields = $tw.wiki.getTiddler(title).fields;
|
|
this.shortcutKeysList[i] = tiddlerFields.key !== undefined ? tiddlerFields.key : undefined;
|
|
this.shortcutActionList[i] = tiddlerFields.text;
|
|
this.shortcutParsedList[i] = this.shortcutKeysList[i] !== undefined ? this.parseKeyDescriptors(this.shortcutKeysList[i]) : undefined;
|
|
this.shortcutPriorityList[i] = tiddlerFields.priority === "yes" ? true : false;
|
|
}
|
|
};
|
|
|
|
/*
|
|
event: the keyboard event object
|
|
options:
|
|
onlyPriority: true if only priority global shortcuts should be invoked
|
|
*/
|
|
KeyboardManager.prototype.handleKeydownEvent = function(event, options) {
|
|
options = options || {};
|
|
var key, action;
|
|
for(var i=0; i<this.shortcutTiddlers.length; i++) {
|
|
if(options.onlyPriority && this.shortcutPriorityList[i] !== true) {
|
|
continue;
|
|
}
|
|
|
|
if(this.shortcutParsedList[i] !== undefined && this.checkKeyDescriptors(event,this.shortcutParsedList[i])) {
|
|
key = this.shortcutParsedList[i];
|
|
action = this.shortcutActionList[i];
|
|
}
|
|
}
|
|
if(key !== undefined) {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
$tw.rootWidget.invokeActionString(action,$tw.rootWidget,event);
|
|
return true;
|
|
}
|
|
return false;
|
|
};
|
|
|
|
KeyboardManager.prototype.detectNewShortcuts = function(changedTiddlers) {
|
|
var shortcutConfigTiddlers = [],
|
|
handled = false;
|
|
$tw.utils.each(this.lookupNames,function(platformDescriptor) {
|
|
var descriptorString = "$:/config/" + platformDescriptor + "/";
|
|
Object.keys(changedTiddlers).forEach(function(configTiddler) {
|
|
var configString = configTiddler.substr(0, configTiddler.lastIndexOf("/") + 1);
|
|
if(configString === descriptorString) {
|
|
shortcutConfigTiddlers.push(configTiddler);
|
|
handled = true;
|
|
}
|
|
});
|
|
});
|
|
if(handled) {
|
|
return $tw.utils.hopArray(changedTiddlers,shortcutConfigTiddlers);
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
KeyboardManager.prototype.handleShortcutChanges = function(changedTiddlers) {
|
|
var newList = this.getShortcutTiddlerList();
|
|
var hasChanged = $tw.utils.hopArray(changedTiddlers,this.shortcutTiddlers) ? true :
|
|
($tw.utils.hopArray(changedTiddlers,newList) ? true :
|
|
(this.detectNewShortcuts(changedTiddlers))
|
|
);
|
|
// Re-cache shortcuts if something changed
|
|
if(hasChanged) {
|
|
this.updateShortcutLists(newList);
|
|
}
|
|
};
|
|
|
|
exports.KeyboardManager = KeyboardManager;
|
|
|
|
})();
|