/** * Copyright JS Foundation and other contributors, http://js.foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. **/ RED.keyboard = (function() { var isMac = /Mac/i.test(window.navigator.platform); var handlers = {}; var partialState; var keyMap = { "left":37, "up":38, "right":39, "down":40, "escape":27, "enter": 13, "backspace": 8, "delete": 46, "space": 32, ";":186, "=":187, ",":188, "-":189, ".":190, "/":191, "\\":220, "'":222, "?":191 // <- QWERTY specific } var metaKeyCodes = { 16:true, 17:true, 18: true, 91:true, 93: true } var actionToKeyMap = {} var defaultKeyMap = {}; // FF generates some different keycodes because reasons. var firefoxKeyCodeMap = { 59:186, 61:187, 173:189 } function migrateOldKeymap() { if ('localStorage' in window && window['localStorage'] !== null) { var oldKeyMap = localStorage.getItem("keymap"); if (oldKeyMap !== null) { localStorage.removeItem("keymap"); var currentEditorSettings = RED.settings.get('editor') || {}; currentEditorSettings.keymap = JSON.parse(oldKeyMap); RED.settings.set('editor',currentEditorSettings); } } } function init() { // Migrate from pre-0.18 migrateOldKeymap(); var currentEditorSettings = RED.settings.get('editor') || {}; var userKeymap = currentEditorSettings.keymap || {}; $.getJSON("red/keymap.json",function(data) { for (var scope in data) { if (data.hasOwnProperty(scope)) { var keys = data[scope]; for (var key in keys) { if (keys.hasOwnProperty(key)) { if (!userKeymap.hasOwnProperty(keys[key])) { addHandler(scope,key,keys[key],false); } defaultKeyMap[keys[key]] = { scope:scope, key:key, user:false }; } } } } for (var action in userKeymap) { if (userKeymap.hasOwnProperty(action)) { var obj = userKeymap[action]; if (obj.hasOwnProperty('key')) { addHandler(obj.scope, obj.key, action, true); } } } }); RED.userSettings.add({ id:'keyboard', title: RED._("keyboard.keyboard"), get: getSettingsPane, focus: function() { setTimeout(function() { $("#user-settings-tab-keyboard-filter").focus(); },200); } }) } function revertToDefault(action) { var currentAction = actionToKeyMap[action]; if (currentAction) { removeHandler(currentAction.key); } if (defaultKeyMap.hasOwnProperty(action)) { var obj = defaultKeyMap[action]; addHandler(obj.scope, obj.key, action, false); } } function parseKeySpecifier(key) { var parts = key.toLowerCase().split("-"); var modifiers = {}; var keycode; var blank = 0; for (var i=0;i 1) { return null; } else { keycode = parts[i].toUpperCase().charCodeAt(0); } break; } } return [keycode,modifiers]; } function resolveKeyEvent(evt) { var slot = partialState||handlers; if (evt.ctrlKey || evt.metaKey) { slot = slot.ctrl; } if (slot && evt.shiftKey) { slot = slot.shift; } if (slot && evt.altKey) { slot = slot.alt; } var keyCode = firefoxKeyCodeMap[evt.keyCode] || evt.keyCode; if (slot && slot[keyCode]) { var handler = slot[keyCode]; if (!handler.scope) { if (partialState) { partialState = null; return resolveKeyEvent(evt); } else if (Object.keys(handler).length > 0) { partialState = handler; evt.preventDefault(); return null; } else { return null; } } else if (handler.scope && handler.scope !== "*") { var target = evt.target; while (target.nodeName !== 'BODY' && target.id !== handler.scope) { target = target.parentElement; } if (target.nodeName === 'BODY') { handler = null; } } partialState = null; return handler; } else if (partialState) { partialState = null; return resolveKeyEvent(evt); } } d3.select(window).on("keydown",function() { if (metaKeyCodes[d3.event.keyCode]) { return; } var handler = resolveKeyEvent(d3.event); if (handler && handler.ondown) { if (typeof handler.ondown === "string") { RED.actions.invoke(handler.ondown); } else { handler.ondown(); } d3.event.preventDefault(); } }); function addHandler(scope,key,modifiers,ondown) { var mod = modifiers; var cbdown = ondown; if (typeof modifiers == "function" || typeof modifiers === "string") { mod = {}; cbdown = modifiers; } var keys = []; var i=0; if (typeof key === 'string') { if (typeof cbdown === 'string') { actionToKeyMap[cbdown] = {scope:scope,key:key}; if (typeof ondown === 'boolean') { actionToKeyMap[cbdown].user = ondown; } } var parts = key.split(" "); for (i=0;i'; function formatKey(key) { var formattedKey = isMac?key.replace(/ctrl-?/,"⌘"):key; formattedKey = isMac?formattedKey.replace(/alt-?/,"⌥"):key; formattedKey = formattedKey.replace(/shift-?/,"⇧") formattedKey = formattedKey.replace(/left/,"←") formattedKey = formattedKey.replace(/up/,"↑") formattedKey = formattedKey.replace(/right/,"→") formattedKey = formattedKey.replace(/down/,"↓") return ''+formattedKey.split(" ").join(' ')+''; } function validateKey(key) { key = key.trim(); var parts = key.split(" "); for (i=0;i').attr('placeholder',RED._('keyboard.unassigned')).val(object.key||"").appendTo(key); keyInput.on("keyup",function(e) { if (e.keyCode === 13) { return endEditShortcut(); } var currentVal = $(this).val(); currentVal = currentVal.trim(); var valid = (currentVal === "" || RED.keyboard.validateKey(currentVal)); $(this).toggleClass("input-error",!valid); }) var scopeSelect = $('').appendTo(scope); scopeSelect.i18n(); scopeSelect.val(object.scope||'*'); var div = $('
').appendTo(scope); var okButton = $('').appendTo(div); var revertButton = $('').appendTo(div); okButton.click(function(e) { e.stopPropagation(); endEditShortcut(); }); revertButton.click(function(e) { e.stopPropagation(); RED.keyboard.revertToDefault(object.id); container.empty(); container.removeClass('keyboard-shortcut-entry-expanded'); var shortcut = RED.keyboard.getShortcut(object.id); var userKeymap = RED.settings.get('keymap') || {}; var currentEditorSettings = RED.settings.get('editor') || {}; var userKeymap = currentEditorSettings.keymap || {}; userKeymap[object.id] = null; currentEditorSettings.keymap = userKeymap; RED.settings.set('editor',currentEditorSettings); var obj = { id:object.id, scope:shortcut?shortcut.scope:undefined, key:shortcut?shortcut.key:undefined, user:shortcut?shortcut.user:undefined } buildShortcutRow(container,obj); }) keyInput.focus(); } } function endEditShortcut(cancel) { var container = $('.keyboard-shortcut-entry-expanded'); if (container.length === 1) { var object = container.data('data'); var keyInput = container.find(".keyboard-shortcut-entry-key input"); var scopeSelect = container.find(".keyboard-shortcut-entry-scope select"); if (!cancel) { var key = keyInput.val().trim(); var scope = scopeSelect.val(); var valid = (key === "" || RED.keyboard.validateKey(key)); if (valid) { var current = RED.keyboard.getShortcut(object.id); if ((!current && key) || (current && (current.scope !== scope || current.key !== key))) { var keyDiv = container.find(".keyboard-shortcut-entry-key"); var scopeDiv = container.find(".keyboard-shortcut-entry-scope"); keyDiv.empty(); scopeDiv.empty(); if (object.key) { RED.keyboard.remove(object.key,true); } container.find(".keyboard-shortcut-entry-text i").css("opacity",1); if (key === "") { keyDiv.parent().addClass("keyboard-shortcut-entry-unassigned"); keyDiv.append($('').text(RED._('keyboard.unassigned')) ); delete object.key; delete object.scope; } else { keyDiv.parent().removeClass("keyboard-shortcut-entry-unassigned"); keyDiv.append(RED.keyboard.formatKey(key)) $("").text(scope).appendTo(scopeDiv); object.key = key; object.scope = scope; RED.keyboard.add(object.scope,object.key,object.id,true); } var currentEditorSettings = RED.settings.get('editor') || {}; var userKeymap = currentEditorSettings.keymap || {}; userKeymap[object.id] = RED.keyboard.getShortcut(object.id); currentEditorSettings.keymap = userKeymap; RED.settings.set('editor',currentEditorSettings); } } } keyInput.remove(); scopeSelect.remove(); $('.keyboard-shortcut-edit').remove(); container.removeClass('keyboard-shortcut-entry-expanded'); } } function buildShortcutRow(container,object) { var item = $('
').appendTo(container); container.data('data',object); var text = object.id.replace(/(^.+:([a-z]))|(-([a-z]))/g,function() { if (arguments[5] === 0) { return arguments[2].toUpperCase(); } else { return " "+arguments[4].toUpperCase(); } }); var label = $('
').addClass("keyboard-shortcut-entry-text").text(text).appendTo(item); var user = $('').prependTo(label); if (!object.user) { user.css("opacity",0); } var key = $('
').appendTo(item); if (object.key) { key.append(RED.keyboard.formatKey(object.key)); } else { item.addClass("keyboard-shortcut-entry-unassigned"); key.append($('').text(RED._('keyboard.unassigned')) ); } var scope = $('
').appendTo(item); $("").text(object.scope === '*'?'global':object.scope||"").appendTo(scope); container.click(editShortcut); } function getSettingsPane() { var pane = $('
'); $('
'+ '
'+ '
'+ '
'+ '
').appendTo(pane); pane.find("input").searchBox({ delay: 100, change: function() { var filterValue = $(this).val().trim(); if (filterValue === "") { shortcutList.editableList('filter', null); } else { filterValue = filterValue.replace(/\s/g,""); shortcutList.editableList('filter', function(data) { return data.id.toLowerCase().replace(/^.*:/,"").replace("-","").indexOf(filterValue) > -1; }) } } }); var shortcutList = $('
    ').css({ position: "absolute", top: "32px", bottom: "0", left: "0", right: "0" }).appendTo(pane).editableList({ addButton: false, scrollOnAdd: false, addItem: function(container,i,object) { buildShortcutRow(container,object); }, }); var shortcuts = RED.actions.list(); shortcuts.sort(function(A,B) { var Aid = A.id.replace(/^.*:/,"").replace(/[ -]/g,"").toLowerCase(); var Bid = B.id.replace(/^.*:/,"").replace(/[ -]/g,"").toLowerCase(); return Aid.localeCompare(Bid); }); shortcuts.forEach(function(s) { shortcutList.editableList('addItem',s); }); return pane; } return { init: init, add: addHandler, remove: removeHandler, getShortcut: function(actionName) { return actionToKeyMap[actionName]; }, revertToDefault: revertToDefault, formatKey: formatKey, validateKey: validateKey } })();