fix(common/core/web): Remove empty rows in OSK

This commit is contained in:
Darcy Wong 2021-09-16 10:29:58 +07:00
parent 2079947319
commit 87b580d998
6 changed files with 669 additions and 51 deletions

View file

@ -48,7 +48,7 @@ namespace com.keyman.keyboards {
* Matches the key code as set within Keyman Developer for the layout.
* For example, K_R or U_0020. Denotes either physical keys or virtual keys with custom output,
* with no additional metadata like layer or active modifiers.
*
*
* Is used to determine the keycode for input events, rule-matching, and keystroke processing.
*/
public get baseKeyID(): string {
@ -61,20 +61,20 @@ namespace com.keyman.keyboards {
/**
* A unique identifier based on both the key ID & the 'desktop layer' to be used for the key.
*
*
* Allows diambiguation of scenarios where the same key ID is used twice within a layer, but
* with different innate modifiers. (Refer to https://github.com/keymanapp/keyman/issues/4617)
* The 'desktop layer' may be omitted if it matches the key's display layer.
*
*
* Examples, given a 'default' display layer, matching keys to Keyman keyboard language:
*
*
* ```
* "K_Q"
* "K_Q"
* + [K_Q]
* "K_Q+shift"
* + [K_Q SHIFT]
* ```
*
*
* Useful when the active layer of an input-event is already known.
*/
public get coreID(): string {
@ -83,7 +83,7 @@ namespace com.keyman.keyboards {
}
let baseID = this.id || '';
if(this.displayLayer != this.layer) {
baseID = baseID + '+' + this.layer;
}
@ -94,19 +94,19 @@ namespace com.keyman.keyboards {
/**
* A keyboard-unique identifier to be used for any display elements representing this key
* in user interfaces and/or on-screen keyboards.
*
*
* Distinguishes between otherwise-identical keys on different layers of an OSK.
* Includes identifying information about the key's display layer.
*
*
* Examples, given a 'default' display layer, matching keys to Keyman keyboard language:
*
*
* ```
* "default-K_Q"
* "default-K_Q"
* + [K_Q]
* "default-K_Q+shift"
* + [K_Q SHIFT]
* ```
*
*
* Useful when only the active keyboard is known about an input event.
*/
public get elementID(): string {
@ -538,7 +538,7 @@ namespace com.keyman.keyboards {
return;
default:
// Refer to text/codes.ts - these are Keyman-custom "keycodes" used for
// layer shifting keys. To be safe, we currently let K_TABBACK and
// layer shifting keys. To be safe, we currently let K_TABBACK and
// K_TABFWD through, though we might be able to drop them too.
let code = com.keyman.text.Codes[key.baseKeyID];
if(code > 50000 && code < 50011) {
@ -629,6 +629,31 @@ namespace com.keyman.keyboards {
return this.layerMap[layerId];
}
/**
* Refer to https://github.com/keymanapp/keyman/issues/254, which mentions
* KD-11 from a prior issue-tracking system from the closed-source days that
* resulted in an unintended extra empty row.
*
* It'll be pretty rare to see a keyboard affected by the bug, but we don't
* 100% control all keyboards out there, so it's best we make sure the edge
* case is covered.
*
* @param layers The layer group to be loaded for the form factor. Will be
* mutated by this operation.
*/
static correctLayerEmptyRowBug(layers: LayoutLayer[]) {
for(let n=0; n<layers.length; n++) {
let layer=layers[n];
let rows=layer['row'];
let i: number;
for(i=rows.length-1; i>=0; i--) {
if(!Array.isArray(rows[i]['key']) || rows[i]['key'].length == 0) {
rows.splice(i, 1)
}
}
}
}
/**
*
* @param layout
@ -640,27 +665,11 @@ namespace com.keyman.keyboards {
}
// Create a separate OSK div for each OSK layer, only one of which will ever be visible
var n: number, i: number;
var layers: LayoutLayer[], layer: LayoutLayer;
var n: number;
let layerMap: {[layerId: string]: ActiveLayer} = {};
var rows: LayoutRow[];
layers=layout['layer'];
// ***Delete any empty rows at the end added by compiler bug...
for(n=0; n<layers.length; n++) {
layer=layers[n]; rows=layer['row'];
for(i=rows.length; i>0; i--) {
if(rows[i-1]['key'].length > 0) {
break;
}
}
if(i < rows.length) {
rows.splice(i-rows.length,rows.length-i);
}
}
// ...remove to here when compiler bug fixed ***
let layers=layout['layer'];
ActiveLayout.correctLayerEmptyRowBug(layers);
// Add class functions to the existing layout object, allowing it to act as an ActiveLayout.
let dummy = new ActiveLayout();

View file

@ -863,25 +863,6 @@ namespace com.keyman.osk {
var tKey=this.getDefaultKeyObject();
tKey['fontsize']=ls.fontSize;
// Identify key labels (e.g. *Shift*) that require the special OSK font
var specialLabel=/\*\w+\*/;
// ***Delete any empty rows at the end added by compiler bug...
for(n=0; n<layers.length; n++) {
let layer=layers[n];
let rows=layer['row'];
for(i=rows.length; i>0; i--) {
if(rows[i-1]['key'].length > 0) {
break;
}
}
if(i < rows.length) {
rows.splice(i-rows.length,rows.length-i);
}
}
// ...remove to here when compiler bug fixed ***
// Set the OSK row height, **assuming all layers have the same number of rows**
// Calculate default row height

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,522 @@
KeymanWeb.KR(new Keyboard_empty_row());
function Keyboard_empty_row() {
this.KI = "Keyboard_empty_row";
this.KN = "empty row";
this.KMINVER = "9.0";
this.KV = null;
this.KDU = 0;
this.KH = '';
this.KM = 0;
this.KBVER = "1.0";
this.KMBM = 0x0000;
this.KVKL = {
"phone": {
"font": "Tahoma",
"displayUnderlying": false,
"layer": [{
"id": "default",
"row": [{
"id": "1",
"key": []
}, {
"id": "2",
"key": [{
"id": "K_Q",
"text": "q"
}, {
"id": "K_W",
"text": "w"
}, {
"id": "K_E",
"text": "e"
}, {
"id": "K_R",
"text": "r"
}, {
"id": "K_T",
"text": "t"
}, {
"id": "K_Y",
"text": "y"
}, {
"id": "K_U",
"text": "u"
}, {
"id": "K_I",
"text": "i"
}, {
"id": "K_O",
"text": "o"
}, {
"id": "K_P",
"text": "p"
}]
}, {
"id": "3",
"key": [{
"id": "K_A",
"pad": "50",
"text": "a"
}, {
"id": "K_S",
"text": "s"
}, {
"id": "K_D",
"text": "d"
}, {
"id": "K_F",
"text": "f"
}, {
"id": "K_G",
"text": "g"
}, {
"id": "K_H",
"text": "h"
}, {
"id": "K_J",
"text": "j"
}, {
"id": "K_K",
"text": "k"
}, {
"id": "K_L",
"text": "l"
}, {
"width": "10",
"id": "T_new_88",
"sp": "10"
}]
}, {
"id": "4",
"key": [{
"nextlayer": "shift",
"id": "K_SHIFT",
"sp": "1",
"text": "*Shift*"
}, {
"id": "K_Z",
"text": "z"
}, {
"id": "K_X",
"text": "x"
}, {
"id": "K_C",
"text": "c"
}, {
"id": "K_V",
"text": "v"
}, {
"id": "K_B",
"text": "b"
}, {
"id": "K_N",
"text": "n"
}, {
"id": "K_M",
"text": "m"
}, {
"id": "K_PERIOD",
"text": ".",
"sk": [{
"id": "K_COMMA",
"text": ","
}, {
"layer": "shift",
"id": "K_1",
"text": "!"
}, {
"layer": "shift",
"id": "K_SLASH",
"text": "?"
}, {
"id": "K_QUOTE",
"text": "'"
}, {
"layer": "shift",
"id": "K_QUOTE",
"text": "\""
}, {
"id": "K_BKSLASH",
"text": "\\"
}, {
"layer": "shift",
"id": "K_COLON",
"text": ":"
}, {
"id": "K_COLON",
"text": ";"
}]
}, {
"width": "100",
"id": "K_BKSP",
"sp": "1",
"text": "*BkSp*"
}]
}, {
"id": "5",
"key": [{
"nextlayer": "numeric",
"width": "150",
"id": "K_NUMLOCK",
"sp": "1",
"text": "*123*"
}, {
"width": "120",
"id": "K_LOPT",
"sp": "1",
"text": "*Menu*"
}, {
"width": "610",
"id": "K_SPACE"
}, {
"width": "150",
"id": "K_ENTER",
"sp": "1",
"text": "*Enter*"
}]
}]
}, {
"id": "shift",
"row": [{
"id": "1",
"key": [{
"id": "K_Q",
"text": "Q"
}, {
"id": "K_W",
"text": "W"
}, {
"id": "K_E",
"text": "E"
}, {
"id": "K_R",
"text": "R"
}, {
"id": "K_T",
"text": "T"
}, {
"id": "K_Y",
"text": "Y"
}, {
"id": "K_U",
"text": "U"
}, {
"id": "K_I",
"text": "I"
}, {
"id": "K_O",
"text": "O"
}, {
"id": "K_P",
"text": "P"
}]
}, {
"id": "2"
}, {
"id": "3",
"key": [{
"id": "K_A",
"pad": "50",
"text": "A"
}, {
"id": "K_S",
"text": "S"
}, {
"id": "K_D",
"text": "D"
}, {
"id": "K_F",
"text": "F"
}, {
"id": "K_G",
"text": "G"
}, {
"id": "K_H",
"text": "H"
}, {
"id": "K_J",
"text": "J"
}, {
"id": "K_K",
"text": "K"
}, {
"id": "K_L",
"text": "L"
}, {
"width": "10",
"id": "T_new_122",
"sp": "10"
}]
}, {
"id": "4",
"key": [{
"nextlayer": "default",
"id": "K_SHIFT",
"sp": "2",
"text": "*Shift*"
}, {
"id": "K_Z",
"text": "Z"
}, {
"id": "K_X",
"text": "X"
}, {
"id": "K_C",
"text": "C"
}, {
"id": "K_V",
"text": "V"
}, {
"id": "K_B",
"text": "B"
}, {
"id": "K_N",
"text": "N"
}, {
"id": "K_M",
"text": "M"
}, {
"layer": "default",
"id": "K_PERIOD",
"text": ".",
"sk": [{
"layer": "default",
"id": "K_COMMA",
"text": ","
}, {
"layer": "shift",
"id": "K_1",
"text": "!"
}, {
"layer": "shift",
"id": "K_SLASH",
"text": "?"
}, {
"layer": "default",
"id": "K_QUOTE",
"text": "'"
}, {
"layer": "shift",
"id": "K_QUOTE",
"text": "\""
}, {
"layer": "default",
"id": "K_BKSLASH",
"text": "\\"
}, {
"layer": "shift",
"id": "K_COLON",
"text": ":"
}, {
"layer": "default",
"id": "K_COLON",
"text": ";"
}]
}, {
"id": "K_BKSP",
"sp": "1",
"text": "*BkSp*"
}]
}, {
"id": "5",
"key": [{
"nextlayer": "numeric",
"width": "150",
"id": "K_NUMLOCK",
"sp": "1",
"text": "*123*"
}, {
"width": "120",
"id": "K_LOPT",
"sp": "1",
"text": "*Menu*"
}, {
"width": "610",
"id": "K_SPACE"
}, {
"width": "150",
"id": "K_ENTER",
"sp": "1",
"text": "*Enter*"
}]
}]
}, {
"id": "numeric",
"row": [{
"id": "1",
"key": [{
"id": "K_1",
"text": "1"
}, {
"id": "K_2",
"text": "2"
}, {
"id": "K_3",
"text": "3"
}, {
"id": "K_4",
"text": "4"
}, {
"id": "K_5",
"text": "5"
}, {
"id": "K_6",
"text": "6"
}, {
"id": "K_7",
"text": "7"
}, {
"id": "K_8",
"text": "8"
}, {
"id": "K_9",
"text": "9"
}, {
"id": "K_0",
"text": "0"
}]
}, {
"id": "2",
"key": [{
"layer": "shift",
"id": "K_4",
"pad": "50",
"text": "$"
}, {
"layer": "shift",
"id": "K_2",
"text": "@"
}, {
"layer": "shift",
"id": "K_3",
"text": "#"
}, {
"layer": "shift",
"id": "K_5",
"text": "%"
}, {
"layer": "shift",
"id": "K_7",
"text": "&"
}, {
"layer": "shift",
"id": "K_HYPHEN",
"text": "_"
}, {
"layer": "default",
"id": "K_EQUAL",
"text": "="
}, {
"layer": "shift",
"id": "K_BKSLASH",
"text": "|"
}, {
"layer": "default",
"id": "K_BKSLASH",
"text": "\\"
}, {
"width": "10",
"id": "T_new_156",
"sp": "10"
}]
}, {
"id": "3"
}, {
"id": "4",
"key": [{
"id": "K_LBRKT",
"pad": "110",
"text": "[",
"sk": [{
"id": "U_00AB",
"text": "\u00AB"
}, {
"layer": "shift",
"id": "K_COMMA",
"text": "<"
}, {
"layer": "shift",
"id": "K_LBRKT",
"text": "{"
}]
}, {
"layer": "shift",
"id": "K_9",
"text": "("
}, {
"layer": "shift",
"id": "K_0",
"text": ")"
}, {
"id": "K_RBRKT",
"text": "]",
"sk": [{
"id": "U_00BB",
"text": "\u00BB"
}, {
"layer": "shift",
"id": "K_PERIOD",
"text": ">"
}, {
"layer": "shift",
"id": "K_RBRKT",
"text": "}"
}]
}, {
"layer": "shift",
"id": "K_EQUAL",
"text": "+"
}, {
"id": "K_HYPHEN",
"text": "-"
}, {
"layer": "shift",
"id": "K_8",
"text": "*"
}, {
"id": "K_SLASH",
"text": "\/"
}, {
"width": "100",
"id": "K_BKSP",
"sp": "1",
"text": "*BkSp*"
}]
}, {
"id": "5",
"key": [{
"nextlayer": "default",
"width": "150",
"id": "K_LOWER",
"sp": "1",
"text": "*abc*"
}, {
"width": "120",
"id": "K_LOPT",
"sp": "1",
"text": "*Menu*"
}, {
"width": "610",
"id": "K_SPACE"
}, {
"width": "150",
"id": "K_ENTER",
"sp": "1",
"text": "*Enter*"
}]
}]
}]
}
};
this.KVER = "13.0.118.0";
this.gs = function(t, e) {
return this.g0(t, e);
};
this.g0 = function(t, e) {
var k = KeymanWeb,
r = 0,
m = 0;
return r;
};
}

View file

@ -0,0 +1,104 @@
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
<!-- Set the viewport width to match phone and tablet device widths -->
<meta name="viewport" content="width=device-width,user-scalable=no" />
<!-- Allow KeymanWeb to be saved to the iPhone home screen -->
<meta name="apple-mobile-web-app-capable" content="yes" />
<!-- Enable IE9 Standards mode -->
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>KeymanWeb Test Page - Empty Row</title>
<!-- Your page CSS -->
<style type='text/css'>
body {font-family: Tahoma,helvetica;}
h3 {font-size: 1em;font-weight:normal;color: darkred; margin-bottom: 4px}
.test {font-size: 1.5em; width:80%; min-height:30px; border: 1px solid gray;}
#KeymanWebControl {width:50%;min-width:600px;}
</style>
<!-- Insert uncompiled KeymanWeb source scripts -->
<script src="../../release/unminified/web/keymanweb.js" type="application/javascript"></script>
<!--
For desktop browsers, a script for the user interface must be inserted here.
Standard UIs are toggle, button, float and toolbar.
The toolbar UI is best for any page designed to support keyboards for
a large number of languages.
-->
<script src="../../release/unminified/web/kmwuitoggle.js"></script>
<!-- Initialization: set paths to keyboards, resources and fonts as required -->
<script>
var kmw=window.keyman;
kmw.init({
attachType: 'auto'
}).then(function() {
// A testing keyboard with empty rows on each layer
kmw.addKeyboards({id:'empty_row', name: 'Empty Row',
languages:{
id:'en', name:'English'
},
filename: 'empty_row-1.0.js'
});
kmw.addKeyboards({id:'afghan_turkmen', name:'Afghan Turkmen',
languages:{
id:'tk-Arab', name:'Turkmen (Arabic)'
},
filename: 'afghan_turkmen-1.1.js'
});
});
</script>
</head>
<!-- Sample page HTML -->
<body>
<h2>KeymanWeb Sample Page - Empty Row Testing</h2>
<p>This page is designed to test that the OSK removes empty rows in touch-layout keyboards
so they're not displayed. Refer to issue <a href="https://github.com/keymanapp/keyman/issues/5327">#5327</a>.</p>
<p>The empty_row keyboard is a custom keyboard compiled with Developer 13.0, and contains an empty row in each of the 3 layers.</p>
<p>The afghan_turkmen keyboard comes directly from the community site reporting the issue, so disregard that
the shift key does not switch layers.
</p>
<p>Load the page in mobile viewport and also check developer console does not have exceptions. </p>
<hr/>
<div>
<!--
The following DIV is used to position the Button or Toolbar User Interfaces on the page.
If omitted, those User Interfaces will appear at the top of the document body.
(It is ignored by other User Interfaces.)
-->
<div id='KeymanWebControl'></div>
<h3>Type in your language in this text area:</h3>
<textarea id='ta1' class='test' placeholder='Type here'></textarea>
<h3>or in this input field:</h3>
<input class='test' value='' placeholder='or here'/>
<h3><a href="../index.html">Return to testing home page</a></h3>
</div>
</body>
<!--
*** DEVELOPER NOTE -- FIREFOX CONFIGURATION FOR TESTING ***
*
* If the URL bar starts with <b>file://</b>, Firefox may not load the font used
* to display the special characters used in the On-Screen Keyboard.
*
* To work around this Firefox bug, navigate to <b>about:config</b>
* and set <b>security.fileuri.strict_origin_policy</b> to <b>false</b>
* while testing.
*
* Firefox resolves website-based CSS URI references correctly without needing
* any configuration change, so this change should only be made for file-based testing.
*
***
-->
</html>

View file

@ -26,6 +26,7 @@
<h2><a href="./attachment-api/?mode=auto">Tests the new Attachment/Enablement API functionality</a><h2>
<h2><a href="./chirality/">Chirality testing/bootstrapping</a><h2>
<h2><a href="./options-with-save/">Tests option/variable store functionality</a></h2>
<h2><a href="./empty-row/">Tests OSK handling of empty rows</a></h2>
<h2><a href="./platform/">Platform testing</a></h2>
<h2><a href="./prediction-ui/">Prediction UI testing</a></h2>
<h2><a href="./prediction-mtnt/">Prediction - robust testing</a></h2>