started project revival -- updated keyboard.js and split out the bindings...

Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
Alex A. Naanou 2014-02-04 00:23:11 +04:00
parent 2b8081ce9d
commit 95301411e4
3 changed files with 402 additions and 260 deletions

View File

@ -110,6 +110,9 @@ CKEDITOR.disableAutoInline = true
<script src="platform.js"></script>
<!-- XXX load this as early as possible... -->
<script src="keybindings.js"></script>
<script>
function showInOverlay(text){
@ -237,144 +240,6 @@ function handleFileSelect(evt) {
}
var KEYBOARD_CONFIG = {
'.overlay': {
title: 'Overlay mode.',
doc: '',
ignore: '*',
Esc: function(){
removeOverlay()
return false
},
},
'.editor:not(.inline-editor-mode)': {
title: 'Editor mode.',
doc: '',
'0': function(){
var n = getPageNumber()
if(togglePageView('?') == 'on'){
setMagazineScale(getPageTargetScale(1))
} else {
setMagazineScale(getPageTargetScale(PAGES_IN_RIBBON))
}
setCurrentPage(n)
},
Esc: '0',
'=': function(){
var n = getPageNumber()
setMagazineScale(Math.min(
getMagazineScale() * 1.2,
getPageTargetScale(1)))
setCurrentPage(n)
},
'-': function(){
var n = getPageNumber()
setMagazineScale(Math.max(
getMagazineScale() * 0.8,
getPageTargetScale(PAGES_IN_RIBBON*2)))
setCurrentPage(n)
},
'O': {
// load...
// XXX needs testing...
'ctrl': function(){
showInOverlay('<h1>Open Issue</h1>'+
'<input type="file" id="upload" name="file" multiple onchange="handleFileSelect(event)"/>')
},
},
'S': {
// save...
// XXX needs testing...
'ctrl': function(){
showInOverlay('<h1>Save Issue</h1>'+
'<p>NOTE: this download will not include the actual '+
'images. at this point, images should be added manually.</p>'+
'<p><a id="data_download" href="#">Download</a></p>')
// setup the data...
$(generateMagazineDownload)
},
},
// ?
'/': function(){
showInOverlay('<h1>Controls</h1>'+
'<p>NOTE: this section is a stub.<p>'+
'<table width="100%">'+
'<tr><td align="right" width="45%"><b> C-O </b></td><td> Load issue from file. </td></tr>'+
'<tr><td align="right"><b> C-S </b></td><td> Save issue to file. </td></tr>'+
'<tr><td align="right"><b> - / + </b></td><td> Zoom out/in. </td></tr>'+
'<tr><td align="right"><b> 0 </b></td><td> Set default zoom level. </td></tr>'+
'</table>')
},
},
// ignore all keys except Esc here...
'.inline-editor-mode': {
title: 'Inline editor mode.',
doc: '',
//ignore: '*'
Esc: function(){
$(':focus').blur()
return false
},
},
'.chrome:not(.inline-editor-mode)': {
title: 'Global bindings.',
doc: '',
Esc: function(){
if(toggleEditor('?') == 'on'){
toggleEditor('off')
} else {
togglePageView('off')
}
},
Home: firstPage,
End: lastPage,
Left: {
default: function(){ prevPage() },
shift: prevBookmark,
ctrl: prevArticle,
},
Right: {
default: function(){ nextPage() },
shift: nextBookmark,
ctrl: nextArticle,
},
Space: {
default: 'Right',
shift: 'Left'
},
//Tab: 'Space',
Tab: function(){ return false },
Enter: function(){ togglePageView('on') },
// combined navigation with actions..
Up: function(){ togglePageView() },
Down: function(){ togglePageView() },
F: function(){ togglePageFitMode() },
B: {
default: function(){ toggleBookmark() },
ctrl: function(){ toggleThemes() },
},
// XXX this should not be in the production viewer...
E: function(){ toggleEditor() },
},
}
$(document).ready(function(){
// this is to fix some sort of bug baking things align in a wrong

View File

@ -1,94 +1,173 @@
/*********************************************************************/
// NOTE: use String.fromCharCode(code)...
// list of keys to be ignored by handler but still handled by the browser...
var keybindings = {
/*
// global bindings...
'*': {
title: 'Global',
var KEYBOARD_CONFIG = {
'Global bindings': {
doc: 'NOTE: binding priority is the same as the order of sections '+
'on this page.',
pattern: '*',
F4: {
alt: doc('Close viewer',
function(){
if(window.require != null){
require('nw.gui')
.Window.get().close()
return false
}
}),
},
F5: doc('Full reload viewer',
function(){
if(window.require != null){
require('nw.gui')
.Window.get().reload()
return false
}
}),
F12: doc('Show devTools',
function(){
if(window.require != null){
require('nw.gui')
.Window.get().showDevTools()
return false
}
}),
},
'.overlay': {
title: 'Overlay mode.',
doc: '',
ignore: [
116, // F5
122, // F11
123, // F12
8 // BkSp
],
ignore: '*',
// ignore the modifiers (shift, alt, ctrl, caps)...
16: function(){},
17: 16,
18: 16,
20: 16, // Caps Lock
Esc: function(){
removeOverlay()
return false
},
},
'.editor:not(.inline-editor-mode)': {
title: 'Editor mode.',
doc: '',
// overlay...
'.overlay-mode': {
title: 'Overlay mode',
doc: 'Overlay mode key bindings.',
'0': function(){
var n = getPageNumber()
if(togglePageView('?') == 'on'){
setMagazineScale(getPageTargetScale(1))
} else {
setMagazineScale(getPageTargetScale(PAGES_IN_RIBBON))
}
setCurrentPage(n)
},
Esc: '0',
ignore: [
33, // PgUp
34, // PgDown
37, // Left
39, // Right
36, // Home
32, // Space
35, // End
38, // Up
40, // Down
],
'=': function(){
var n = getPageNumber()
setMagazineScale(Math.min(
getMagazineScale() * 1.2,
getPageTargetScale(1)))
setCurrentPage(n)
},
'-': function(){
var n = getPageNumber()
setMagazineScale(Math.max(
getMagazineScale() * 0.8,
getPageTargetScale(PAGES_IN_RIBBON*2)))
setCurrentPage(n)
},
'O': {
// load...
// XXX needs testing...
'ctrl': function(){
showInOverlay('<h1>Open Issue</h1>'+
'<input type="file" id="upload" name="file" multiple onchange="handleFileSelect(event)"/>')
},
},
'S': {
// save...
// XXX needs testing...
'ctrl': function(){
showInOverlay('<h1>Save Issue</h1>'+
'<p>NOTE: this download will not include the actual '+
'images. at this point, images should be added manually.</p>'+
'<p><a id="data_download" href="#">Download</a></p>')
// setup the data...
$(generateMagazineDownload)
},
},
// ?
'/': function(){
showInOverlay('<h1>Controls</h1>'+
'<p>NOTE: this section is a stub.<p>'+
'<table width="100%">'+
'<tr><td align="right" width="45%"><b> C-O </b></td><td> Load issue from file. </td></tr>'+
'<tr><td align="right"><b> C-S </b></td><td> Save issue to file. </td></tr>'+
'<tr><td align="right"><b> - / + </b></td><td> Zoom out/in. </td></tr>'+
'<tr><td align="right"><b> 0 </b></td><td> Set default zoom level. </td></tr>'+
'</table>')
},
},
*/
// ignore all keys here...
// ignore all keys except Esc here...
'.inline-editor-mode': {
ignore: '*'
},
// everything except overlays...
'.viewer:not(.inline-editor-mode)': {
title: 'Ribbon and Viewer',
title: 'Inline editor mode.',
doc: '',
// navigation...
36: goToMagazineCover, // Home
219: 36, // [
35: goToMagazineEnd, // End
221: 35, // ]
37: {
'default': prevPage, // Right
'ctrl': prevArticle, // ctrl-Right
'shift': prevBookmark // shift-Right
},
188: 37, // <
39: {
'default': nextPage, // Left
'ctrl': nextArticle, // ctrl-Left
'shift': nextBookmark // shift-Left
},
32: {
'default': nextPage, // Space
'shift': prevPage // shift-Space
},
190: 39, // >
//ignore: '*'
Esc: function(){
$(':focus').blur()
return false
},
},
66: {
'default': toggleBookmark, // B
'ctrl': function(){toggleThemes()}, // ctrl-B
},
'.chrome:not(.inline-editor-mode)': {
title: 'Global bindings.',
doc: '',
Esc: function(){
if(toggleEditor('?') == 'on'){
toggleEditor('off')
} else {
togglePageView('off')
}
},
Home: firstPage,
End: lastPage,
Left: {
default: function(){ prevPage() },
shift: prevBookmark,
ctrl: prevArticle,
},
Right: {
default: function(){ nextPage() },
shift: nextBookmark,
ctrl: nextArticle,
},
Space: {
default: 'Right',
shift: 'Left'
},
//Tab: 'Space',
Tab: function(){ return false },
Enter: function(){ togglePageView('on') },
// combined navigation with actions..
38: function(){togglePageView()}, // Up
40: function(){togglePageView()}, // Down
Up: function(){ togglePageView() },
Down: function(){ togglePageView() },
13: function(){togglePageView('on')}, // Enter
27: function(){togglePageView('off')}, // Esc
}
}
F: function(){ togglePageFitMode() },
B: {
default: function(){ toggleBookmark() },
ctrl: function(){ toggleThemes() },
},
// XXX this should not be in the production viewer...
E: function(){ toggleEditor() },
},
}
/*********************************************************************/

View File

@ -10,6 +10,17 @@
/*********************************************************************/
// Attributes to be ignored my the key handler...
//
// These are used for system tasks.
var KEYBOARD_SYSTEM_ATTRS = [
'doc',
'title',
'ignore',
'pattern'
]
// Neither _SPECIAL_KEYS nor _KEY_CODES are meant for direct access, use
// toKeyName(<code>) and toKeyCode(<name>) for a more uniform access.
//
@ -109,22 +120,49 @@ function toKeyCode(c){
// documentation wrapper...
function doc(text, func){
func = func == null ? function(){return true}: func
func = !func ? function(){return true}: func
func.doc = text
return func
}
// Build or normalize a modifier string.
//
// Acceptable argument sets:
// - none -> ""
// - true, false, true -> "ctrl+shift"
// - true, false -> "ctrl"
// - [true, false] -> "ctrl"
// - 'alt+shift' -> "alt+shift"
// - 'shift - alt' -> "alt+shift"
//
function normalizeModifiers(c, a, s){
if(c != null && c.constructor.name == 'Array'){
a = c[1]
s = c[2]
c = c[0]
}
if(typeof(c) == typeof('str')){
var modifiers = c
} else {
var modifiers = (c ? 'ctrl' : '')
+ (a ? ' alt' : '')
+ (s ? ' shift' : '')
}
// build the dormalized modifier string...
var res = /ctrl/i.test(modifiers) ? 'ctrl' : ''
res += /alt/i.test(modifiers) ? (res != '' ? '+alt' : 'alt') : ''
res += /shift/i.test(modifiers) ? (res != '' ? '+shift' : 'shift') : ''
return res
}
/* Key handler getter
*
* For doc on format see makeKeyboardHandler(...)
*
* modes can be:
* - 'any' (default) - Get list of all applicable handlers up until
* the first applicable ignore.
* - 'all' - Get ALL handlers, including ignores
* - <mode> - Get handlers for an explicit mode
*
* modifiers can be:
* - '' (default) - No modifiers
* - '?' - Return list of applicable modifiers per mode
@ -135,6 +173,15 @@ function doc(text, func){
* Ex:
* 'ctrl+shift'
* NOTE: 'shift+ctrl' is wrong.
* NOTE: normalizeModifiers(...) can be used as
* a reference, if in doubt.
*
* modes can be:
* - 'any' (default) - Get list of all applicable handlers up until
* the first applicable ignore.
* - 'all' - Get ALL handlers, including ignores
* - <mode> - Get handlers for an explicit mode
*
*
* This will also resolve several shifted keys by name, for example:
* 'shift-/' is the same as '?', and either can be used, but the shorter
@ -148,24 +195,41 @@ function doc(text, func){
* }
*
*
* <handler> can be:
* - <function> - handler
* - [<doc>, <function>]
* - lisp-style handler
* - 'IGNORE' - if mode is 'all' and key is in .ignore
* - [<function>, 'IGNORE NEXT']
* - if mode is 'all' and the key is both in .ignore
* and a handler is defined in the current section
* NOTE: in this case if this mode matches, all
* the subsequent handlers will get ignored
* in normal modes...
*
*
* NOTE: adding a key to the ignore list has the same effect as returning
* false form it's handler in the same context.
* NOTE: it is not possible to do a shift-? as it is already shifted.
* NOTE: if a key is not handled in a mode, that mode will not be
* present in the resulting object.
* NOTE: this will not unwrap lisp-style (see below) handlers.
* NOTE: modes are prioritized by order of occurrence.
* NOTE: modifiers can be a list of three bools...
* (see: normalizeModifiers(...) for further information)
*
* XXX need an explicit way to prioritize modes, avoiding object attr
* ordering...
* XXX check do we need did_handling here...
*
* XXX BUG explicitly given modes do not yield results if the pattern
* does not match...
*/
function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){
var chr = null
var s_chr = null
// XXX I do not understand why this is here...
var did_handling = false
var did_ignore = false
modifiers = modifiers == null ? '' : modifiers
modifiers = modifiers != '?' ? normalizeModifiers(modifiers) : modifiers
modes = modes == null ? 'any' : modes
shifted_keys = shifted_keys == null ? _SHIFT_KEYS : shifted_keys
@ -184,18 +248,41 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){
res = {}
for(var mode in keybindings){
for(var title in keybindings){
// test for mode compatibility...
// XXX this fails for explicitly given mode...
if(modes != 'all'
&& (modes != 'any'
&& modes != mode
|| $(mode).length == 0)){
// If a key is ignored then look no further...
if(did_ignore){
if(modes != 'all'){
break
} else {
did_ignore = false
if(modifiers != '?' && res[mode] != 'IGNORE'){
res[mode] = [ res[mode], 'IGNORE NEXT']
}
}
}
// older version compatibility...
if(keybindings[title].pattern != null){
var mode = keybindings[title].pattern
} else {
var mode = title
}
// check if we need to skip this mode...
if( !(modes == 'all'
// explicit mode match...
|| modes == mode
// 'any' means we need to check the mode...
|| (modes == 'any'
// '*' always matches...
&& mode == '*'
// match the mode...
|| $(mode).length != 0))){
continue
}
var bindings = keybindings[mode]
var bindings = keybindings[title]
if(s_chr != null && s_chr in bindings){
var handler = bindings[s_chr]
@ -208,6 +295,7 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){
}
// alias...
// XXX should this be before after or combined with ignore handling...
while( handler != null
&& (typeof(handler) == typeof(123)
|| typeof(handler) == typeof('str')
@ -244,21 +332,24 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){
}
}
// if something is ignored then just breakout and stop handling...
if(bindings.ignore == '*'
|| bindings.ignore != null
&& (bindings.ignore.indexOf(key) != -1
|| bindings.ignore.indexOf(chr) != -1)){
did_handling = true
// ignoring a key will stop processing it...
if(modes == 'all' || mode == modes){
// NOTE: if a handler is defined in this section, this
// will be overwritten...
// XXX need to add the handler to this if it's defined...
res[mode] = 'IGNORE'
}
did_ignore = true
}
// no handler...
if(handler == null){
// if something is ignored then just breakout and stop handling...
if(bindings.ignore == '*'
|| bindings.ignore != null
&& (bindings.ignore.indexOf(key) != -1
|| bindings.ignore.indexOf(chr) != -1)){
did_handling = true
// ignoring a key will stop processing it...
if(modes == 'all' || mode == modes){
res[mode] = 'IGNORE'
} else {
break
}
}
continue
}
@ -308,10 +399,9 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){
/* Basic key binding format:
*
* {
* <css-selector>: {
* // meta-data used to generate user docs/help/config
* title: <text>,
* <title>: {
* doc: <text>,
* pattern: <css-selector>,
*
* // this defines the list of keys to ignore by the handler.
* // NOTE: use "*" to ignore all keys other than explicitly
@ -338,12 +428,14 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){
* default: <callback> | <key-def-x>,
*
* // a modifier can be any single modifier, like shift or a
* // combination of modifiers like 'ctrl+shift', given in order
* // combination of modifiers like 'ctrl+shift', in order
* // of priority.
* // supported modifiers are (in order of priority):
* // supported modifiers, ordered by priority, are:
* // - ctrl
* // - alt
* // - shift
* // NOTE: if in doubt use normalizeModifiers(..) as a
* // reference...
* <modifer>: [...],
* ...
* },
@ -354,6 +446,13 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){
* ...
* },
*
* // legacy format, still supported... (deprecated)
* <css-selector>: {
* // meta-data used to generate user docs/help/config
* title: <text>,
* ...
* },
*
* ...
* }
*
@ -365,6 +464,8 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){
* - action -- any arbitrary string that is not in the above categories.
*
*
* NOTE: adding a key to the ignore list has the same effect as returning
* false form it's handler in the same context.
* NOTE: actions,the last case, are used for alias referencing, they will
* never match a real key, but will get resolved in alias searches.
* NOTE: to test what to use as <key-def> use toKeyCode(..) / toKeyName(..).
@ -380,8 +481,8 @@ function getKeyHandlers(key, modifiers, keybindings, modes, shifted_keys){
* NOTE: the number keys are named with a leading hash '#' (e.g. '#8')
* to avoid conflicsts with keys that have the code with the same
* value (e.g. 'backspace' (8)).
* NOTE: one can use a doc(<doc-string>, <callback>) as a shorthand to assign
* a docstring to a handler.
* NOTE: one can use a doc(<doc-string>, <callback>) as a shorthand to
* assign a docstring to a handler.
* it will only assign .doc attr and return the original function.
*
* XXX need an explicit way to prioritize modes...
@ -398,10 +499,8 @@ function makeKeyboardHandler(keybindings, unhandled){
// key data...
var key = evt.keyCode
// normalize the modifiers...
var modifiers = evt.ctrlKey ? 'ctrl' : ''
modifiers += evt.altKey ? (modifiers != '' ? '+alt' : 'alt') : ''
modifiers += evt.shiftKey ? (modifiers != '' ? '+shift' : 'shift') : ''
// get modifiers...
var modifiers = [evt.ctrlKey, evt.altKey, evt.shiftKey]
//window.DEBUG && console.log('KEY:', key, chr, modifiers)
@ -450,16 +549,25 @@ function makeKeyboardHandler(keybindings, unhandled){
*
* NOTE: this will not add keys (key names) that are not explicit key names.
*/
// XXX do we need to normalize/pre-process keybindings???
// - might be a good idea to normalize the <modifiers>...
function buildKeybindingsHelp(keybindings, shifted_keys){
shifted_keys = shifted_keys == null ? _SHIFT_KEYS : shifted_keys
var res = {}
var mode, title
for(var pattern in keybindings){
mode = keybindings[pattern]
for(var title in keybindings){
mode = keybindings[title]
// older version compatibility...
if(keybindings[title].pattern != null){
var pattern = keybindings[title].pattern
} else {
var pattern = title
// titles and docs...
var title = mode.title == null ? pattern : mode.title
}
// titles and docs...
title = mode.title == null ? pattern : mode.title
res[title] = {
doc: mode.doc == null ? '' : mode.doc
}
@ -467,25 +575,27 @@ function buildKeybindingsHelp(keybindings, shifted_keys){
// handlers...
for(var key in mode){
if(key == 'doc' || key == 'title' || key == 'ignore'){
if(KEYBOARD_SYSTEM_ATTRS.indexOf(key) >= 0){
continue
}
//var modifiers = getKeyHandlers(key, '?', keybindings, pattern)[pattern]
var modifiers = getKeyHandlers(key, '?', keybindings, 'all')[pattern]
modifiers = modifiers == 'none' || modifiers == undefined ? [''] : modifiers
for(var i=0; i < modifiers.length; i++){
var mod = modifiers[i]
//var handler = getKeyHandlers(key, mod, keybindings, pattern)[pattern]
var handler = getKeyHandlers(key, mod, keybindings, 'all')[pattern]
if(handler.constructor.name == 'Array' && handler[1] == 'IGNORE NEXT'){
handler = handler[0]
}
// standard object doc...
if('doc' in handler){
var doc = handler.doc
// lisp style...
} else if(typeof(handler) == typeof([]) && handler.constructor.name == 'Array'){
} else if(handler.constructor.name == 'Array'){
var doc = handler[1]
// no doc...
@ -528,6 +638,31 @@ function buildKeybindingsHelp(keybindings, shifted_keys){
}
// Get a list of keys associated with a given doc...
//
// The second argument must be a structure formated as returned by
// buildKeybindingsHelp(...)
//
// Returned format:
// {
// <section-name> : <key-spec>
// ...
// }
//
// NOTE: <key-spec> is the same as generated by buildKeybindingsHelp(..)
function getKeysByDoc(doc, help){
var res = {}
for(var mode in help){
var name = mode
var section = help[mode]
if(doc in section){
res[mode] = section[doc]
}
}
return res
}
// Build a basic HTML table with keyboard help...
//
// The table will look like this:
@ -584,6 +719,69 @@ function buildKeybindingsHelpHTML(keybindings){
}
// Build HTML for a single key definition...
//
// Format if combining sections (default):
// <span class="key-doc">
// <span class="doc"> DOC </span>
// <span class="keys"> KEYS </span>
// </span>
//
// Format if not combining sections:
// <span class="key-doc">
// <span class="doc"> DOC </span>
// <span class="section">
// <span class="name"> MODE NAME </span>
// <span class="keys"> KEYS </span>
// </span>
// ...
// </span>
//
// XXX not yet sure if we are handling the sections correctly...
function getKeysByDocHTML(doc, help, combine_sections){
combine_sections = combine_sections == null ? true : combine_sections
var spec = getKeysByDoc(doc, help)
var res = '<span class="key-doc">'
res += '<span class="doc">'+ doc +'</span>'
var keys = []
for(var section in spec){
if(!combine_sections){
keys = spec[section].join(', ')
res += '<span class="section">'
+'<span class="name">'+ section +'</span>'
+'<span class="keys">'+ keys +'</span>'
+'</span>'
} else {
keys = keys.concat(spec[section])
}
}
if(combine_sections){
res += '<span class="keys">'+ keys.join(', ') +'</span>'
}
return res + '</span>'
}
// Update key definitions...
//
// NOTE: this does not support multiple sections...
function updateHTMLKeyDoc(help, root){
root = root == null ? $('body') : root
return root.find('.key-doc').each(function(i, e){
e = $(e)
var doc = e.find('.doc')
var keys = $(getKeysByDocHTML(doc.html(), help)).find('.keys')
e.find('.keys').html(keys.html())
})
}
/**********************************************************************
* Key binding editor...