spiegel-keyman/web/source/kmwbase.ts

645 lines
No EOL
23 KiB
TypeScript

// Includes KMW-added property declaration extensions for HTML elements.
/// <reference path="kmwexthtml.ts" />
// Includes a promise polyfill (needed for IE)
/// <reference path="../node_modules/es6-shim/es6-shim.min.js" />
// Defines build-environment includes, since `tsc` doesn't provide a compile-time define.
/// <reference path="environment.inc.ts" />
// Defines the web-page interface object.
/// <reference path="singleton.ts" />
// Defines the core text processor.
/// <reference path="text/processor.ts" />
// Defines the web-page interface object.
/// <reference path="kmwdom.ts" />
// Includes KMW-added property declaration extensions for HTML elements.
/// <reference path="kmwutils.ts" />
// Defines the keyboard callback object.
/// <reference path="text/kbdInterface.ts" />
// Defines keyboard data & management classes.
/// <reference path="kmwkeyboards.ts" />
// Defines built-in keymapping.
/// <reference path="kmwkeymaps.ts" />
// Defines KMW's hotkey management object.
/// <reference path="kmwhotkeys.ts" />
// Defines the ui management code that tracks UI activation and such.
/// <reference path="kmwuimanager.ts" />
// Defines OSK management code.
/// <reference path="osk/oskManager.ts" />
// Defines the language modeling layer (for use in autocorrect and text prediction)
/// <reference path="includes/lmlayer.ts" />
// Defines the model manager.
/// <reference path="text/prediction/modelManager.ts" />
/***
KeymanWeb 11.0
Copyright 2017-2019 SIL International
***/
namespace com.keyman {
export class KeymanBase {
_TitleElement = null; // I1972 - KeymanWeb Titlebar should not be a link
_IE = 0; // browser version identification
_MasterDocument = null; // Document with controller (to allow iframes to distinguish local/master control)
_HotKeys = []; // Array of document-level hotkey objects
warned = false; // Warning flag (to prevent multiple warnings)
baseFont = 'sans-serif'; // Default font for mapped input elements
appliedFont = ''; // Chain of fonts to be applied to mapped input elements
fontCheckTimer = null; // Timer for testing loading of embedded fonts
srcPath = ''; // Path to folder containing executing keymanweb script
rootPath = ''; // Path to server root
protocol = ''; // Protocol used for the KMW script.
mustReloadKeyboard = false;// Force keyboard refreshing even if already loaded
globalKeyboard = null; // Indicates the currently-active keyboard for controls without independent keyboard settings.
globalLanguageCode = null; // Indicates the language code corresponding to `globalKeyboard`.
isEmbedded = false; // Indicates if the KeymanWeb instance is embedded within a mobile app.
// Blocks full page initialization when set to `true`.
refocusTimer = 0; // Tracks a timeout event that aids of OSK modifier/state key tracking when the document loses focus.
initialized: number; // Signals the initialization state of the KeymanWeb system.
isHeadless = false; // Indicates that KMW lacks any access to the DOM. Nothing yet implemented for '= true'.
'build' = 300; // TS needs this to be defined within the class.
// Used as placeholders during initialization.
// The corresponding class properties should be dropped after a refactor;
// this is an intermediate solution while doing the big conversion.
static _srcPath: string;
static _rootPath: string;
static _protocol: string;
static __BUILD__: number;
// Internal objects
['util']: Util;
['osk']: com.keyman.osk.OSKManager;
['ui']: any;
['interface']: text.KeyboardInterface;
keyboardManager: KeyboardManager;
domManager: DOMManager;
hotkeyManager: HotkeyManager;
uiManager: UIManager;
keyMapManager: KeyMapManager;
textProcessor: text.Processor;
modelManager: text.prediction.ModelManager;
touchAliasing: DOMEventHandlers;
// Defines option-tracking object as a string map.
options: { [name: string]: string; } = {
'root':'',
'resources':'',
'keyboards':'',
'fonts':'',
'attachType':'',
'ui':null
};
// Stub functions (defined later in code only if required)
setDefaultDeviceOptions(opt){}
getStyleSheetPath(s){return s;}
getKeyboardPath(f, p?){return f;}
KC_(n, ln, Pelem){return '';}
handleRotationEvents(){}
// Will serve as an API function for a workaround, in case of future touch-alignment issues.
['alignInputs'](eleList?: HTMLElement[]){}
hideInputs() {};
namespaceID(Pstub) {};
preserveID(Pk) {};
setInitialized(val: number) {
this.initialized = this['initialized'] = val;
}
refreshElementContent = null;
// -------------
constructor() {
// Allow internal minification of the public modules.
this.util = this['util'] = new Util(this);
window['KeymanWeb'] = this.interface = this['interface'] = new text.KeyboardInterface();
this.ui = this['ui'] = {};
this.keyboardManager = new KeyboardManager(this);
this.domManager = new DOMManager(this);
this.hotkeyManager = new HotkeyManager(this);
this.uiManager = new UIManager(this);
this.keyMapManager = new KeyMapManager();
this.textProcessor = new text.Processor();
this.modelManager = new text.prediction.ModelManager();
this.osk = this['osk'] = new com.keyman.osk.OSKManager();
// Load properties from their static variants.
this['build'] = KeymanBase.__BUILD__;
this.srcPath = KeymanBase._srcPath;
this.rootPath = KeymanBase._rootPath;
this.protocol = KeymanBase._protocol;
this['version'] = com.keyman.environment.VERSION;
this['helpURL'] = 'http://help.keyman.com/go';
this.setInitialized(0);
// Signals that a KMW load has occurred in order to prevent double-loading.
this['loaded'] = true;
}
delayedInit() {
// Track the selected Event-handling object.
this.touchAliasing = this.util.device.touchable ? this.domManager.touchHandlers : this.domManager.nonTouchHandlers;
}
/**
* Triggers a KeymanWeb engine shutdown to facilitate a full system reset.
* This function is designed for use with KMW unit-testing, which reloads KMW
* multiple times to test the different initialization paths.
*/
['shutdown']() {
// Disable page focus/blur events, which can sometimes trigger and cause parallel KMW instances in testing.
this.util.detachDOMEvent(window, 'focus', this['pageFocusHandler'], false);
this.util.detachDOMEvent(window, 'blur', this['pageFocusHandler'], false);
this.domManager.shutdown();
this.osk.shutdown();
this.util.shutdown();
this.keyboardManager.shutdown();
this.modelManager.shutdown();
if(this.ui && this.ui.shutdown) {
this.ui.shutdown();
}
DOMEventHandlers.states = new CommonDOMStates();
}
/**
* Expose font testing to allow checking that SpecialOSK or custom font has
* been correctly loaded by browser
*
* @param {string} fName font-family name
* @return {boolean} true if available
**/
['isFontAvailable'](fName: string): boolean {
return this.util.checkFont({'family':fName});
}
/**
* Function addEventListener
* Scope Public
* @param {string} event event to handle
* @param {function(Event)} func event handler function
* @return {boolean} value returned by util.addEventListener
* Description Wrapper function to add and identify KeymanWeb-specific event handlers
*/
['addEventListener'](event: string, func): boolean {
return this.util.addEventListener('kmw.'+event, func);
}
/**
* Function _GetEventObject
* Scope Private
* @param {Event=} e Event object if passed by browser
* @return {Event|null} Event object
* Description Gets the event object from the window when using Internet Explorer
* and handles getting the event correctly in frames
*/
_GetEventObject<E extends Event>(e: E) { // I2404 - Attach to controls in IFRAMEs
if (!e) {
e = window.event as E;
if(!e) {
var elem: HTMLElement|Document = this.domManager.getLastActiveElement();
if(elem) {
elem = elem.ownerDocument;
var win: Window;
if(elem) {
win = elem.defaultView;
}
if(!win) {
return null;
}
e = win.event as E;
}
}
}
return e;
}
/**
* Function _push
* Scope Private
* @param {Array} Parray Array
* @param {*} Pval Value to be pushed or appended to array
* @return {Array} Returns extended array
* Description Push (if possible) or append a value to an array
*/
_push<T>(Parray: T[], Pval: T) {
if(Parray.push) {
Parray.push(Pval);
} else {
Parray=Parray.concat(Pval);
}
return Parray;
}
// Base object API definitions
/**
* Function attachToControl
* Scope Public
* @param {Element} Pelem Element to which KMW will be attached
* Description Attaches KMW to control (or IFrame)
*/
['attachToControl'](Pelem: HTMLElement) {
this.domManager.attachToControl(Pelem);
}
/**
* Function detachFromControl
* Scope Public
* @param {Element} Pelem Element from which KMW will detach
* Description Detaches KMW from a control (or IFrame)
*/
['detachFromControl'](Pelem: HTMLElement) {
this.domManager.detachFromControl(Pelem);
}
/**
* Exposed function to load keyboards by name. One or more arguments may be used
*
* @param {string|Object} x keyboard name string or keyboard metadata JSON object
*
*/
['addKeyboards'](x) {
if(arguments.length == 0) {
this.keyboardManager.keymanCloudRequest('',false);
} else {
this.keyboardManager.addKeyboardArray(arguments);
}
}
/**
* Add default or all keyboards for a given language
*
* @param {string} arg Language name (multiple arguments allowed)
**/
['addKeyboardsForLanguage'](arg) {
this.keyboardManager.addLanguageKeyboards(arguments);
}
/**
* Call back from cloud for adding keyboard metadata
*
* @param {Object} x metadata object
**/
['register'](x) {
this.keyboardManager.register(x);
}
/**
* Build 362: removeKeyboards() remove keyboard from list of available keyboards
*
* @param {string} x keyboard name string
*
*/
['removeKeyboards'](x) {
return this.keyboardManager.removeKeyboards(x);
}
/**
* Allow to change active keyboard by (internal) keyboard name
*
* @param {string} PInternalName Internal name
* @param {string} PLgCode Language code
*/
['setActiveKeyboard'](PInternalName: string, PLgCode: string): Promise<void> {
return this.keyboardManager.setActiveKeyboard(PInternalName,PLgCode);
}
/**
* Function getActiveKeyboard
* Scope Public
* @return {string} Name of active keyboard
* Description Return internal name of currently active keyboard
*/
['getActiveKeyboard'](): string {
return this.keyboardManager.getActiveKeyboardName();
}
/**
* Function getActiveLanguage
* Scope Public
* @param {boolean=} true to retrieve full language name, false/undefined to retrieve code.
* @return {string} language code
* Description Return language code for currently selected language
*/
['getActiveLanguage'](fullName?: boolean): string {
return this.keyboardManager.getActiveLanguage(fullName);
}
['isAttached'](x: HTMLElement): boolean {
return this.domManager.isAttached(x);
}
/**
* Function isCJK
* Scope Public
* @param {Object=} k0
* @return {boolean}
* Description Tests if active keyboard (or optional argument) uses a pick list (Chinese, Japanese, Korean, etc.)
* (This function accepts either keyboard structure.)
*/
['isCJK'](k0) {
return this.keyboardManager.isCJK(k0);
}
/**
* Function isChiral
* Scope Public
* @param {string|Object=} k0
* @return {boolean}
* Description Tests if the active keyboard (or optional argument) uses chiral modifiers.
*/
['isChiral'](k0?) {
return this.keyboardManager.isChiral(k0);
}
/**
* Get keyboard meta data for the selected keyboard and language
*
* @param {string} PInternalName Internal name of keyboard
* @param {string=} PlgCode language code
* @return {Object} Details of named keyboard
*
**/
['getKeyboard'](PInternalName: string, PlgCode?: string) {
var Ln, Lrn;
var kbdList = this.keyboardManager.getDetailedKeyboards();
for(Ln=0; Ln < kbdList.length; Ln++) {
Lrn = kbdList[Ln];
if(Lrn['InternalName'] == PInternalName || Lrn['InternalName'] == "Keyboard_" + PInternalName) {
if(arguments.length < 2) {
return Lrn;
}
if(Lrn['LanguageCode'] == PlgCode) {
return Lrn;
}
}
}
return null;
}
/**
* Get array of available keyboard stubs
*
* @return {Array} Array of available keyboards
*
*/
['getKeyboards']() {
return this.keyboardManager.getDetailedKeyboards();
}
/**
* Gets the cookie for the name and language code of the most recently active keyboard
*
* Defaults to US English, but this needs to be user-set in later revision (TODO)
*
* @return {string} InternalName:LanguageCode
*/
['getSavedKeyboard']() {
return this.keyboardManager.getSavedKeyboard();
}
/**
* Function Initialization
* Scope Public
* @param {Object} arg object array of user-defined properties
* Description KMW window initialization
*/
['init'](arg): Promise<any> {
return this.domManager.init(arg);
}
/**
* Function resetContext
* Scope Public
* Description Revert OSK to default layer and clear any deadkeys and modifiers
*/
['resetContext']() {
this.interface.resetContext();
};
/**
* Function setNumericLayer
* Scope Public
* Description Set OSK to numeric layer if it exists
*/
['setNumericLayer']() {
this.interface.setNumericLayer();
};
/**
* Function disableControl
* Scope Public
* @param {Element} Pelem Element to be disabled
* Description Disables a KMW control element
*/
['disableControl'](Pelem: HTMLElement) {
this.domManager.disableControl(Pelem);
}
/**
* Function enableControl
* Scope Public
* @param {Element} Pelem Element to be disabled
* Description Disables a KMW control element
*/
['enableControl'](Pelem: HTMLMapElement) {
this.domManager.enableControl(Pelem);
}
/**
* Function setKeyboardForControl
* Scope Public
* @param {Element} Pelem Control element
* @param {string|null=} Pkbd Keyboard (Clears the set keyboard if set to null.)
* @param {string|null=} Plc Language Code
* Description Set default keyboard for the control
*/
['setKeyboardForControl'](Pelem: HTMLElement, Pkbd?: string, Plc?: string) {
this.domManager.setKeyboardForControl(Pelem, Pkbd, Plc);
}
/**
* Function getKeyboardForControl
* Scope Public
* @param {Element} Pelem Control element
* @return {string|null} The independently-managed keyboard for the control.
* Description Returns the keyboard ID of the current independently-managed keyboard for this control.
* If it is currently following the global keyboard setting, returns null instead.
*/
['getKeyboardForControl'](Pelem) {
this.domManager.getKeyboardForControl(Pelem);
}
/**
* Function getLanguageForControl
* Scope Public
* @param {Element} Pelem Control element
* @return {string|null} The independently-managed keyboard for the control.
* Description Returns the language code used with the current independently-managed keyboard for this control.
* If it is currently following the global keyboard setting, returns null instead.
*/
['getLanguageForControl'](Pelem) {
this.domManager.getLanguageForControl(Pelem);
}
/**
* Set focus to last active target element (browser-dependent)
*/
['focusLastActiveElement']() {
this.domManager.focusLastActiveElement();
}
/**
* Get the last active target element *before* KMW activated (I1297)
*
* @return {Object}
*/
['getLastActiveElement']() {
return this.domManager.getLastActiveElement();
}
/**
* Set the active input element directly optionally setting focus
*
* @param {Object|string} e element id or element
* @param {boolean=} setFocus optionally set focus (KMEW-123)
**/
['setActiveElement'](e: string|HTMLElement, setFocus: boolean) {
return this.domManager.setActiveElement(e, setFocus);
}
/**
* Move focus to user-specified element
*
* @param {string|Object} e element or element id
*
**/
['moveToElement'](e: string|HTMLElement) {
this.domManager.moveToElement(e);
}
/**
* Function addHotkey
* Scope Public
* @param {number} keyCode
* @param {number} shiftState
* @param {function(Object)} handler
* Description Add hot key handler to array of document-level hotkeys triggered by key up event
*/
['addHotKey'](keyCode: number, shiftState: number, handler: () => void) {
this.hotkeyManager.addHotKey(keyCode, shiftState, handler);
}
/**
* Function removeHotkey
* Scope Public
* @param {number} keyCode
* @param {number} shiftState
* Description Remove a hot key handler from array of document-level hotkeys triggered by key up event
*/
['removeHotKey'](keyCode: number, shiftState: number) {
this.hotkeyManager.removeHotkey(keyCode, shiftState);
}
/**
* Function getUIState
* Scope Public
* @return {Object.<string,(boolean|number)>}
* Description Return object with activation state of UI:
* activationPending (bool): KMW being activated
* activated (bool): KMW active
*/
['getUIState'](): UIState {
return this.uiManager.getUIState();
}
/**
* Set or clear the IsActivatingKeymanWebUI flag (exposed function)
*
* @param {(boolean|number)} state Activate (true,false)
*/
['activatingUI'](state: boolean) {
this.uiManager.setActivatingUI(state);
}
// Functions that might be added later
['beepKeyboard']: () => void;
/**
* @param {number} dn Number of pre-caret characters to delete
* @param {string} s Text to insert
* @param {number=} dr Number of post-caret characters to delete
*/
['oninserttext']: (dn: number, s: string, dr?: number) => void;
/**
* Create copy of the OSK that can be used for embedding in documentation or help
* The currently active keyboard will be returned if PInternalName is null
*
* @param {string} PInternalName internal name of keyboard, with or without Keyboard_ prefix
* @param {number} Pstatic static keyboard flag (unselectable elements)
* @param {string=} argFormFactor layout form factor, defaulting to 'desktop'
* @param {(string|number)=} argLayerId name or index of layer to show, defaulting to 'default'
* @return {Object} DIV object with filled keyboard layer content
*/
['BuildVisualKeyboard'](PInternalName, Pstatic, argFormFactor, argLayerId): HTMLElement {
if(this.osk.vkbd) {
return this.osk.vkbd.buildDocumentationKeyboard(PInternalName, Pstatic, argFormFactor, argLayerId);
} else {
return null;
}
}
}
}
/**
* Determine path and protocol of executing script, setting them as
* construction defaults.
*
* This can only be done during load when the active script will be the
* last script loaded. Otherwise the script must be identified by name.
*/
var scripts = document.getElementsByTagName('script');
var ss = scripts[scripts.length-1].src;
var sPath = ss.substr(0,ss.lastIndexOf('/')+1);
import KeymanBase = com.keyman.KeymanBase;
KeymanBase._srcPath = sPath;
KeymanBase._rootPath = sPath.replace(/(https?:\/\/)([^\/]*)(.*)/,'$1$2/');
KeymanBase._protocol = sPath.replace(/(.{3,5}:)(.*)/,'$1');
/** @define {number} build counter that gets set by the build environment */
KeymanBase.__BUILD__ = 299;
/**
* Base code: Declare major component namespaces, instances, and utility functions
*/
// If a copy of the script is already loaded, detect this and prevent re-initialization / data reset.
if(!window['keyman'] || !window['keyman']['loaded']) {
(function() {
/* The base object call may need to be moved into a separate, later file eventually.
* It will be necessary to override methods with kmwnative.ts and kmwembedded.ts before the
* affected objects are initialized.
*
* We only recreate the 'keyman' object if it's not been loaded.
* As this is the base object, not creating it prevents a KMW system reset.
*/
window['keyman'] = com.keyman.singleton = new KeymanBase();
})();
}