diff --git a/.github/workflows/api-verification.yml b/.github/workflows/api-verification.yml index 7d4a473978..786e8ebad2 100644 --- a/.github/workflows/api-verification.yml +++ b/.github/workflows/api-verification.yml @@ -37,7 +37,6 @@ jobs: id: environment_step run: | cat artifacts/env >> $GITHUB_OUTPUT - cat artifacts/env - name: Set pending status on PR builds id: set_status @@ -68,22 +67,14 @@ jobs: - name: "Verify API for libkeymancore*.so (${{ steps.environment_step.outputs.GIT_BRANCH }}, branch ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }}, by ${{ steps.environment_step.outputs.GIT_USER }})" run: | - echo "VERSION=${{ steps.environment_step.outputs.VERSION }}" - echo "PRERELEASE_TAG=${{ steps.environment_step.outputs.PRERELEASE_TAG }}" - echo "GIT_SHA=${{ steps.environment_step.outputs.GIT_SHA }}" - echo "GIT_BASE=${{ steps.environment_step.outputs.GIT_BASE }}" - ls -al ${GITHUB_WORKSPACE} - ls -al ${GITHUB_WORKSPACE}/artifacts BIN_PACKAGE=$(ls "${GITHUB_WORKSPACE}/artifacts/" | grep "${PKG_NAME}[0-9]*_${{ steps.environment_step.outputs.VERSION }}-1${{ steps.environment_step.outputs.PRERELEASE_TAG }}+$(lsb_release -c -s)1_amd64.deb") - echo "BIN_PACKAGE=${BIN_PACKAGE}" - ls -al ${BIN_PACKAGE} cd ${{ github.workspace }}/keyman/linux ./scripts/deb-packaging.sh \ --gha \ - --bin-pkg "${BIN_PACKAGE}" \ + --bin-pkg "${GITHUB_WORKSPACE}/artifacts/${BIN_PACKAGE}" \ --git-sha "${{ steps.environment_step.outputs.GIT_SHA }}" \ --git-base "${{ steps.environment_step.outputs.GIT_BASE }}" \ - verify # 2>> $GITHUB_STEP_SUMMARY + verify 2>> $GITHUB_STEP_SUMMARY - name: Archive .symbols file uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0 @@ -99,23 +90,22 @@ jobs: if: ${{ always() && needs.api_verification.outputs.IS_TEST_BUILD == 'true' }} steps: - name: Set success - # TEMP commented until things are working! - # if: needs.api_verification.result == 'success' + if: needs.api_verification.result == 'success' run: | echo "RESULT=success" >> $GITHUB_ENV echo "MSG=Package build succeeded" >> $GITHUB_ENV - # - name: Set cancelled - # if: needs.api_verification.result == 'cancelled' - # run: | - # echo "RESULT=error" >> $GITHUB_ENV - # echo "MSG=Package build cancelled" >> $GITHUB_ENV + - name: Set cancelled + if: needs.api_verification.result == 'cancelled' + run: | + echo "RESULT=error" >> $GITHUB_ENV + echo "MSG=Package build cancelled" >> $GITHUB_ENV - # - name: Set failure - # if: needs.api_verification.result == 'failure' - # run: | - # echo "RESULT=failure" >> $GITHUB_ENV - # echo "MSG=Package build failed" >> $GITHUB_ENV + - name: Set failure + if: needs.api_verification.result == 'failure' + run: | + echo "RESULT=failure" >> $GITHUB_ENV + echo "MSG=Package build failed" >> $GITHUB_ENV - name: Set final status run: | diff --git a/HISTORY.md b/HISTORY.md index 815254b7d4..c5e1f2e780 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,66 @@ # Keyman Version History +## 18.0.55 alpha 2024-06-13 + +* fix(developer): handle missing OSK when importing a Windows keyboard into a touch-only project (#11720) +* fix(developer): verify email addresses in .kps and .keyboard_info (#11735) +* change(web): prep for better asynchronous prediction handling (#10343) + +## 18.0.54 alpha 2024-06-12 + +* fix(common): remove subpackage entries for older TS version (#11745) +* chore(common): end use of ts-node (#11746) +* feat(web): add bulk_render variant that loads and renders keyboards from local KMP (#10432) + +## 18.0.53 alpha 2024-06-10 + +* fix(android): check current orientation when redisplaying system keyboard (#11604) +* fix(android): fix keyboard size after rotation and restore via onSizeChanged, after layout (#11722) +* fix(developer): fix kmcmplib unit-test include paths (#11749) + +## 18.0.52 alpha 2024-06-08 + +* fix(developer): prevent two touch layout editors opening for the same file (#11717) +* chore(common): cleanup meson deprecations and warnings (#11523) +* feat(developer): support language reference in context help (#11737) +* test(developer): kmcmplib compiler unit tests (#11378) + +## 18.0.51 alpha 2024-06-07 + +* fix(web): fix osk touch-focus tracking (#11705) +* fix(web): defer keyboard activation requests made during engine initialization (#11713) +* chore(developer): add context/character-map (#11656) +* chore(developer): add context/wordlist-editor (#11658) +* chore(developer): add context/new-model-project-parameters (#11677) +* fix(common): remove allowJs from web's tsconfig.base.json (#11718) +* change(web): precompile all TS-based tests (#11723) +* chore(developer): add extra logging for assertion failure when pressing backspace in debugger (#11707) +* chore: add cherry-pick information in commit messages (#11708) +* fix(developer): handle encoding errors when loading wordlists (#11711) +* chore(ios): remove dead Swift-side keyboard gesture code (#11672) +* fix(mac): change build configuration to prevent cycle error in Xcode 15 (#11730) +* refactor(web): Replace deprecated substr with substring (#11637) + +## 18.0.50 alpha 2024-06-06 + +* chore(common): adds retry mechanism for build script npm ci calls (#11451) +* fix(web): get row-height for flick constraints after performing layout (#11691) +* chore(ios): enable webview debugging (#11229) +* fix(android): handle `IllegalArgumentException` when initializing `CloudDownloadMgr`, add logging to check for unhandled side-effects (#11626) +* chore: replace git dep on restructure with 3.0.1 in npm (#11657) +* chore: move xml2js into the repo to eliminate npm git dependency (#11660) +* chore(common): move CLDR import copy into build step for common/web/types (#11690) +* fix(developer): handle editor initializing after debugger when setting execution point (#11587) +* fix(developer): treat js files with unrecognized encodings as non-keyboard files (#11698) +* fix(developer): disable example edit controls if no examples in Package Editor (#11701) + +## 18.0.49 alpha 2024-06-05 + +* fix(web): revert #11598 to eliminate use of `finalInput` which caused crash after moving caret (#11685) +* fix(common): correctly display result of multiple option parameters in builder (#11679) +* fix(linux): specify path with package name (#11694) +* fix(linux): remove debug output and re-enable failures (#11695) + ## 18.0.48 alpha 2024-06-04 * fix(linux): add debug output (#11668) diff --git a/VERSION.md b/VERSION.md index 7a839a0965..b38400af1f 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -18.0.49 \ No newline at end of file +18.0.56 \ No newline at end of file diff --git a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java index cf32ba0c00..c6885b2b56 100644 --- a/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java +++ b/android/KMAPro/kMAPro/src/main/java/com/keyman/android/SystemKeyboard.java @@ -147,6 +147,16 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven KMManager.onStartInput(attribute, restarting); KMManager.resetContext(KeyboardType.KEYBOARD_TYPE_SYSTEM); + // This method (likely) includes the IME equivalent to `onResume` for `Activity`-based classes, + // making it an important time to detect orientation changes. + Context appContext = getApplicationContext(); + int newOrientation = KMManager.getOrientation(appContext); + if(newOrientation != lastOrientation) { + lastOrientation = newOrientation; + Configuration newConfig = this.getResources().getConfiguration(); + KMManager.onConfigurationChanged(newConfig); + } + // Temporarily disable predictions on certain fields (e.g. hidden password field or numeric) int inputType = attribute.inputType; KMManager.setMayPredictOverride(inputType); @@ -154,7 +164,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven KMManager.setBannerOptions(false); } else if (KMManager.isKeyboardLoaded(KeyboardType.KEYBOARD_TYPE_SYSTEM)){ // Check if predictions needs to be re-enabled per Settings preference - Context appContext = getApplicationContext(); Keyboard kbInfo = KMManager.getCurrentKeyboardInfo(appContext); if (kbInfo != null) { String langId = kbInfo.getLanguageID(); @@ -171,7 +180,7 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven ExtractedText icText = ic.getExtractedText(new ExtractedTextRequest(), 0); /* We do sometimes receive null `icText.text`, even though - getExtractedText() docs does not list this as a possible + getExtractedText() docs does not list this as a possible return value, so we test for that as well (#11479) */ if (icText != null && icText.text != null) { @@ -199,15 +208,6 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven super.onUpdateExtractingVisibility(ei); } - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - if (newConfig.orientation != lastOrientation) { - lastOrientation = newConfig.orientation; - KMManager.onConfigurationChanged(newConfig); - } - } - @Override public void onConfigureWindow(Window win, boolean isFullscreen, boolean isCandidatesOnly) { super.onConfigureWindow(win, isFullscreen, isCandidatesOnly); diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index 1582c970f8..39ce3a5f30 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -145,7 +145,7 @@ function setOskHeight(h) { function setOskWidth(w) { if(w > 0) { - oskWidth = w; + oskWidth = w / window.devicePixelRatio; } } diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java index d45937fe73..6788c8f90e 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/KMKeyboard.java @@ -195,13 +195,13 @@ final class KMKeyboard extends WebView { int selMin = icText.selectionStart, selMax = icText.selectionEnd; int textLength = rawText.length(); - + if (selMin < 0 || selMax < 0) { // There is no selection or cursor // Reference https://developer.android.com/reference/android/text/Selection#getSelectionEnd(java.lang.CharSequence) return false; } else if (selMin > textLength || selMax > textLength) { - // Selection is past end of existing text -- should not be possible but we + // Selection is past end of existing text -- should not be possible but we // are seeing it happen; #11506 return false; } @@ -231,7 +231,7 @@ final class KMKeyboard extends WebView { selMin -= pairsAtStart; selMax -= (pairsAtStart + pairsSelected); this.loadJavascript(KMString.format("updateKMSelectionRange(%d,%d)", selMin, selMax)); - + return true; } @@ -262,7 +262,7 @@ final class KMKeyboard extends WebView { // When `.isTestMode() == true`, the setWebContentsDebuggingEnabled method is not available // and thus will trigger unit-test failures. if (!KMManager.isTestMode() && ( - (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 || + (context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0 || KMManager.getTier(null) != KMManager.Tier.STABLE )) { // Enable debugging of WebView via adb. Not used during unit tests @@ -443,20 +443,15 @@ final class KMKeyboard extends WebView { dismissHelpBubble(); } + @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); RelativeLayout.LayoutParams params = KMManager.getKeyboardLayoutParams(); + // I suspect this is the part we should actually be calling directly... this.setLayoutParams(params); - - int bannerHeight = KMManager.getBannerHeight(context); - int oskHeight = KMManager.getKeyboardHeight(context); - if (this.htmlBannerString != null && !this.htmlBannerString.isEmpty()) { - setHTMLBanner(this.htmlBannerString); - } - loadJavascript(KMString.format("setBannerHeight(%d)", bannerHeight)); - loadJavascript(KMString.format("setOskWidth(%d)", newConfig.screenWidthDp)); - loadJavascript(KMString.format("setOskHeight(%d)", oskHeight)); + this.invalidate(); + this.requestLayout(); this.dismissHelpBubble(); @@ -465,6 +460,27 @@ final class KMKeyboard extends WebView { } } + @Override + public void onSizeChanged(int width, int height, int oldWidth, int oldHeight) { + super.onSizeChanged(width, height, oldWidth, oldHeight); + int bannerHeight = KMManager.getBannerHeight(context); + int oskHeight = KMManager.getKeyboardHeight(context); + + if(bannerHeight + oskHeight != height) { + // We'll proceed, but cautiously and with logging. + KMLog.LogInfo(TAG, "Height mismatch: onSizeChanged = " + height + ", our version = " + (bannerHeight + oskHeight)); + } + + if (this.htmlBannerString != null && !this.htmlBannerString.isEmpty()) { + setHTMLBanner(this.htmlBannerString); + } + + loadJavascript(KMString.format("setBannerHeight(%d)", bannerHeight)); + loadJavascript(KMString.format("setOskWidth(%d)", width)); + // Must be last - it's the one that triggers a Web-engine layout refresh. + loadJavascript(KMString.format("setOskHeight(%d)", oskHeight)); + } + public void dismissSuggestionMenuWindow() { try { if (suggestionMenuWindow != null && suggestionMenuWindow.isShowing()) { diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java index 8c0d4403cb..8a25ab09ba 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/cloud/CloudDownloadMgr.java @@ -40,6 +40,7 @@ public class CloudDownloadMgr{ { if(instance!=null) return; + KMLog.LogBreadcrumb("CloudDownloadMgr", "CloudDownloadMgr.createInstance() - first call", true); instance = new CloudDownloadMgr(); } @@ -60,6 +61,7 @@ public class CloudDownloadMgr{ if(isInitialized) return; try { + KMLog.LogBreadcrumb("CloudDownloadMgr", "attempting CloudDownloadMgr.initialize()", true); // Runtime-registered boradcasts receivers must specify export behavior to indicate whether // or not the receiver should be exported to all other apps on the device // https://developer.android.com/about/versions/14/behavior-changes-14#runtime-receivers-exported @@ -69,11 +71,12 @@ public class CloudDownloadMgr{ } else { aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } - } catch (IllegalStateException e) { + } catch (IllegalArgumentException e) { String message = "initialize error: "; KMLog.LogException(TAG, message, e); } isInitialized = true; + KMLog.LogBreadcrumb("CloudDownloadMgr", ".initialize() call complete", false); } /** @@ -82,11 +85,15 @@ public class CloudDownloadMgr{ */ public synchronized void shutdown(Context aContext) { - if(!isInitialized) + if(!isInitialized) { return; + } + + KMLog.LogBreadcrumb("CloudDownloadMgr", "CloudDownloadMgr.shutdown()", true); + try { aContext.unregisterReceiver(completeListener); - } catch (IllegalStateException e) { + } catch (IllegalArgumentException e) { String message = "shutdown error: "; KMLog.LogException(TAG, message, e); } @@ -206,8 +213,10 @@ public class CloudDownloadMgr{ CloudApiTypes.CloudApiParam... params) { if(!isInitialized) { - Log.w(TAG, "Downloadmanager not initialized. Initializing CloudDownloadMgr."); + Log.w(TAG, "DownloadManager not initialized. Initializing CloudDownloadMgr."); initialize(aContext); + } else { + KMLog.LogBreadcrumb("CloudDownloadMgr", "CloudDownloadMgr.executeAsDownload() called; already initialized", true); } synchronized (downloadSetByDownloadIdentifier) { @@ -218,7 +227,7 @@ public class CloudDownloadMgr{ DownloadManager downloadManager = (DownloadManager) aContext.getSystemService(Context.DOWNLOAD_SERVICE); if(downloadManager==null) - throw new IllegalStateException("Downloadmanager is not available"); + throw new IllegalStateException("DownloadManager is not available"); aCallback.initializeContext(aContext); diff --git a/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java b/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java index 3eeabdcbdf..cb13cc967e 100644 --- a/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java +++ b/android/KMEA/app/src/main/java/com/keyman/engine/util/KMLog.java @@ -13,6 +13,7 @@ import com.keyman.engine.KMManager; import com.keyman.engine.util.DependencyUtil; import com.keyman.engine.util.DependencyUtil.LibraryType; +import io.sentry.Breadcrumb; import io.sentry.Sentry; import io.sentry.SentryLevel; @@ -34,6 +35,48 @@ public final class KMLog { } } + /** + * Utility to log info and add as a Sentry breadcrumb, rather than + * as an independent message + * @param tag String of the caller + * @param msg String of the info message + */ + public static void LogBreadcrumb(String tag, String msg, boolean addStackTrace) { + if (msg == null || msg.isEmpty()) { + return; + } + + Log.i(tag, msg); + + if (!DependencyUtil.libraryExists(LibraryType.SENTRY) || !Sentry.isEnabled()) { + return; + } + + Breadcrumb crumb = new Breadcrumb(); + crumb.setMessage(msg); + crumb.setLevel(SentryLevel.INFO); + + if(addStackTrace) { + StackTraceElement[] rawTrace = Thread.currentThread().getStackTrace(); + + // The call that gets us the stack-trace above... shows up in the + // stack trace, so we'll skip the first few (redundant) entries. + int skipCount = 3; + + // Sentry does limit the size of messages... so let's just + // keep 10 entries and call it a day. + int limit = Math.min(rawTrace.length, 10 + skipCount); + if(rawTrace.length > skipCount) { + String[] trace = new String[limit - skipCount]; + for (int i = skipCount; i < limit; i++) { + trace[i-skipCount] = rawTrace[i].toString(); + } + crumb.setData("stacktrace", trace); + } + } + Sentry.addBreadcrumb(crumb); + } + /** * Utility to log error and send to Sentry * @param tag String of the caller diff --git a/common/models/templates/package.json b/common/models/templates/package.json index ec6bc8f7e6..2a25297f93 100644 --- a/common/models/templates/package.json +++ b/common/models/templates/package.json @@ -57,7 +57,7 @@ "c8": "^7.12.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "dependencies": { "@keymanapp/keyman-version": "*", diff --git a/common/models/types/package.json b/common/models/types/package.json index c3005a8eb5..a9f68f4ea1 100644 --- a/common/models/types/package.json +++ b/common/models/types/package.json @@ -29,7 +29,7 @@ }, "homepage": "https://github.com/keymanapp/keyman/tree/master/common/models/types#readme", "devDependencies": { - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "files": [ "README.md", diff --git a/common/models/wordbreakers/package.json b/common/models/wordbreakers/package.json index d3dd15493e..580e600c0e 100644 --- a/common/models/wordbreakers/package.json +++ b/common/models/wordbreakers/package.json @@ -54,8 +54,7 @@ "c8": "^7.12.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "type": "module" } diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index bd06f32175..978bcd6dcf 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -46,8 +46,7 @@ "@keymanapp/resources-gosh": "*", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "dependencies": { "@keymanapp/models-templates": "*", diff --git a/common/predictive-text/src/web/sourcemappedWorker.ts b/common/predictive-text/src/web/sourcemappedWorker.ts index da7ed77eb9..18a89ab05f 100644 --- a/common/predictive-text/src/web/sourcemappedWorker.ts +++ b/common/predictive-text/src/web/sourcemappedWorker.ts @@ -2,6 +2,9 @@ import unwrap from '../unwrap.js'; import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.js"; export default class SourcemappedWorker { + // the only difference to DefaultWorker is that this class uses + // the unminified LM* blobs + static constructInstance(): Worker { return new Worker(this.asBlobURI(LMLayerWorkerCode)); } diff --git a/common/test/resources/package.json b/common/test/resources/package.json index 9b4ddf1a69..9295190f2d 100644 --- a/common/test/resources/package.json +++ b/common/test/resources/package.json @@ -5,6 +5,6 @@ "license": "MIT", "devDependencies": { "@keymanapp/resources-gosh": "*", - "typescript": "^4.9.5" + "typescript": "^5.4.5" } } diff --git a/common/tools/sourcemap-path-remapper/package.json b/common/tools/sourcemap-path-remapper/package.json index ce5f88b195..312b3ed964 100644 --- a/common/tools/sourcemap-path-remapper/package.json +++ b/common/tools/sourcemap-path-remapper/package.json @@ -20,8 +20,7 @@ "@keymanapp/resources-gosh": "*", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "dependencies": { "convert-source-map": "^2.0.0" diff --git a/common/web/gesture-recognizer/.mocharc.env.js b/common/web/gesture-recognizer/.mocharc.env.js deleted file mode 100644 index 81e54c7365..0000000000 --- a/common/web/gesture-recognizer/.mocharc.env.js +++ /dev/null @@ -1,5 +0,0 @@ -import { fileURLToPath } from "url"; -import { dirname } from 'path'; - -// Tells ts-node where to find the tsconfig.json to be used for executing TS unit tests. -process.env.TS_NODE_PROJECT = `${dirname(fileURLToPath(import.meta.url))}/src/test/auto/tsconfig.json`; \ No newline at end of file diff --git a/common/web/gesture-recognizer/.mocharc.json b/common/web/gesture-recognizer/.mocharc.json deleted file mode 100644 index bf90c42bf4..0000000000 --- a/common/web/gesture-recognizer/.mocharc.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extensions": ["js", "ts"], - "spec" : [ - "src/test/auto/headless/**/*.*" - ], - "node-option": [ - "loader=ts-node/esm" - ], - "require": ".mocharc.env.js" -} \ No newline at end of file diff --git a/common/web/gesture-recognizer/package.json b/common/web/gesture-recognizer/package.json index 7bd7525a67..66cf248c0f 100644 --- a/common/web/gesture-recognizer/package.json +++ b/common/web/gesture-recognizer/package.json @@ -6,8 +6,7 @@ "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", "promise-status-async": "^1.2.10", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "imports": { "#tools": "./build/tools/obj/index.js" diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gesturePath.spec.ts b/common/web/gesture-recognizer/src/test/auto/headless/gesturePath.spec.ts index 8d32e726d7..111e00ee93 100644 --- a/common/web/gesture-recognizer/src/test/auto/headless/gesturePath.spec.ts +++ b/common/web/gesture-recognizer/src/test/auto/headless/gesturePath.spec.ts @@ -1,5 +1,5 @@ import { assert } from 'chai' -import sinon from 'sinon'; +import sinon from 'sinon'; import path from 'path'; import url from 'url'; import fs from 'fs'; @@ -8,9 +8,11 @@ import { GestureDebugPath } from '@keymanapp/gesture-recognizer'; import { timedPromise } from '@keymanapp/web-utils'; import { TouchpathTurtle } from '#tools'; -// Ensures that the resources are resolved relative to this script, not to the cwd when the test -// runner was launched. -const scriptFolder = path.dirname(url.fileURLToPath(import.meta.url)); +// Ensures that the resources are resolved relative to this script's source, not +// to the cwd when the test runner was launched or to its built version. +const scriptFolder = path.dirname(url.fileURLToPath(import.meta.url)) + .replace("build/test/auto", "src/test/auto") // Mac/Linux + .replace("build\\test\\auto", "src\\test\\auto"); // Windows const SEGMENT_TEST_JSON_FOLDER = path.resolve(`${scriptFolder}/../../resources/json/segmentation`); describe("GesturePath", function() { diff --git a/common/web/gesture-recognizer/src/test/auto/tsconfig.json b/common/web/gesture-recognizer/src/test/auto/tsconfig.json deleted file mode 100644 index e091004e2b..0000000000 --- a/common/web/gesture-recognizer/src/test/auto/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -/* - * VS Code Intellisense needs this helper in order to properly use subpath imports in the - * test specs found under the `headless` subfolder. Otherwise, it'll report errors while - * editing - even if the tests themselves actually work. - */ -{ - "extends": "../../../../tsconfig.kmw-main-base.json", - "compilerOptions": { - // Not needed when testing via Node, and when `true` it seems to desync preset breakpoints - // worse than when `false`. - "importHelpers": false, - // Makes the tests... somewhat tricky to handle properly. - "noImplicitAny": false - } -} diff --git a/common/web/gesture-recognizer/src/test/resources/@types/promiseStatus.d.ts b/common/web/gesture-recognizer/src/test/resources/@types/promiseStatus.d.ts new file mode 100644 index 0000000000..d51f97fce0 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/@types/promiseStatus.d.ts @@ -0,0 +1,47 @@ +declare module 'promise-status-async' { + export type PromisePredicate = (p: Promise) => Promise + + export enum PromiseStatuses { + PROMISE_PENDING = 'pending', + PROMISE_RESOLVED = 'resolved', + PROMISE_REJECTED = 'rejected' + } + + export type PromisePendingState = { + status: PromiseStatuses.PROMISE_PENDING + } + + export type PromiseResolvedState = { + status: PromiseStatuses.PROMISE_RESOLVED, + value: T + } + + export type PromiseRejectedState = { + status: PromiseStatuses.PROMISE_REJECTED, + reason: T + } + + export type PromiseState = PromisePendingState | PromiseResolvedState | PromiseRejectedState + export const promiseState: (promise: T | Promise) => PromiseState + export const promiseStatus: (promise: T | Promise) => PromiseStatuses + + const PROMISE_PENDING: PromiseStatuses.PROMISE_PENDING + const PROMISE_RESOLVED: PromiseStatuses.PROMISE_RESOLVED + const PROMISE_REJECTED: PromiseStatuses.PROMISE_REJECTED + + const PromisePredicates: { + isPromisePending: PromisePredicate, + isPromiseResolved: PromisePredicate, + isPromiseRejected: PromisePredicate, + isPromiseNotPending: PromisePredicate, + isPromiseNotResolved: PromisePredicate, + isPromiseNotRejected: PromisePredicate + } + + const isPromisePending: PromisePredicate + const isPromiseResolved: PromisePredicate + const isPromiseRejected: PromisePredicate + const isPromiseNotPending: PromisePredicate + const isPromiseNotResolved: PromisePredicate + const isPromiseNotRejected: PromisePredicate +} diff --git a/common/web/gesture-recognizer/src/test/resources/simulateMultiSourceInput.ts b/common/web/gesture-recognizer/src/test/resources/simulateMultiSourceInput.ts index 164919d31c..b8501326fb 100644 --- a/common/web/gesture-recognizer/src/test/resources/simulateMultiSourceInput.ts +++ b/common/web/gesture-recognizer/src/test/resources/simulateMultiSourceInput.ts @@ -1,6 +1,5 @@ -import { InputSample, buildGestureMatchInspector, GestureSource, gestures } from '@keymanapp/gesture-recognizer'; +import { InputSample, buildGestureMatchInspector, GestureSource, GestureSourceSubview, gestures } from '@keymanapp/gesture-recognizer'; import { ManagedPromise, timedPromise } from '@keymanapp/web-utils'; -import { GestureSourceSubview } from '../../../build/obj/headless/gestureSource.js'; type GestureMatcher = gestures.matchers.GestureMatcher; type MatcherSelection = gestures.matchers.MatcherSelection; @@ -80,7 +79,7 @@ function prepareSourcesFromPriorMatcher( ): ReturnType> { const spec = contactSpec; const existingSources = spec.matcher.sources.map((src) => { - return src instanceof GestureSourceSubview ? src.baseSource : src; + return src instanceof GestureSourceSubview ? src.baseSource : src; }); const sequences = spec.continuation; @@ -330,7 +329,7 @@ export function simulateMultiSourceMatcherInput( const config: SimulationConfig, Type> = { construction: (source) => new gestures.matchers.GestureMatcher(modelSpec, source), addSource: (obj, source) => { - if(source instanceof GestureSource) { + if(source instanceof GestureSource) { obj.addContact(source) } else { throw new Error("Error in internal sim-engine configuration"); diff --git a/common/web/gesture-recognizer/src/test/tsconfig.json b/common/web/gesture-recognizer/src/test/tsconfig.json new file mode 100644 index 0000000000..4eea3ef803 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/tsconfig.json @@ -0,0 +1,30 @@ +/* + * VS Code Intellisense needs this helper in order to properly use subpath imports in the + * test specs found under the `headless` subfolder. Otherwise, it'll report errors while + * editing - even if the tests themselves actually work. + */ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "../../build/test/", + // Not needed when testing via Node, and when `true` it seems to desync preset breakpoints + // worse than when `false`. + "importHelpers": false, + + "allowJs": true, + + "baseUrl": "../../", + "tsBuildInfoFile": "../../build/test/tsconfig.tsbuildinfo", + "rootDir": "./", + "typeRoots": ["./resources/@types", "../../../../../node_modules/@types"] + }, + "include": [ + "./auto/headless/**/*.ts", + "./resources/**/*.ts" + ], + // Undo the base config's exclude. + "exclude": ["../../../../../node_modules/promise-status-async/lib/index.d.ts"], + "references": [ + {"path": "../../"} + ] +} diff --git a/common/web/gesture-recognizer/test.sh b/common/web/gesture-recognizer/test.sh index dca49780a1..03d0e32fa1 100755 --- a/common/web/gesture-recognizer/test.sh +++ b/common/web/gesture-recognizer/test.sh @@ -45,7 +45,12 @@ test-headless ( ) { MOCHA_FLAGS="$MOCHA_FLAGS --reporter mocha-teamcity-reporter" fi - c8 mocha --recursive $MOCHA_FLAGS ./src/test/auto/headless/ + # The currently-bundled declaration file for this package generates errors when compiling against it + # with current tsc versions. + rm -f "${KEYMAN_ROOT}/node_modules/promise-status-async/lib/index.d.ts" + + tsc -b ./src/test/tsconfig.json + c8 mocha --recursive $MOCHA_FLAGS ./build/test/auto/headless/ } test-browser ( ) { diff --git a/common/web/input-processor/package.json b/common/web/input-processor/package.json index eb618b335e..6a81cacde7 100644 --- a/common/web/input-processor/package.json +++ b/common/web/input-processor/package.json @@ -21,8 +21,7 @@ "@keymanapp/resources-gosh": "*", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "scripts": { "test": "gosh ./test.sh" diff --git a/common/web/keyboard-processor/package.json b/common/web/keyboard-processor/package.json index 3e281508e1..3f0a595897 100644 --- a/common/web/keyboard-processor/package.json +++ b/common/web/keyboard-processor/package.json @@ -21,8 +21,7 @@ "c8": "^7.12.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "scripts": { "build": "gosh build.sh", diff --git a/common/web/keyman-version/package.json b/common/web/keyman-version/package.json index ac33bdc690..bdd2e9bc84 100644 --- a/common/web/keyman-version/package.json +++ b/common/web/keyman-version/package.json @@ -13,6 +13,6 @@ "license": "MIT", "type": "module", "devDependencies": { - "typescript": "^4.9.5" + "typescript": "^5.4.5" } } diff --git a/common/web/lm-message-types/package.json b/common/web/lm-message-types/package.json index 01e880c9b2..835a95045b 100644 --- a/common/web/lm-message-types/package.json +++ b/common/web/lm-message-types/package.json @@ -7,7 +7,7 @@ }, "license": "MIT", "devDependencies": { - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "files": [ "message.d.ts" diff --git a/common/web/lm-worker/package.json b/common/web/lm-worker/package.json index 77bbc1d49f..1402fe5828 100644 --- a/common/web/lm-worker/package.json +++ b/common/web/lm-worker/package.json @@ -34,8 +34,7 @@ "combine-source-map": "^0.8.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "dependencies": { "@keymanapp/keyman-version": "*", diff --git a/common/web/lm-worker/src/main/index.ts b/common/web/lm-worker/src/main/index.ts index e775ed5d35..3873dd19ab 100644 --- a/common/web/lm-worker/src/main/index.ts +++ b/common/web/lm-worker/src/main/index.ts @@ -328,12 +328,12 @@ export default class LMLayerWorker { switch(payload.message) { case 'predict': var {transform, context} = payload; - var suggestions = compositor.predict(transform, context); - - // Now that the suggestions are ready, send them out! - this.cast('suggestions', { - token: payload.token, - suggestions: suggestions + compositor.predict(transform, context).then((suggestions) => { + // Now that the suggestions are ready, send them out! + this.cast('suggestions', { + token: payload.token, + suggestions: suggestions + }); }); break; case 'wordbreak': @@ -358,11 +358,12 @@ export default class LMLayerWorker { break; case 'revert': var {reversion, context} = payload; - var suggestions: Suggestion[] = compositor.applyReversion(reversion, context); - this.cast('postrevert', { - token: payload.token, - suggestions: suggestions + compositor.applyReversion(reversion, context).then((suggestions) => { + this.cast('postrevert', { + token: payload.token, + suggestions: suggestions + }); }); break; case 'reset-context': diff --git a/common/web/lm-worker/src/main/model-compositor.ts b/common/web/lm-worker/src/main/model-compositor.ts index 492bb1c730..bf99e65abd 100644 --- a/common/web/lm-worker/src/main/model-compositor.ts +++ b/common/web/lm-worker/src/main/model-compositor.ts @@ -65,7 +65,7 @@ export default class ModelCompositor { return returnedPredictions; } - predict(transformDistribution: Transform | Distribution, context: Context): Suggestion[] { + async predict(transformDistribution: Transform | Distribution, context: Context): Promise { let suggestionDistribution: Distribution = []; let lexicalModel = this.lexicalModel; let punctuation = this.punctuation; @@ -249,21 +249,11 @@ export default class ModelCompositor { // Worth considering: extend Traversal to allow direct prediction lookups? // let traversal = match.finalTraversal; - // Find a proper Transform ID to map the correction to. - // Without it, we can't apply the suggestion. - let finalInput: Transform; - if(match.inputSequence.length > 0) { - // common case: from the same keystroke `inputTransform`, with matching `.id`. - finalInput = match.inputSequence[match.inputSequence.length - 1].sample; - } else { - finalInput = inputTransform; // A fallback measure. Greatly matters for empty contexts. - } - // Replace the existing context with the correction. let correctionTransform: Transform = { insert: correction, // insert correction string deleteLeft: deleteLeft, - id: finalInput.id // The correction should always be based on the most recent external transform/transcription ID. + id: inputTransform.id // The correction should always be based on the most recent external transform/transcription ID. } let rootCost = match.totalCost; @@ -658,18 +648,19 @@ export default class ModelCompositor { return reversion; } - applyReversion(reversion: Reversion, context: Context): Suggestion[] { + async applyReversion(reversion: Reversion, context: Context): Promise { // If we are unable to track context (because the model does not support LexiconTraversal), // we need a "fallback" strategy. let compositor = this; - let fallbackSuggestions = function() { + let fallbackSuggestions = async function() { let revertedContext = models.applyTransform(reversion.transform, context); - let suggestions = compositor.predict({insert: '', deleteLeft: 0}, revertedContext); + const suggestions = await compositor.predict({insert: '', deleteLeft: 0}, revertedContext); suggestions.forEach(function(suggestion) { // A reversion's transform ID is the additive inverse of its original suggestion; // we revert to the state of said original suggestion. suggestion.transformId = -reversion.transformId; }); + return suggestions; } diff --git a/common/web/lm-worker/src/test/mocha/cases/worker-custom-punctuation.js b/common/web/lm-worker/src/test/mocha/cases/worker-custom-punctuation.js index 8114ac530a..e5c0eacc80 100644 --- a/common/web/lm-worker/src/test/mocha/cases/worker-custom-punctuation.js +++ b/common/web/lm-worker/src/test/mocha/cases/worker-custom-punctuation.js @@ -8,7 +8,7 @@ import DummyModel from '#./models/dummy-model.js'; import ModelCompositor from '#./model-compositor.js'; describe('Custom Punctuation', function () { - it('appears in the keep suggestion', function () { + it('appears in the keep suggestion', async function () { let dummySuggestions = [{ transform: { insert: 'Hello', @@ -37,7 +37,7 @@ describe('Custom Punctuation', function () { // The model compositor is responsible for adding this to the display as // string. var composite = new ModelCompositor(model, true); - var suggestions = composite.predict([{ sample: { insert: 'o', deleteLeft: 0 }, p: 1.00 }], { + var suggestions = await composite.predict([{ sample: { insert: 'o', deleteLeft: 0 }, p: 1.00 }], { left: 'Hrll', startOfBuffer: false, endOfBuffer: true }); assert.lengthOf(suggestions, 3); @@ -54,7 +54,7 @@ describe('Custom Punctuation', function () { }); describe("insertAfterWord", function () { - it('appears after "word" suggestion', function () { + it('appears after "word" suggestion', async function () { let dummySuggestions = [ { transform: { insert: 'ᚈᚑᚋ', deleteLeft: 0, }, @@ -82,7 +82,7 @@ describe('Custom Punctuation', function () { // The model compositor is responsible for adding this to the display as // string. var composite = new ModelCompositor(model, true); - var suggestions = composite.predict([{ sample: { insert: 'ᚋ', deleteLeft: 0 }, p: 1.00 }], { + var suggestions = await composite.predict([{ sample: { insert: 'ᚋ', deleteLeft: 0 }, p: 1.00 }], { left: '᚛ᚈᚑ', startOfBuffer: false, endOfBuffer: true }); assert.lengthOf(suggestions, dummySuggestions.length); diff --git a/common/web/lm-worker/src/test/mocha/cases/worker-model-compositor.js b/common/web/lm-worker/src/test/mocha/cases/worker-model-compositor.js index 82672b4a88..716c28c61c 100644 --- a/common/web/lm-worker/src/test/mocha/cases/worker-model-compositor.js +++ b/common/web/lm-worker/src/test/mocha/cases/worker-model-compositor.js @@ -19,7 +19,7 @@ describe('ModelCompositor', function() { {wordBreaker: wordBreakers.default} ); - it('generates suggestions with expected properties', function() { + it('generates suggestions with expected properties', async function() { let compositor = new ModelCompositor(plainModel, true); let context = { left: 'th', startOfBuffer: true, endOfBuffer: true, @@ -30,7 +30,7 @@ describe('ModelCompositor', function() { deleteLeft: 0 }; - let suggestions = compositor.predict(inputTransform, context); + let suggestions = await compositor.predict(inputTransform, context); suggestions.forEach(function(suggestion) { // Suggstions are built based on the context state BEFORE the triggering // input, replacing the prediction's root with the complete word. @@ -55,7 +55,7 @@ describe('ModelCompositor', function() { }); }); - it('strongly avoids corrections for single-character roots', function() { + it('strongly avoids corrections for single-character roots', async function() { let compositor = new ModelCompositor(plainModel, true); let context = { left: '', startOfBuffer: true, endOfBuffer: true, @@ -68,8 +68,8 @@ describe('ModelCompositor', function() { {sample: {insert: 'a', deleteLeft: 0}, p: 0.4} // but at lower weight than 'and' (998). ]; - compositor.predict({insert: '', deleteLeft: 0}, context); // Initialize context tracking first! - let suggestions = compositor.predict(inputDistribution, context); + await compositor.predict({insert: '', deleteLeft: 0}, context); // Initialize context tracking first! + let suggestions = await compositor.predict(inputDistribution, context); // remove the keep suggestion; we're not testing that here. suggestions = suggestions.filter((suggestion) => suggestion.tag != 'keep'); @@ -94,7 +94,7 @@ describe('ModelCompositor', function() { assert.isUndefined(aRange.find((suggestion) => suggestion.transform.insert.charAt(0) == 'q')); }); - it('properly handles suggestions after a backspace', function() { + it('properly handles suggestions after a backspace', async function() { let compositor = new ModelCompositor(plainModel, true); let context = { left: 'the ', startOfBuffer: true, endOfBuffer: true, @@ -105,7 +105,7 @@ describe('ModelCompositor', function() { deleteLeft: 1 }; - let suggestions = compositor.predict(inputTransform, context); + let suggestions = await compositor.predict(inputTransform, context); suggestions.forEach(function(suggestion) { // Suggestions always delete the full root of the suggestion. // @@ -117,7 +117,7 @@ describe('ModelCompositor', function() { }); }); - it('properly handles suggestions for the first letter after a ` `', function() { + it('properly handles suggestions for the first letter after a ` `', async function() { let compositor = new ModelCompositor(plainModel, true); let context = { left: 'the', startOfBuffer: true, endOfBuffer: true, @@ -128,7 +128,7 @@ describe('ModelCompositor', function() { deleteLeft: 0 }; - let suggestions = compositor.predict(inputTransform, context); + let suggestions = await compositor.predict(inputTransform, context); suggestions.forEach(function(suggestion) { // After a space, predictions are based on a new, zero-length root. // With nothing to replace, .deleteLeft should be zero. @@ -136,7 +136,7 @@ describe('ModelCompositor', function() { }); }); - it('properly handles suggestions for the first letter after a `\'`', function() { + it('properly handles suggestions for the first letter after a `\'`', async function() { let compositor = new ModelCompositor(plainModel, true); let context = { left: "the '", startOfBuffer: true, endOfBuffer: true, @@ -149,7 +149,7 @@ describe('ModelCompositor', function() { deleteLeft: 0 }; - let suggestions = compositor.predict(inputTransform, context); + let suggestions = await compositor.predict(inputTransform, context); suggestions.forEach(function(suggestion) { // Suggestions always delete the full root of the suggestion. // Which, here, didn't exist before the input. Nothing to @@ -320,20 +320,20 @@ describe('ModelCompositor', function() { } ); - it('should produce suggestions from uncased input', function() { + it('should produce suggestions from uncased input', async function() { let model = uncasedModel; - var composite = new ModelCompositor(model, true); + var compositor = new ModelCompositor(model, true); // Initialize context let context = { left: 'th', startOfBuffer: false, endOfBuffer: true, }; - composite.predict({insert: '', deleteLeft: 0}, context); + await compositor.predict({insert: '', deleteLeft: 0}, context); // Pretend to fat finger "the" as "thr" var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 }; var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 }; - var suggestions = composite.predict([thr, the], context); + var suggestions = await compositor.predict([thr, the], context); // Get the top suggest for 'the' and 'thr*'. var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'the' || s.displayAs === '“the”'; })[0]; @@ -348,20 +348,20 @@ describe('ModelCompositor', function() { assert.isAbove(theSuggestion.p, thrSuggestion.p); }); - it('should not produce suggestions from cased input', function() { + it('should not produce suggestions from cased input', async function() { let model = uncasedModel; - var composite = new ModelCompositor(model, true); + var compositor = new ModelCompositor(model, true); // Initialize context let context = { left: 'TH', startOfBuffer: false, endOfBuffer: true, }; - composite.predict({insert: '', deleteLeft: 0}, context); + await compositor.predict({insert: '', deleteLeft: 0}, context); // Pretend to fat finger "the" as "thr" var the = { sample: { insert: 'R', deleteLeft: 0}, p: 0.45 }; var thr = { sample: { insert: 'E', deleteLeft: 0}, p: 0.55 }; - var suggestions = composite.predict([thr, the], context); + var suggestions = await compositor.predict([thr, the], context); // We should only receive a 'keep' suggestion. assert.equal(suggestions.length, 1); @@ -395,20 +395,20 @@ describe('ModelCompositor', function() { } ); - it('should produce suggestions from uncased input', function() { + it('should produce suggestions from uncased input', async function() { let model = casedModel; - var composite = new ModelCompositor(model, true); + let compositor = new ModelCompositor(model, true); // Initialize context let context = { left: 'th', startOfBuffer: false, endOfBuffer: true, }; - composite.predict({insert: '', deleteLeft: 0}, context); + await compositor.predict({insert: '', deleteLeft: 0}, context); // Pretend to fat finger "the" as "thr" var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 }; var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 }; - var suggestions = composite.predict([thr, the], context); + var suggestions = await compositor.predict([thr, the], context); // Get the top suggest for 'the' and 'thr*'. var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'the' || s.displayAs === '“the”'; })[0]; @@ -423,20 +423,20 @@ describe('ModelCompositor', function() { assert.isAbove(theSuggestion.p, thrSuggestion.p); }); - it('should produce capitalized suggestions from fully-uppercased input', function() { + it('should produce capitalized suggestions from fully-uppercased input', async function() { let model = casedModel; - var composite = new ModelCompositor(model, true); + let compositor = new ModelCompositor(model, true); // Initialize context let context = { left: 'TH', startOfBuffer: false, endOfBuffer: true, }; - composite.predict({insert: '', deleteLeft: 0}, context); + await compositor.predict({insert: '', deleteLeft: 0}, context); // Pretend to fat finger "the" as "thr" var the = { sample: { insert: 'R', deleteLeft: 0}, p: 0.45 }; var thr = { sample: { insert: 'E', deleteLeft: 0}, p: 0.55 }; - var suggestions = composite.predict([thr, the], context); + var suggestions = await compositor.predict([thr, the], context); // Get the top suggest for 'the' and 'thr*'. var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'THE' || s.displayAs === '“THE”'; })[0]; @@ -451,20 +451,20 @@ describe('ModelCompositor', function() { assert.isAbove(theSuggestion.p, thrSuggestion.p); }); - it('should produce "initial-case" suggestions from input with an initial capital', function() { + it('should produce "initial-case" suggestions from input with an initial capital', async function() { let model = casedModel; - var composite = new ModelCompositor(model, true); + let compositor = new ModelCompositor(model, true); // Initialize context let context = { left: 'Th', startOfBuffer: false, endOfBuffer: true, }; - composite.predict({insert: '', deleteLeft: 0}, context); + await compositor.predict({insert: '', deleteLeft: 0}, context); // Pretend to fat finger "the" as "thr" var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 }; var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 }; - var suggestions = composite.predict([thr, the], context); + var suggestions = await compositor.predict([thr, the], context); // Get the top suggest for 'the' and 'thr*'. var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'The' || s.displayAs === '“The”'; })[0]; @@ -479,20 +479,20 @@ describe('ModelCompositor', function() { assert.isAbove(theSuggestion.p, thrSuggestion.p); }); - it('also from input with partial capitalization when including an initial capital', function() { + it('also from input with partial capitalization when including an initial capital', async function() { let model = casedModel; - var composite = new ModelCompositor(model, true); + let compositor = new ModelCompositor(model, true); // Initialize context let context = { left: 'TH', startOfBuffer: false, endOfBuffer: true, }; - composite.predict({insert: '', deleteLeft: 0}, context); + await compositor.predict({insert: '', deleteLeft: 0}, context); // Pretend to fat finger "the" as "thr" var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 }; var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 }; - var suggestions = composite.predict([thr, the], context); + var suggestions = await compositor.predict([thr, the], context); // Get the top suggest for 'the' and 'thr*'. var theSuggestion = suggestions.filter(function (s) { return s.displayAs.startsWith('The'); })[0]; @@ -508,22 +508,22 @@ describe('ModelCompositor', function() { }); describe('Prediction with legacy Models (12.0 / 13.0)', function() { - it('should compose suggestions from a fat-fingered keypress (no keying needed)', function () { + it('should compose suggestions from a fat-fingered keypress (no keying needed)', async function () { var model = new TrieModel( jsonFixture('models/tries/english-1000') ); - var composite = new ModelCompositor(model, true); + let compositor = new ModelCompositor(model, true); // Initialize context let context = { left: 'th', startOfBuffer: false, endOfBuffer: true, }; - composite.predict({insert: '', deleteLeft: 0}, context); + await compositor.predict({insert: '', deleteLeft: 0}, context); // Pretend to fat finger "the" as "thr" var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 }; var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 }; - var suggestions = composite.predict([thr, the], context); + var suggestions = await compositor.predict([thr, the], context); // Get the top suggest for 'the' and 'thr*'. var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'the' || s.displayAs === '“the”'; })[0]; @@ -538,22 +538,22 @@ describe('ModelCompositor', function() { assert.isAbove(theSuggestion.p, thrSuggestion.p); }); - it('should compose suggestions from a fat-fingered keypress (keying needed)', function () { + it('should compose suggestions from a fat-fingered keypress (keying needed)', async function () { var model = new TrieModel( jsonFixture('models/tries/english-1000') ); - var composite = new ModelCompositor(model, true); + let compositor = new ModelCompositor(model, true); // Initialize context let context = { left: 'Th', startOfBuffer: false, endOfBuffer: true, }; - composite.predict({insert: '', deleteLeft: 0}, context); + await compositor.predict({insert: '', deleteLeft: 0}, context); // Pretend to fat finger "the" as "thr" var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 }; var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 }; - var suggestions = composite.predict([thr, the], context); + var suggestions = await compositor.predict([thr, the], context); // Get the top suggest for 'the' and 'thr*'. // As of 15.0+, because of #5429, the keep suggestion `"The"` will not be merged @@ -841,7 +841,7 @@ describe('ModelCompositor', function() { // While this isn't a state the LMLayer should ever operate within, this provides // a useful base state for developing further tests against the method. - it('model without traversals: returns appropriate suggestions upon reversion', function() { + it('model without traversals: returns appropriate suggestions upon reversion', async function() { // This setup matches 'acceptSuggestion' the test case // it('first word of context + postTransform provided, .deleteLeft > 0') // seen earlier in the file. @@ -878,7 +878,7 @@ describe('ModelCompositor', function() { let appliedContext = models.applyTransform(baseSuggestion.transform, baseContext); assert.equal(appliedContext.left, "hello "); - let suggestions = compositor.applyReversion(reversion, appliedContext); + let suggestions = await compositor.applyReversion(reversion, appliedContext); // As this test is a bit... 'hard-wired', we only get the 'keep' suggestion. // It should still be accurate, though. @@ -891,7 +891,7 @@ describe('ModelCompositor', function() { assert.deepEqual(suggestions[0].transform, expectedTransform); }); - it('model with traversals: returns appropriate suggestions upon reversion', function() { + it('model with traversals: returns appropriate suggestions upon reversion', async function() { // This setup matches 'acceptSuggestion' the test case // it('first word of context + postTransform provided, .deleteLeft > 0') // seen earlier in the file. @@ -911,7 +911,7 @@ describe('ModelCompositor', function() { let model = new models.TrieModel(jsonFixture('models/tries/english-1000'), {punctuation: englishPunctuation}); let compositor = new ModelCompositor(model, true); - let initialSuggestions = compositor.predict(postTransform, baseContext); + let initialSuggestions = await compositor.predict(postTransform, baseContext); let keepSuggestion = initialSuggestions[0]; assert.equal(keepSuggestion.tag, 'keep'); // corresponds to `postTransform`, but the transform isn't equal. @@ -921,13 +921,13 @@ describe('ModelCompositor', function() { assert.equal(reversion.id, -baseSuggestion.id); let appliedContext = models.applyTransform(baseSuggestion.transform, baseContext); - let reversionSuggestions = compositor.applyReversion(reversion, appliedContext); + let reversionSuggestions = await compositor.applyReversion(reversion, appliedContext); // The returned suggestion list should match the original suggestion list. assert.deepEqual(reversionSuggestions, initialSuggestions); }); - it('model with traversals: properly tracks context state', function() { + it('model with traversals: properly tracks context state', async function() { // Could be merged with the previous test case, but I think it's good to have the error // sets flagged separately. @@ -946,7 +946,7 @@ describe('ModelCompositor', function() { let model = new models.TrieModel(jsonFixture('models/tries/english-1000'), {punctuation: englishPunctuation}); let compositor = new ModelCompositor(model, true); - let initialSuggestions = compositor.predict(postTransform, baseContext); + let initialSuggestions = await compositor.predict(postTransform, baseContext); const suggestionContextState = compositor.contextTracker.newest; let keepSuggestion = initialSuggestions[0]; diff --git a/common/web/lm-worker/src/test/mocha/cases/worker-predict.js b/common/web/lm-worker/src/test/mocha/cases/worker-predict.js index f050998e87..cbc4d433d3 100644 --- a/common/web/lm-worker/src/test/mocha/cases/worker-predict.js +++ b/common/web/lm-worker/src/test/mocha/cases/worker-predict.js @@ -8,10 +8,11 @@ import LMLayerWorker from '#./index.js'; import { configWorker, createMessageEventWithData, emptyContext, iGotDistractedByHazel, importScriptsWith, randomToken, zeroTransform } from '@keymanapp/common-test-resources/model-helpers.mjs'; +import { timedPromise } from '@keymanapp/web-utils'; describe('LMLayerWorker', function () { describe('#predict()', function () { - it('should send back suggestions', function () { + it('should send back suggestions', async function () { var suggestion = { transform: { insert: 'I ', @@ -63,6 +64,10 @@ describe('LMLayerWorker', function () { context: emptyContext() })); + // predict() is async, so we need to relinquish control flow temporarily + // in order for a return message to become available. + await timedPromise(500); + // Retrieve the internal 'dummy' suggestions for comparison. var hazel = iGotDistractedByHazel(); diff --git a/common/web/recorder/package.json b/common/web/recorder/package.json index 7dcc696cda..4812d8a778 100644 --- a/common/web/recorder/package.json +++ b/common/web/recorder/package.json @@ -25,6 +25,6 @@ "@keymanapp/web-utils": "*" }, "devDependencies": { - "typescript": "^4.9.5" + "typescript": "^5.4.5" } } diff --git a/common/web/sentry-manager/package.json b/common/web/sentry-manager/package.json index a3e6c142bc..54f5f64dce 100644 --- a/common/web/sentry-manager/package.json +++ b/common/web/sentry-manager/package.json @@ -17,7 +17,7 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "dependencies": { "@keymanapp/keyman-version": "*", diff --git a/common/web/tslib/package.json b/common/web/tslib/package.json index ceec8c6409..3590694858 100644 --- a/common/web/tslib/package.json +++ b/common/web/tslib/package.json @@ -18,7 +18,7 @@ }, "dependencies": { "tslib": "^2.5.2", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "devDependencies": { "@keymanapp/resources-gosh": "*", diff --git a/common/web/types/build.sh b/common/web/types/build.sh index 4452bc7646..df11540b42 100755 --- a/common/web/types/build.sh +++ b/common/web/types/build.sh @@ -89,10 +89,14 @@ function copy_cldr_imports() { function do_configure() { compile_schemas - copy_cldr_imports verify_npm_setup } +function do_build() { + copy_cldr_imports + tsc --build +} + function do_test() { eslint . tsc --build test @@ -103,6 +107,6 @@ function do_test() { builder_run_action clean rm -rf ./build/ ./tsconfig.tsbuildinfo builder_run_action configure do_configure -builder_run_action build tsc --build +builder_run_action build do_build builder_run_action test do_test builder_run_action publish builder_publish_npm diff --git a/common/web/types/package.json b/common/web/types/package.json index de9ad69360..9327e48590 100644 --- a/common/web/types/package.json +++ b/common/web/types/package.json @@ -31,9 +31,10 @@ "dependencies": { "@keymanapp/ldml-keyboard-constants": "*", "@keymanapp/keyman-version": "*", - "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", + "restructure": "3.0.1", "semver": "^7.5.2", - "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" }, "devDependencies": { "@types/chai": "^4.1.7", @@ -50,8 +51,7 @@ "git-diff": "^2.0.6", "hexy": "^0.3.4", "mocha": "^8.4.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "mocha": { "spec": "build/test/**/test-*.js", @@ -70,6 +70,7 @@ ], "exclude-after-remap": true, "exclude": [ + "src/deps/", "src/kmx/kmx-plus-builder/", "src/kmx/kmx-plus.ts", "src/kmx/kmx-builder.ts", diff --git a/common/web/types/src/deps/xml2js/LICENSE b/common/web/types/src/deps/xml2js/LICENSE new file mode 100644 index 0000000000..e3b4222a66 --- /dev/null +++ b/common/web/types/src/deps/xml2js/LICENSE @@ -0,0 +1,19 @@ +Copyright 2010, 2011, 2012, 2013. All rights reserved. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to +deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +sell copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS +IN THE SOFTWARE. diff --git a/common/web/types/src/deps/xml2js/README.md b/common/web/types/src/deps/xml2js/README.md new file mode 100644 index 0000000000..67f2104a51 --- /dev/null +++ b/common/web/types/src/deps/xml2js/README.md @@ -0,0 +1,507 @@ +node-xml2js +=========== + +Ever had the urge to parse XML? And wanted to access the data in some sane, +easy way? Don't want to compile a C parser, for whatever reason? Then xml2js is +what you're looking for! + +Description +=========== + +Simple XML to JavaScript object converter. It supports bi-directional conversion. +Uses [sax-js](https://github.com/isaacs/sax-js/) and +[xmlbuilder-js](https://github.com/oozcitak/xmlbuilder-js/). + +Note: If you're looking for a full DOM parser, you probably want +[JSDom](https://github.com/tmpvar/jsdom). + +Installation +============ + +Simplest way to install `xml2js` is to use [npm](http://npmjs.org), just `npm +install xml2js` which will download xml2js and all dependencies. + +xml2js is also available via [Bower](http://bower.io/), just `bower install +xml2js` which will download xml2js and all dependencies. + +Usage +===== + +No extensive tutorials required because you are a smart developer! The task of +parsing XML should be an easy one, so let's make it so! Here's some examples. + +Shoot-and-forget usage +---------------------- + +You want to parse XML as simple and easy as possible? It's dangerous to go +alone, take this: + +```javascript +var parseString = require('xml2js').parseString; +var xml = "Hello xml2js!" +parseString(xml, function (err, result) { + console.dir(result); +}); +``` + +Can't get easier than this, right? This works starting with `xml2js` 0.2.3. +With CoffeeScript it looks like this: + +```coffeescript +{parseString} = require 'xml2js' +xml = "Hello xml2js!" +parseString xml, (err, result) -> + console.dir result +``` + +If you need some special options, fear not, `xml2js` supports a number of +options (see below), you can specify these as second argument: + +```javascript +parseString(xml, {trim: true}, function (err, result) { +}); +``` + +Simple as pie usage +------------------- + +That's right, if you have been using xml-simple or a home-grown +wrapper, this was added in 0.1.11 just for you: + +```javascript +var fs = require('fs'), + xml2js = require('xml2js'); + +var parser = new xml2js.Parser(); +fs.readFile(__dirname + '/foo.xml', function(err, data) { + parser.parseString(data, function (err, result) { + console.dir(result); + console.log('Done'); + }); +}); +``` + +Look ma, no event listeners! + +You can also use `xml2js` from +[CoffeeScript](https://github.com/jashkenas/coffeescript), further reducing +the clutter: + +```coffeescript +fs = require 'fs', +xml2js = require 'xml2js' + +parser = new xml2js.Parser() +fs.readFile __dirname + '/foo.xml', (err, data) -> + parser.parseString data, (err, result) -> + console.dir result + console.log 'Done.' +``` + +But what happens if you forget the `new` keyword to create a new `Parser`? In +the middle of a nightly coding session, it might get lost, after all. Worry +not, we got you covered! Starting with 0.2.8 you can also leave it out, in +which case `xml2js` will helpfully add it for you, no bad surprises and +inexplicable bugs! + +Promise usage +------------- + +```javascript +var xml2js = require('xml2js'); +var xml = ''; + +// With parser +var parser = new xml2js.Parser(/* options */); +parser.parseStringPromise(xml).then(function (result) { + console.dir(result); + console.log('Done'); +}) +.catch(function (err) { + // Failed +}); + +// Without parser +xml2js.parseStringPromise(xml /*, options */).then(function (result) { + console.dir(result); + console.log('Done'); +}) +.catch(function (err) { + // Failed +}); +``` + +Parsing multiple files +---------------------- + +If you want to parse multiple files, you have multiple possibilities: + + * You can create one `xml2js.Parser` per file. That's the recommended one + and is promised to always *just work*. + * You can call `reset()` on your parser object. + * You can hope everything goes well anyway. This behaviour is not + guaranteed work always, if ever. Use option #1 if possible. Thanks! + +So you wanna some JSON? +----------------------- + +Just wrap the `result` object in a call to `JSON.stringify` like this +`JSON.stringify(result)`. You get a string containing the JSON representation +of the parsed object that you can feed to JSON-hungry consumers. + +Displaying results +------------------ + +You might wonder why, using `console.dir` or `console.log` the output at some +level is only `[Object]`. Don't worry, this is not because `xml2js` got lazy. +That's because Node uses `util.inspect` to convert the object into strings and +that function stops after `depth=2` which is a bit low for most XML. + +To display the whole deal, you can use `console.log(util.inspect(result, false, +null))`, which displays the whole result. + +So much for that, but what if you use +[eyes](https://github.com/cloudhead/eyes.js) for nice colored output and it +truncates the output with `…`? Don't fear, there's also a solution for that, +you just need to increase the `maxLength` limit by creating a custom inspector +`var inspect = require('eyes').inspector({maxLength: false})` and then you can +easily `inspect(result)`. + +XML builder usage +----------------- + +Since 0.4.0, objects can be also be used to build XML: + +```javascript +var xml2js = require('xml2js'); + +var obj = {name: "Super", Surname: "Man", age: 23}; + +var builder = new xml2js.Builder(); +var xml = builder.buildObject(obj); +``` +will result in: + +```xml + + + Super + Man + 23 + +``` + +At the moment, a one to one bi-directional conversion is guaranteed only for +default configuration, except for `attrkey`, `charkey` and `explicitArray` options +you can redefine to your taste. Writing CDATA is supported via setting the `cdata` +option to `true`. + +To specify attributes: +```javascript +var xml2js = require('xml2js'); + +var obj = {root: {$: {id: "my id"}, _: "my inner text"}}; + +var builder = new xml2js.Builder(); +var xml = builder.buildObject(obj); +``` +will result in: +```xml + +my inner text +``` + +### Adding xmlns attributes + +You can generate XML that declares XML namespace prefix / URI pairs with xmlns attributes. + +Example declaring a default namespace on the root element: + +```javascript +let obj = { + Foo: { + $: { + "xmlns": "http://foo.com" + } + } +}; +``` +Result of `buildObject(obj)`: +```xml + +``` +Example declaring non-default namespaces on non-root elements: +```javascript +let obj = { + 'foo:Foo': { + $: { + 'xmlns:foo': 'http://foo.com' + }, + 'bar:Bar': { + $: { + 'xmlns:bar': 'http://bar.com' + } + } + } +} +``` +Result of `buildObject(obj)`: +```xml + + + +``` + + +Processing attribute, tag names and values +------------------------------------------ + +Since 0.4.1 you can optionally provide the parser with attribute name and tag name processors as well as element value processors (Since 0.4.14, you can also optionally provide the parser with attribute value processors): + +```javascript + +function nameToUpperCase(name){ + return name.toUpperCase(); +} + +//transform all attribute and tag names and values to uppercase +parseString(xml, { + tagNameProcessors: [nameToUpperCase], + attrNameProcessors: [nameToUpperCase], + valueProcessors: [nameToUpperCase], + attrValueProcessors: [nameToUpperCase]}, + function (err, result) { + // processed data +}); +``` + +The `tagNameProcessors` and `attrNameProcessors` options +accept an `Array` of functions with the following signature: + +```javascript +function (name){ + //do something with `name` + return name +} +``` + +The `attrValueProcessors` and `valueProcessors` options +accept an `Array` of functions with the following signature: + +```javascript +function (value, name) { + //`name` will be the node name or attribute name + //do something with `value`, (optionally) dependent on the node/attr name + return value +} +``` + +Some processors are provided out-of-the-box and can be found in `lib/processors.js`: + +- `normalize`: transforms the name to lowercase. +(Automatically used when `options.normalize` is set to `true`) + +- `firstCharLowerCase`: transforms the first character to lower case. +E.g. 'MyTagName' becomes 'myTagName' + +- `stripPrefix`: strips the xml namespace prefix. E.g `` will become 'Bar'. +(N.B.: the `xmlns` prefix is NOT stripped.) + +- `parseNumbers`: parses integer-like strings as integers and float-like strings as floats +E.g. "0" becomes 0 and "15.56" becomes 15.56 + +- `parseBooleans`: parses boolean-like strings to booleans +E.g. "true" becomes true and "False" becomes false + +Options +======= + +Apart from the default settings, there are a number of options that can be +specified for the parser. Options are specified by ``new Parser({optionName: +value})``. Possible options are: + + * `attrkey` (default: `$`): Prefix that is used to access the attributes. + Version 0.1 default was `@`. + * `charkey` (default: `_`): Prefix that is used to access the character + content. Version 0.1 default was `#`. + * `explicitCharkey` (default: `false`) Determines whether or not to use + a `charkey` prefix for elements with no attributes. + * `trim` (default: `false`): Trim the whitespace at the beginning and end of + text nodes. + * `normalizeTags` (default: `false`): Normalize all tag names to lowercase. + * `normalize` (default: `false`): Trim whitespaces inside text nodes. + * `explicitRoot` (default: `true`): Set this if you want to get the root + node in the resulting object. + * `emptyTag` (default: `''`): what will the value of empty nodes be. In case + you want to use an empty object as a default value, it is better to provide a factory + function `() => ({})` instead. Without this function a plain object would + become a shared reference across all occurrences with unwanted behavior. + * `explicitArray` (default: `true`): Always put child nodes in an array if + true; otherwise an array is created only if there is more than one. + * `ignoreAttrs` (default: `false`): Ignore all XML attributes and only create + text nodes. + * `mergeAttrs` (default: `false`): Merge attributes and child elements as + properties of the parent, instead of keying attributes off a child + attribute object. This option is ignored if `ignoreAttrs` is `true`. + * `validator` (default `null`): You can specify a callable that validates + the resulting structure somehow, however you want. See unit tests + for an example. + * `xmlns` (default `false`): Give each element a field usually called '$ns' + (the first character is the same as attrkey) that contains its local name + and namespace URI. + * `explicitChildren` (default `false`): Put child elements to separate + property. Doesn't work with `mergeAttrs = true`. If element has no children + then "children" won't be created. Added in 0.2.5. + * `childkey` (default `$$`): Prefix that is used to access child elements if + `explicitChildren` is set to `true`. Added in 0.2.5. + * `preserveChildrenOrder` (default `false`): Modifies the behavior of + `explicitChildren` so that the value of the "children" property becomes an + ordered array. When this is `true`, every node will also get a `#name` field + whose value will correspond to the XML nodeName, so that you may iterate + the "children" array and still be able to determine node names. The named + (and potentially unordered) properties are also retained in this + configuration at the same level as the ordered "children" array. Added in + 0.4.9. + * `charsAsChildren` (default `false`): Determines whether chars should be + considered children if `explicitChildren` is on. Added in 0.2.5. + * `includeWhiteChars` (default `false`): Determines whether whitespace-only + text nodes should be included. Added in 0.4.17. + * `async` (default `false`): Should the callbacks be async? This *might* be + an incompatible change if your code depends on sync execution of callbacks. + Future versions of `xml2js` might change this default, so the recommendation + is to not depend on sync execution anyway. Added in 0.2.6. + * `strict` (default `true`): Set sax-js to strict or non-strict parsing mode. + Defaults to `true` which is *highly* recommended, since parsing HTML which + is not well-formed XML might yield just about anything. Added in 0.2.7. + * `attrNameProcessors` (default: `null`): Allows the addition of attribute + name processing functions. Accepts an `Array` of functions with following + signature: + ```javascript + function (name){ + //do something with `name` + return name + } + ``` + Added in 0.4.14 + * `attrValueProcessors` (default: `null`): Allows the addition of attribute + value processing functions. Accepts an `Array` of functions with following + signature: + ```javascript + function (value, name){ + //do something with `name` + return name + } + ``` + Added in 0.4.1 + * `tagNameProcessors` (default: `null`): Allows the addition of tag name + processing functions. Accepts an `Array` of functions with following + signature: + ```javascript + function (name){ + //do something with `name` + return name + } + ``` + Added in 0.4.1 + * `valueProcessors` (default: `null`): Allows the addition of element value + processing functions. Accepts an `Array` of functions with following + signature: + ```javascript + function (value, name){ + //do something with `name` + return name + } + ``` + Added in 0.4.6 + +Options for the `Builder` class +------------------------------- +These options are specified by ``new Builder({optionName: value})``. +Possible options are: + + * `attrkey` (default: `$`): Prefix that is used to access the attributes. + Version 0.1 default was `@`. + * `charkey` (default: `_`): Prefix that is used to access the character + content. Version 0.1 default was `#`. + * `rootName` (default `root` or the root key name): root element name to be used in case + `explicitRoot` is `false` or to override the root element name. + * `renderOpts` (default `{ 'pretty': true, 'indent': ' ', 'newline': '\n' }`): + Rendering options for xmlbuilder-js. + * pretty: prettify generated XML + * indent: whitespace for indentation (only when pretty) + * newline: newline char (only when pretty) + * `xmldec` (default `{ 'version': '1.0', 'encoding': 'UTF-8', 'standalone': true }`: + XML declaration attributes. + * `xmldec.version` A version number string, e.g. 1.0 + * `xmldec.encoding` Encoding declaration, e.g. UTF-8 + * `xmldec.standalone` standalone document declaration: true or false + * `doctype` (default `null`): optional DTD. Eg. `{'ext': 'hello.dtd'}` + * `headless` (default: `false`): omit the XML header. Added in 0.4.3. + * `allowSurrogateChars` (default: `false`): allows using characters from the Unicode + surrogate blocks. + * `cdata` (default: `false`): wrap text nodes in `` instead of + escaping when necessary. Does not add `` if it is not required. + Added in 0.4.5. + +`renderOpts`, `xmldec`,`doctype` and `headless` pass through to +[xmlbuilder-js](https://github.com/oozcitak/xmlbuilder-js). + +Updating to new version +======================= + +Version 0.2 changed the default parsing settings, but version 0.1.14 introduced +the default settings for version 0.2, so these settings can be tried before the +migration. + +```javascript +var xml2js = require('xml2js'); +var parser = new xml2js.Parser(xml2js.defaults["0.2"]); +``` + +To get the 0.1 defaults in version 0.2 you can just use +`xml2js.defaults["0.1"]` in the same place. This provides you with enough time +to migrate to the saner way of parsing in `xml2js` 0.2. We try to make the +migration as simple and gentle as possible, but some breakage cannot be +avoided. + +So, what exactly did change and why? In 0.2 we changed some defaults to parse +the XML in a more universal and sane way. So we disabled `normalize` and `trim` +so `xml2js` does not cut out any text content. You can reenable this at will of +course. A more important change is that we return the root tag in the resulting +JavaScript structure via the `explicitRoot` setting, so you need to access the +first element. This is useful for anybody who wants to know what the root node +is and preserves more information. The last major change was to enable +`explicitArray`, so everytime it is possible that one might embed more than one +sub-tag into a tag, xml2js >= 0.2 returns an array even if the array just +includes one element. This is useful when dealing with APIs that return +variable amounts of subtags. + +Running tests, development +========================== + +[![Build Status](https://travis-ci.org/Leonidas-from-XIV/node-xml2js.svg?branch=master)](https://travis-ci.org/Leonidas-from-XIV/node-xml2js) +[![Coverage Status](https://coveralls.io/repos/Leonidas-from-XIV/node-xml2js/badge.svg?branch=)](https://coveralls.io/r/Leonidas-from-XIV/node-xml2js?branch=master) +[![Dependency Status](https://david-dm.org/Leonidas-from-XIV/node-xml2js.svg)](https://david-dm.org/Leonidas-from-XIV/node-xml2js) + +The development requirements are handled by npm, you just need to install them. +We also have a number of unit tests, they can be run using `npm test` directly +from the project root. This runs zap to discover all the tests and execute +them. + +If you like to contribute, keep in mind that `xml2js` is written in +CoffeeScript, so don't develop on the JavaScript files that are checked into +the repository for convenience reasons. Also, please write some unit test to +check your behaviour and if it is some user-facing thing, add some +documentation to this README, so people will know it exists. Thanks in advance! + +Getting support +=============== + +Please, if you have a problem with the library, first make sure you read this +README. If you read this far, thanks, you're good. Then, please make sure your +problem really is with `xml2js`. It is? Okay, then I'll look at it. Send me a +mail and we can talk. Please don't open issues, as I don't think that is the +proper forum for support problems. Some problems might as well really be bugs +in `xml2js`, if so I'll let you know to open an issue instead :) + +But if you know you really found a bug, feel free to open an issue instead. diff --git a/common/web/types/src/deps/xml2js/bom.js b/common/web/types/src/deps/xml2js/bom.js new file mode 100644 index 0000000000..0ad6e2a4a4 --- /dev/null +++ b/common/web/types/src/deps/xml2js/bom.js @@ -0,0 +1,8 @@ +"use strict"; +export function stripBOM(str) { + if (str[0] === '\uFEFF') { + return str.substring(1); + } else { + return str; + } +}; diff --git a/common/web/types/src/deps/xml2js/builder.js b/common/web/types/src/deps/xml2js/builder.js new file mode 100644 index 0000000000..5dfca61c9f --- /dev/null +++ b/common/web/types/src/deps/xml2js/builder.js @@ -0,0 +1,118 @@ +var escapeCDATA, requiresCDATA, wrapCDATA, + hasProp = {}.hasOwnProperty; + +import * as builder from 'xmlbuilder'; +import { defaults } from './defaults.js'; + +requiresCDATA = function(entry) { + return typeof entry === "string" && (entry.indexOf('&') >= 0 || entry.indexOf('>') >= 0 || entry.indexOf('<') >= 0); +}; + +wrapCDATA = function(entry) { + return ""; +}; + +escapeCDATA = function(entry) { + return entry.replace(']]>', ']]]]>'); +}; + +export class Builder { + constructor(opts) { + var key, ref, value; + this.options = {}; + ref = defaults["0.2"]; + for (key in ref) { + if (!hasProp.call(ref, key)) continue; + value = ref[key]; + this.options[key] = value; + } + for (key in opts) { + if (!hasProp.call(opts, key)) continue; + value = opts[key]; + this.options[key] = value; + } + } + + buildObject(rootObj) { + var attrkey, charkey, render, rootElement, rootName; + attrkey = this.options.attrkey; + charkey = this.options.charkey; + if ((Object.keys(rootObj).length === 1) && (this.options.rootName === defaults['0.2'].rootName)) { + rootName = Object.keys(rootObj)[0]; + rootObj = rootObj[rootName]; + } else { + rootName = this.options.rootName; + } + render = (function(_this) { + return function(element, obj) { + var attr, child, entry, index, key, value; + if (typeof obj !== 'object') { + if (_this.options.cdata && requiresCDATA(obj)) { + element.raw(wrapCDATA(obj)); + } else { + element.txt(obj); + } + } else if (Array.isArray(obj)) { + for (index in obj) { + if (!hasProp.call(obj, index)) continue; + child = obj[index]; + for (key in child) { + entry = child[key]; + element = render(element.ele(key), entry).up(); + } + } + } else { + for (key in obj) { + if (!hasProp.call(obj, key)) continue; + child = obj[key]; + if (key === attrkey) { + if (typeof child === "object") { + for (attr in child) { + value = child[attr]; + element = element.att(attr, value); + } + } + } else if (key === charkey) { + if (_this.options.cdata && requiresCDATA(child)) { + element = element.raw(wrapCDATA(child)); + } else { + element = element.txt(child); + } + } else if (Array.isArray(child)) { + for (index in child) { + if (!hasProp.call(child, index)) continue; + entry = child[index]; + if (typeof entry === 'string') { + if (_this.options.cdata && requiresCDATA(entry)) { + element = element.ele(key).raw(wrapCDATA(entry)).up(); + } else { + element = element.ele(key, entry).up(); + } + } else { + element = render(element.ele(key), entry).up(); + } + } + } else if (typeof child === "object") { + element = render(element.ele(key), child).up(); + } else { + if (typeof child === 'string' && _this.options.cdata && requiresCDATA(child)) { + element = element.ele(key).raw(wrapCDATA(child)).up(); + } else { + if (child == null) { + child = ''; + } + element = element.ele(key, child.toString()).up(); + } + } + } + } + return element; + }; + })(this); + rootElement = builder.create(rootName, this.options.xmldec, this.options.doctype, { + headless: this.options.headless, + allowSurrogateChars: this.options.allowSurrogateChars + }); + return render(rootElement, rootObj).end(this.options.renderOpts); + }; +} diff --git a/common/web/types/src/deps/xml2js/defaults.js b/common/web/types/src/deps/xml2js/defaults.js new file mode 100644 index 0000000000..d100928179 --- /dev/null +++ b/common/web/types/src/deps/xml2js/defaults.js @@ -0,0 +1,69 @@ +// Generated by CoffeeScript 1.12.7 +export const defaults = { + "0.1": { + explicitCharkey: false, + trim: true, + normalize: true, + normalizeTags: false, + attrkey: "@", + charkey: "#", + explicitArray: false, + ignoreAttrs: false, + mergeAttrs: false, + explicitRoot: false, + validator: null, + xmlns: false, + explicitChildren: false, + childkey: '@@', + charsAsChildren: false, + includeWhiteChars: false, + async: false, + strict: true, + attrNameProcessors: null, + attrValueProcessors: null, + tagNameProcessors: null, + valueProcessors: null, + emptyTag: '' + }, + "0.2": { + explicitCharkey: false, + trim: false, + normalize: false, + normalizeTags: false, + attrkey: "$", + charkey: "_", + explicitArray: true, + ignoreAttrs: false, + mergeAttrs: false, + explicitRoot: true, + validator: null, + xmlns: false, + explicitChildren: false, + preserveChildrenOrder: false, + childkey: '$$', + charsAsChildren: false, + includeWhiteChars: false, + async: false, + strict: true, + attrNameProcessors: null, + attrValueProcessors: null, + tagNameProcessors: null, + valueProcessors: null, + rootName: 'root', + xmldec: { + 'version': '1.0', + 'encoding': 'UTF-8', + 'standalone': true + }, + doctype: null, + renderOpts: { + 'pretty': true, + 'indent': ' ', + 'newline': '\n' + }, + headless: false, + chunkSize: 10000, + emptyTag: '', + cdata: false + } + }; diff --git a/common/web/types/src/deps/xml2js/parser.js b/common/web/types/src/deps/xml2js/parser.js new file mode 100644 index 0000000000..aa0101ea7d --- /dev/null +++ b/common/web/types/src/deps/xml2js/parser.js @@ -0,0 +1,377 @@ + var isEmpty, processItem, + bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + +import sax from 'sax'; +import { EventEmitter } from 'events'; +import * as bom from './bom.js'; +import * as processors from './processors.js'; +import { setImmediate } from 'timers'; +import { defaults } from './defaults.js'; + + isEmpty = function(thing) { + return typeof thing === "object" && (thing != null) && Object.keys(thing).length === 0; + }; + + processItem = function(processors, item, key) { + var i, len, process; + for (i = 0, len = processors.length; i < len; i++) { + process = processors[i]; + item = process(item, key); + } + return item; + }; + +/** @type Class */ +export class Parser extends EventEmitter { +// export const Parser = (function(superClass) { + // extend(Parser, superClass); + + constructor(opts) { + super(); + this.parseStringPromise = bind(this.parseStringPromise, this); + this.parseString = bind(this.parseString, this); + this.reset = bind(this.reset, this); + this.assignOrPush = bind(this.assignOrPush, this); + this.processAsync = bind(this.processAsync, this); + var key, ref, value; + if (!(this instanceof Parser)) { + return new Parser(opts); + } + this.options = {}; + ref = defaults["0.2"]; + for (key in ref) { + if (!hasProp.call(ref, key)) continue; + value = ref[key]; + this.options[key] = value; + } + for (key in opts) { + if (!hasProp.call(opts, key)) continue; + value = opts[key]; + this.options[key] = value; + } + if (this.options.xmlns) { + this.options.xmlnskey = this.options.attrkey + "ns"; + } + if (this.options.normalizeTags) { + if (!this.options.tagNameProcessors) { + this.options.tagNameProcessors = []; + } + this.options.tagNameProcessors.unshift(processors.normalize); + } + this.reset(); + } + + processAsync() { + var chunk, err; + try { + if (this.remaining.length <= this.options.chunkSize) { + chunk = this.remaining; + this.remaining = ''; + this.saxParser = this.saxParser.write(chunk); + return this.saxParser.close(); + } else { + chunk = this.remaining.substr(0, this.options.chunkSize); + this.remaining = this.remaining.substr(this.options.chunkSize, this.remaining.length); + this.saxParser = this.saxParser.write(chunk); + return setImmediate(this.processAsync); + } + } catch (error1) { + err = error1; + if (!this.saxParser.errThrown) { + this.saxParser.errThrown = true; + return this.emit(err); + } + } + }; + + assignOrPush(obj, key, newValue) { + if (!(key in obj)) { + if (!this.options.explicitArray) { + return obj[key] = newValue; + } else { + return obj[key] = [newValue]; + } + } else { + if (!(obj[key] instanceof Array)) { + obj[key] = [obj[key]]; + } + return obj[key].push(newValue); + } + }; + + reset() { + var attrkey, charkey, ontext, stack; + this.removeAllListeners(); + this.saxParser = sax.parser(this.options.strict, { + trim: false, + normalize: false, + xmlns: this.options.xmlns + }); + this.saxParser.errThrown = false; + this.saxParser.onerror = (function(_this) { + return function(error) { + _this.saxParser.resume(); + if (!_this.saxParser.errThrown) { + _this.saxParser.errThrown = true; + return _this.emit("error", error); + } + }; + })(this); + this.saxParser.onend = (function(_this) { + return function() { + if (!_this.saxParser.ended) { + _this.saxParser.ended = true; + return _this.emit("end", _this.resultObject); + } + }; + })(this); + this.saxParser.ended = false; + this.EXPLICIT_CHARKEY = this.options.explicitCharkey; + this.resultObject = null; + stack = []; + attrkey = this.options.attrkey; + charkey = this.options.charkey; + this.saxParser.onopentag = (function(_this) { + return function(node) { + var key, newValue, obj, processedKey, ref; + obj = Object.create(null); + obj[charkey] = ""; + if (!_this.options.ignoreAttrs) { + ref = node.attributes; + for (key in ref) { + if (!hasProp.call(ref, key)) continue; + if (!(attrkey in obj) && !_this.options.mergeAttrs) { + obj[attrkey] = Object.create(null); + } + newValue = _this.options.attrValueProcessors ? processItem(_this.options.attrValueProcessors, node.attributes[key], key) : node.attributes[key]; + processedKey = _this.options.attrNameProcessors ? processItem(_this.options.attrNameProcessors, key) : key; + if (_this.options.mergeAttrs) { + _this.assignOrPush(obj, processedKey, newValue); + } else { + obj[attrkey][processedKey] = newValue; + } + } + } + obj["#name"] = _this.options.tagNameProcessors ? processItem(_this.options.tagNameProcessors, node.name) : node.name; + if (_this.options.xmlns) { + obj[_this.options.xmlnskey] = { + uri: node.uri, + local: node.local + }; + } + return stack.push(obj); + }; + })(this); + this.saxParser.onclosetag = (function(_this) { + return function() { + var cdata, emptyStr, key, node, nodeName, obj, objClone, old, s, xpath; + obj = stack.pop(); + nodeName = obj["#name"]; + if (!_this.options.explicitChildren || !_this.options.preserveChildrenOrder) { + delete obj["#name"]; + } + if (obj.cdata === true) { + cdata = obj.cdata; + delete obj.cdata; + } + s = stack[stack.length - 1]; + if (obj[charkey].match(/^\s*$/) && !cdata && !_this.options.includeWhiteChars) { + emptyStr = obj[charkey]; + delete obj[charkey]; + } else { + if (_this.options.trim) { + obj[charkey] = obj[charkey].trim(); + } + if (_this.options.normalize) { + obj[charkey] = obj[charkey].replace(/\s{2,}/g, " ").trim(); + } + obj[charkey] = _this.options.valueProcessors ? processItem(_this.options.valueProcessors, obj[charkey], nodeName) : obj[charkey]; + if (Object.keys(obj).length === 1 && charkey in obj && !_this.EXPLICIT_CHARKEY) { + obj = obj[charkey]; + } + } + if (isEmpty(obj)) { + if (typeof _this.options.emptyTag === 'function') { + obj = _this.options.emptyTag(); + } else { + obj = _this.options.emptyTag !== '' ? _this.options.emptyTag : emptyStr; + } + } + if (_this.options.validator != null) { + xpath = "/" + ((function() { + var i, len, results; + results = []; + for (i = 0, len = stack.length; i < len; i++) { + node = stack[i]; + results.push(node["#name"]); + } + return results; + })()).concat(nodeName).join("/"); + (function() { + var err; + try { + return obj = _this.options.validator(xpath, s && s[nodeName], obj); + } catch (error1) { + err = error1; + return _this.emit("error", err); + } + })(); + } + if (_this.options.explicitChildren && !_this.options.mergeAttrs && typeof obj === 'object') { + if (!_this.options.preserveChildrenOrder) { + node = Object.create(null); + if (_this.options.attrkey in obj) { + node[_this.options.attrkey] = obj[_this.options.attrkey]; + delete obj[_this.options.attrkey]; + } + if (!_this.options.charsAsChildren && _this.options.charkey in obj) { + node[_this.options.charkey] = obj[_this.options.charkey]; + delete obj[_this.options.charkey]; + } + if (Object.getOwnPropertyNames(obj).length > 0) { + node[_this.options.childkey] = obj; + } + obj = node; + } else if (s) { + s[_this.options.childkey] = s[_this.options.childkey] || []; + objClone = Object.create(null); + for (key in obj) { + if (!hasProp.call(obj, key)) continue; + objClone[key] = obj[key]; + } + s[_this.options.childkey].push(objClone); + delete obj["#name"]; + if (Object.keys(obj).length === 1 && charkey in obj && !_this.EXPLICIT_CHARKEY) { + obj = obj[charkey]; + } + } + } + if (stack.length > 0) { + return _this.assignOrPush(s, nodeName, obj); + } else { + if (_this.options.explicitRoot) { + old = obj; + obj = Object.create(null); + obj[nodeName] = old; + } + _this.resultObject = obj; + _this.saxParser.ended = true; + return _this.emit("end", _this.resultObject); + } + }; + })(this); + ontext = (function(_this) { + return function(text) { + var charChild, s; + s = stack[stack.length - 1]; + if (s) { + s[charkey] += text; + if (_this.options.explicitChildren && _this.options.preserveChildrenOrder && _this.options.charsAsChildren && (_this.options.includeWhiteChars || text.replace(/\\n/g, '').trim() !== '')) { + s[_this.options.childkey] = s[_this.options.childkey] || []; + charChild = { + '#name': '__text__' + }; + charChild[charkey] = text; + if (_this.options.normalize) { + charChild[charkey] = charChild[charkey].replace(/\s{2,}/g, " ").trim(); + } + s[_this.options.childkey].push(charChild); + } + return s; + } + }; + })(this); + this.saxParser.ontext = ontext; + return this.saxParser.oncdata = (function(_this) { + return function(text) { + var s; + s = ontext(text); + if (s) { + return s.cdata = true; + } + }; + })(this); + }; + + parseString(str, cb) { + var err; + if ((cb != null) && typeof cb === "function") { + this.on("end", function(result) { + this.reset(); + return cb(null, result); + }); + this.on("error", function(err) { + this.reset(); + return cb(err); + }); + } + try { + str = str.toString(); + if (str.trim() === '') { + this.emit("end", null); + return true; + } + str = bom.stripBOM(str); + if (this.options.async) { + this.remaining = str; + setImmediate(this.processAsync); + return this.saxParser; + } + return this.saxParser.write(str).close(); + } catch (error1) { + err = error1; + if (!(this.saxParser.errThrown || this.saxParser.ended)) { + this.emit('error', err); + return this.saxParser.errThrown = true; + } else if (this.saxParser.ended) { + throw err; + } + } + }; + + parseStringPromise(str) { + return new Promise((function(_this) { + return function(resolve, reject) { + return _this.parseString(str, function(err, value) { + if (err) { + return reject(err); + } else { + return resolve(value); + } + }); + }; + })(this)); + }; + + } + + export const parseString = function(str, a, b) { + var cb, options, parser; + if (b != null) { + if (typeof b === 'function') { + cb = b; + } + if (typeof a === 'object') { + options = a; + } + } else { + if (typeof a === 'function') { + cb = a; + } + options = {}; + } + parser = new Parser(options); + return parser.parseString(str, cb); + }; + + export const parseStringPromise = function(str, a) { + var options, parser; + if (typeof a === 'object') { + options = a; + } + parser = new Parser(options); + return parser.parseStringPromise(str); + }; + diff --git a/common/web/types/src/deps/xml2js/processors.js b/common/web/types/src/deps/xml2js/processors.js new file mode 100644 index 0000000000..2a6849088c --- /dev/null +++ b/common/web/types/src/deps/xml2js/processors.js @@ -0,0 +1,31 @@ + "use strict"; + var prefixMatch; + + prefixMatch = new RegExp(/(?!xmlns)^.*:/); + + export const normalize = function(str) { + return str.toLowerCase(); + }; + + export const firstCharLowerCase = function(str) { + return str.charAt(0).toLowerCase() + str.slice(1); + }; + + export const stripPrefix = function(str) { + return str.replace(prefixMatch, ''); + }; + + export const parseNumbers = function(str) { + if (!isNaN(str)) { + str = str % 1 === 0 ? parseInt(str, 10) : parseFloat(str); + } + return str; + }; + + export const parseBooleans = function(str) { + if (/^(?:true|false)$/i.test(str)) { + str = str.toLowerCase() === 'true'; + } + return str; + }; + diff --git a/common/web/types/src/deps/xml2js/xml2js.js b/common/web/types/src/deps/xml2js/xml2js.js new file mode 100644 index 0000000000..8ecbe3bcf2 --- /dev/null +++ b/common/web/types/src/deps/xml2js/xml2js.js @@ -0,0 +1,27 @@ +var + extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, + hasProp = {}.hasOwnProperty; + + // import { defaults } from './defaults.js'; + import * as builder from './builder.js'; + import * as parser from './parser.js'; + import * as processors from './processors.js'; + + // export const defaults = defaults.defaults; + // export const processors = processors; + + /** @type Class */ + export class ValidationError extends Error { + constructor(message) { + super(message); + this.message = message; + } + }; + + export const Parser = parser.Parser; + + export const Builder = builder.Builder; + + export const parseString = parser.parseString; + + export const parseStringPromise = parser.parseStringPromise; diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts index d005e0a0ab..eb2a168a48 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -1,4 +1,4 @@ -import * as xml2js from 'xml2js'; +import * as xml2js from '../deps/xml2js/xml2js.js'; import { KPJFile, KPJFileProject } from './kpj-file.js'; import { boxXmlArray } from '../util/util.js'; import { KeymanDeveloperProject, KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from './keyman-developer-project.js'; diff --git a/common/web/types/src/kvk/kvks-file-reader.ts b/common/web/types/src/kvk/kvks-file-reader.ts index 532818946d..ff3a094e6f 100644 --- a/common/web/types/src/kvk/kvks-file-reader.ts +++ b/common/web/types/src/kvk/kvks-file-reader.ts @@ -1,4 +1,4 @@ -import * as xml2js from 'xml2js'; +import * as xml2js from '../deps/xml2js/xml2js.js'; import KVKSourceFile from './kvks-file.js'; import { boxXmlArray } from '../util/util.js'; import { DEFAULT_KVK_FONT, VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKey, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js'; diff --git a/common/web/types/src/kvk/kvks-file-writer.ts b/common/web/types/src/kvk/kvks-file-writer.ts index 3ca94aa258..19327e779d 100644 --- a/common/web/types/src/kvk/kvks-file-writer.ts +++ b/common/web/types/src/kvk/kvks-file-writer.ts @@ -1,4 +1,4 @@ -import * as xml2js from 'xml2js'; +import * as xml2js from '../deps/xml2js/xml2js.js'; import KVKSourceFile, { KVKSEncoding, KVKSFlags, KVKSKey, KVKSLayer } from './kvks-file.js'; import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js'; import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js'; diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 4f5c30bb1a..9055e8ed1d 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -1,4 +1,4 @@ -import * as xml2js from 'xml2js'; +import * as xml2js from '../deps/xml2js/xml2js.js'; import { LDMLKeyboardXMLSourceFile, LKImport, ImportStatus } from './ldml-keyboard-xml.js'; import { boxXmlArray } from '../util/util.js'; import { CompilerCallbacks } from '../util/compiler-interfaces.js'; diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 5c79ed6761..3d18812876 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -55,4 +55,6 @@ export * as util from './util/util.js'; export * as KeymanFileTypes from './util/file-types.js'; export * as Schemas from './schemas.js'; -export * as SchemaValidators from './schema-validators.js'; \ No newline at end of file +export * as SchemaValidators from './schema-validators.js'; + +export * as xml2js from './deps/xml2js/xml2js.js'; \ No newline at end of file diff --git a/common/web/types/tsconfig.json b/common/web/types/tsconfig.json index cd779632a0..0473ae92ee 100644 --- a/common/web/types/tsconfig.json +++ b/common/web/types/tsconfig.json @@ -9,6 +9,7 @@ "preserveConstEnums": true, }, "include": [ + "src/deps/xml2js/*.js", "src/**/*.ts", "src/schemas/*.mjs", // Import the validators ], diff --git a/common/web/utils/package.json b/common/web/utils/package.json index 1df79b87ef..b99c7577fe 100644 --- a/common/web/utils/package.json +++ b/common/web/utils/package.json @@ -29,7 +29,7 @@ "c8": "^7.12.0", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "type": "module", "paths": { diff --git a/core/meson.build b/core/meson.build index 0049c95741..4a9ea257ea 100644 --- a/core/meson.build +++ b/core/meson.build @@ -13,7 +13,7 @@ project('keyman_core', 'cpp', 'c', 'b_vscrt=static_from_buildtype', 'warning_level=2', 'debug=true'], - meson_version: '>=0.57.0') + meson_version: '>=1.0') # Import our standard compiler defines; this is copied from # /resources/build/standard.meson.build by build.sh, because diff --git a/core/src/util_normalize.cpp b/core/src/util_normalize.cpp index 947dbe0f85..bcdfc1bb3f 100644 --- a/core/src/util_normalize.cpp +++ b/core/src/util_normalize.cpp @@ -228,8 +228,10 @@ bool has_nfd_boundary_before(km_core_usv cp) { } /** - * Helper to convert icu::UnicodeString to a UTF-32 km_core_usv buffer, - * nul-terminated + * Helper to convert std::u32string to a UTF-32 km_core_usv buffer, + * nul-terminated. + * Parallel to unicode_string_to_usv() + * @returns new buffer, caller owns storage */ km_core_usv *string_to_usv(const std::u32string& src) { return km::core::kmx::u32dup(src.c_str()); diff --git a/core/src/util_normalize_table_generator.cpp b/core/src/util_normalize_table_generator.cpp index b5895a588b..994f16d8b3 100644 --- a/core/src/util_normalize_table_generator.cpp +++ b/core/src/util_normalize_table_generator.cpp @@ -55,7 +55,7 @@ write_nfd_table() { // collect the raw list of chars that do NOT have a boundary before them. std::vector noBoundary; - for (km_core_usv ch = 0; ch < 0x10FFFF; ch++) { + for (km_core_usv ch = 0; ch < km::core::kmx::Uni_MAX_CODEPOINT; ch++) { bool bb = nfd->hasBoundaryBefore(ch); assert(!(ch == 0 && !bb)); // assert that we can use U+0000 as a terminator if (bb) continue; //only emit nonboundary diff --git a/core/tests/meson.build b/core/tests/meson.build index e2e2997582..84c17d4220 100644 --- a/core/tests/meson.build +++ b/core/tests/meson.build @@ -21,7 +21,7 @@ if get_option('keyman_core_tests') if get_option('default_library') != 'static' ctypes_void_p_size = ['-c', 'import ctypes; print(ctypes.sizeof(ctypes.c_void_p))'] - r = run_command(python, ctypes_void_p_size) + r = run_command(python, ctypes_void_p_size, check: true) python_ctypes_compatible = r.stdout().to_int() == cpp_compiler.sizeof('void *') if not python_ctypes_compatible message('Python ctypes is incompatible with built shared object. Disabling some tests.') diff --git a/core/tests/unit/kmx/cat.bat b/core/tests/unit/kmx/cat.bat new file mode 100644 index 0000000000..a5568e3919 --- /dev/null +++ b/core/tests/unit/kmx/cat.bat @@ -0,0 +1,4 @@ +@echo off +set infile=%1 +set infileb=%infile:/=\% +type %infileb% diff --git a/core/tests/unit/kmx/fixtures/binary/meson.build b/core/tests/unit/kmx/fixtures/binary/meson.build index 46b0e78d31..cf96bc75ce 100644 --- a/core/tests/unit/kmx/fixtures/binary/meson.build +++ b/core/tests/unit/kmx/fixtures/binary/meson.build @@ -17,9 +17,9 @@ binary_tests = [ foreach kbd : binary_tests configure_file( - command: copy_cmd + ['@INPUT@', '@OUTPUT@'], input: kbd + '.kmn', - output: kbd + '.kmn' + output: kbd + '.kmn', + copy: true ) configure_file( diff --git a/core/tests/unit/kmx/meson.build b/core/tests/unit/kmx/meson.build index d244e162a5..548ab89522 100644 --- a/core/tests/unit/kmx/meson.build +++ b/core/tests/unit/kmx/meson.build @@ -89,10 +89,8 @@ kmc_root = meson.current_source_dir() / '../../../../developer/src/kmc/build/src kmc_cmd = [node, '--enable-source-maps', kmc_root] if build_machine.system() == 'windows' - copy_cmd = [find_program('cmd.exe', required: true), '/c', 'copy'] - cat_cmd = [find_program('cmd.exe', required: true), '/c', 'type'] + cat_cmd = [find_program(meson.current_source_dir() / 'cat.bat', required: true)] else - copy_cmd = [find_program('cp', required: true)] cat_cmd = [find_program('cat', required: true)] endif @@ -127,7 +125,7 @@ foreach kbd : tests kbd_src_path = common_test_keyboards_baseline / kbd + '.kmn' content = run_command( - cat_cmd, files(kbd_src_path), + cat_cmd, files(kbd_src_path), check: true, ).stdout().strip() cfg = configuration_data() diff --git a/core/tests/unit/ldml/invalid-keyboards/meson.build b/core/tests/unit/ldml/invalid-keyboards/meson.build index da8fb85ad0..e85646b68e 100644 --- a/core/tests/unit/ldml/invalid-keyboards/meson.build +++ b/core/tests/unit/ldml/invalid-keyboards/meson.build @@ -8,19 +8,13 @@ invalid_tests = [ 'ik_000_null_invalid' ] -if build_machine.system() == 'windows' - copy_cmd = [find_program('cmd.exe', required: true), '/c', 'copy'] -else - copy_cmd = [find_program('cp', required: true)] -endif - # Build all keyboards in output folder foreach kbd : invalid_tests configure_file( - command: copy_cmd + ['@INPUT@', '@OUTPUT@'], input: kbd + '.xml', - output: kbd + '.xml' + output: kbd + '.xml', + copy: true ) configure_file( diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build index 25d5d12ac1..15f458c587 100644 --- a/core/tests/unit/ldml/keyboards/meson.build +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -56,8 +56,8 @@ tests += tests_from_cldr # Setup kmc -kmc_root = join_paths(meson.source_root(),'..','developer','src','kmc') -ldml_root = join_paths(meson.source_root(),'..','resources','standards-data','ldml-keyboards','45') +kmc_root = meson.global_source_root() / '../developer/src/kmc' +ldml_root = meson.global_source_root() / '../resources/standards-data/ldml-keyboards/45' ldml_data = join_paths(ldml_root, '3.0') ldml_testdata = join_paths(ldml_root, 'test') kmc_cmd = [node, '--enable-source-maps', kmc_root] diff --git a/core/tests/unit/ldml/meson.build b/core/tests/unit/ldml/meson.build index 42f2d7778e..81f63033fe 100644 --- a/core/tests/unit/ldml/meson.build +++ b/core/tests/unit/ldml/meson.build @@ -22,12 +22,6 @@ invalid_tests = [] # Setup copying of source files, used in child subdir calls -if build_machine.system() == 'windows' - copy_cmd = [find_program('cmd.exe', required: true), '/c', 'copy'] -else - copy_cmd = [find_program('cp', required: true)] -endif - if node.found() # Note: if node is not available, we cannot build the keyboards; build.sh # emits a warning that the ldml keyboard tests will be skipped diff --git a/core/tests/unit/meson.build b/core/tests/unit/meson.build index e3d9f76607..ee141cc12d 100644 --- a/core/tests/unit/meson.build +++ b/core/tests/unit/meson.build @@ -2,10 +2,10 @@ node = find_program('node', required: true) common_test_files = [ meson.current_source_dir() / 'emscripten_filesystem.cpp', - meson.source_root() / '../common/include/test_color.cpp' + meson.global_source_root() / '../common/include/test_color.cpp' ] -hextobin_root = join_paths(meson.source_root(),'..','common','tools','hextobin','build','hextobin.js') +hextobin_root = meson.global_source_root() / '../common/tools/hextobin/build/hextobin.js' hextobin_cmd = [node, hextobin_root] subdir('json') diff --git a/developer/src/common/delphi/packages/Keyman.System.PackageInfoRefreshKeyboards.pas b/developer/src/common/delphi/packages/Keyman.System.PackageInfoRefreshKeyboards.pas index 01e504d7a4..23b7cb4664 100644 --- a/developer/src/common/delphi/packages/Keyman.System.PackageInfoRefreshKeyboards.pas +++ b/developer/src/common/delphi/packages/Keyman.System.PackageInfoRefreshKeyboards.pas @@ -227,7 +227,15 @@ begin // Look for Keyboard_ with TStringStream.Create('', TEncoding.UTF8) do try - LoadFromFile(f.FileName); + try + LoadFromFile(f.FileName); + except + on E:EEncodingError do + begin + // If the file cannot be loaded as UTF-8, we'll ignore it; #11687 + Exit(False); + end; + end; Result := TRegEx.IsMatch(DataString, '\bKeyboard_'+id+'\b', []); finally Free; diff --git a/developer/src/common/web/test-helpers/package.json b/developer/src/common/web/test-helpers/package.json index 40b9943314..5aa6ec301f 100644 --- a/developer/src/common/web/test-helpers/package.json +++ b/developer/src/common/web/test-helpers/package.json @@ -10,8 +10,7 @@ ], "devDependencies": { "@types/node": "^20.4.1", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "scripts": { "build": "tsc -b" diff --git a/developer/src/common/web/utils/index.ts b/developer/src/common/web/utils/index.ts index 8465175063..7a792af2a1 100644 --- a/developer/src/common/web/utils/index.ts +++ b/developer/src/common/web/utils/index.ts @@ -2,4 +2,5 @@ export { validateMITLicense } from './src/validate-mit-license.js'; export { KeymanSentry, SentryNodeOptions } from './src/KeymanSentry.js'; export { getOption, loadOptions, clearOptions } from './src/options.js'; export { escapeMarkdownChar } from './src/markdown.js'; -export { KeymanUrls } from './src/keyman-urls.js'; \ No newline at end of file +export { KeymanUrls } from './src/keyman-urls.js'; +export { isValidEmail } from './src/is-valid-email.js'; \ No newline at end of file diff --git a/developer/src/common/web/utils/package.json b/developer/src/common/web/utils/package.json index 62119daea8..b93cca8efa 100644 --- a/developer/src/common/web/utils/package.json +++ b/developer/src/common/web/utils/package.json @@ -13,8 +13,7 @@ }, "devDependencies": { "@types/node": "^20.4.1", - "ts-node": "^9.1.1", - "typescript": "^4.9.5", + "typescript": "^5.4.5", "c8": "^7.12.0", "mocha": "^8.4.0" }, diff --git a/developer/src/common/web/utils/src/is-valid-email.ts b/developer/src/common/web/utils/src/is-valid-email.ts new file mode 100644 index 0000000000..364010498a --- /dev/null +++ b/developer/src/common/web/utils/src/is-valid-email.ts @@ -0,0 +1,18 @@ +/* + * Keyman is copyright (C) SIL International. MIT License. + * + * Verify email address format, following WHATWG guidelines + */ + +// There is no "good" definition of a valid email address. Email addresses are +// horrific. They can contain comments, whitespace, and all manner of ugly +// things. Because we use AJV to verify JSON files, we use their specification +// on what is a valid email address. Some useful references: +// * https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address +// * http://stackoverflow.com/questions/201323/using-a-regular-expression-to-validate-an-email-address#answer-8829363 +// * https://github.com/ajv-validator/ajv-formats/blob/4ca86d21bd07571a30178cbb3714133db6eada9a/src/formats.ts#L122 +// * https://github.com/ajv-validator/ajv-formats/blob/4ca86d21bd07571a30178cbb3714133db6eada9a/src/formats.ts#L65 + +export function isValidEmail(email: string) { + return /^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?)*$/i.test(email); +} \ No newline at end of file diff --git a/developer/src/common/web/utils/test/test-is-valid-email.ts b/developer/src/common/web/utils/test/test-is-valid-email.ts new file mode 100644 index 0000000000..af2aff17a7 --- /dev/null +++ b/developer/src/common/web/utils/test/test-is-valid-email.ts @@ -0,0 +1,35 @@ +import { assert } from 'chai'; +import 'mocha'; +import { isValidEmail } from '../src/is-valid-email.js'; + +describe('test-is-valid-email', function () { + it('should accept a valid email address', function() { + [ + 'email@example.com', + 'email.example-true@example.com', + 'email@example-example.com', + 'email...@example.com', + 'email_example1@1example.com', + 'email+example@mail.example.com', + 'Email@Example.Com' + ].forEach(email => assert.isTrue(isValidEmail(email), `expected '${email}' to be valid`)); + + // This is accepted, but it's really a bit wonky. But that's an upstream + // issue with overly lax regex and not something we'll attempt to fix: + // + // assert.isTrue(isValidEmail('.@example.com')); + }); + + it('should reject invalid email addresses', function() { + [ + 'email@example.com, email2@example.com', + ' email@example.com', + 'email@example_domain.com', + 'email', + 'email@.', + 'email@example..com', + 'email@', + '@example', + ].forEach(email => assert.isFalse(isValidEmail(email), `expected '${email}' to be invalid`)); + }); +}); diff --git a/developer/src/kmc-analyze/package.json b/developer/src/kmc-analyze/package.json index f851aaa987..8577ed25b3 100644 --- a/developer/src/kmc-analyze/package.json +++ b/developer/src/kmc-analyze/package.json @@ -34,8 +34,7 @@ "c8": "^7.12.0", "chalk": "^2.4.2", "mocha": "^8.4.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "repository": { "type": "git", diff --git a/developer/src/kmc-keyboard-info/package.json b/developer/src/kmc-keyboard-info/package.json index f02e3f6db0..4d4a421025 100644 --- a/developer/src/kmc-keyboard-info/package.json +++ b/developer/src/kmc-keyboard-info/package.json @@ -33,8 +33,7 @@ "c8": "^7.12.0", "chalk": "^2.4.2", "mocha": "^8.4.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "mocha": { "spec": "build/test/**/test-*.js", diff --git a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts index 5337ba2fd4..45c217f3c5 100644 --- a/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts +++ b/developer/src/kmc-keyboard-info/src/keyboard-info-compiler.ts @@ -8,7 +8,7 @@ import { KeyboardInfoFile, KeyboardInfoFileIncludes, KeyboardInfoFileLanguageFon import { KeymanFileTypes, CompilerCallbacks, KmpJsonFile, KmxFileReader, KMX, KeymanTargets, KeymanCompiler, CompilerOptions, KeymanCompilerResult, KeymanCompilerArtifacts, KeymanCompilerArtifact } from "@keymanapp/common-types"; import { KeyboardInfoCompilerMessages } from "./keyboard-info-compiler-messages.js"; import langtags from "./imports/langtags.js"; -import { KeymanUrls, validateMITLicense } from "@keymanapp/developer-utils"; +import { KeymanUrls, isValidEmail, validateMITLicense } from "@keymanapp/developer-utils"; import { KmpCompiler } from "@keymanapp/kmc-package"; import { SchemaValidators } from "@keymanapp/common-types"; @@ -238,6 +238,11 @@ export class KeyboardInfoCompiler implements KeymanCompiler { return null; } + if(!isValidEmail(match[2])) { + this.callbacks.reportMessage(KeyboardInfoCompilerMessages.Error_InvalidAuthorEmail({email:author.url})); + return null; + } + keyboard_info.authorEmail = match[2]; } } diff --git a/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/LICENSE.md b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/LICENSE.md new file mode 100644 index 0000000000..0a8c905431 --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/LICENSE.md @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2022 SIL International + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/.gitattributes b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/.gitattributes new file mode 100644 index 0000000000..7e3549570d --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/.gitattributes @@ -0,0 +1 @@ +khmer_angkor.js -text \ No newline at end of file diff --git a/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.js b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.js new file mode 100644 index 0000000000..f95f54848c --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.js @@ -0,0 +1,5462 @@ +if(typeof keyman === 'undefined') { + console.log('Keyboard requires KeymanWeb 10.0 or later'); + if(typeof tavultesoft !== 'undefined') tavultesoft.keymanweb.util.alert("This keyboard requires KeymanWeb 10.0 or later"); +} else { +KeymanWeb.KR(new Keyboard_khmer_angkor()); +} +function Keyboard_khmer_angkor() +{ + var modCodes = keyman.osk.modifierCodes; + var keyCodes = keyman.osk.keyCodes; + + this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9; + this.KI="Keyboard_khmer_angkor"; + this.KN="Khmer Angkor"; + this.KMINVER="10.0"; + this.KV={F:' 1em "Khmer Busra Kbd"',K102:0}; + this.KV.KLS={ + "rightalt": ["‍","‌","@","","$","€","៙","៚","*","{","}","≈","","","","","ៜ","","ឯ","ឫ","ឨ","[","]","ឦ","ឱ","ឰ","ឩ","ឳ","\\","","","","+","-","×","÷",":","‘","’","ឝ","៘","៖","ៈ","","","","","","","<",">","#","&","ឞ",";","",",",".","/","","","","",""," "], + "rightalt-shift": ["","៱","៲","៳","៴","៵","៶","៷","៸","៹","៰","","","","","","᧠","᧡","᧢","᧣","᧤","᧥","᧦","᧧","᧨","᧩","᧪","᧫","","","","","᧬","᧭","᧮","᧯","᧰","᧱","᧲","᧳","᧴","᧵","᧶","","","","","","","᧷","᧸","᧹","᧺","᧻","᧼","᧽","᧾","᧿","","","","","","",""], + "default": ["«","១","២","៣","៤","៥","៦","៧","៨","៩","០","ឥ","ឲ","","","","ឆ","","","រ","ត","យ","","","","ផ","","ឪ","ឮ","","","","","ស","ដ","ថ","ង","ហ","","ក","ល","","","","","","","","","ឋ","ខ","ច","វ","ប","ន","ម","","។","","","","","","","​"], + "shift": ["»","!","ៗ","\"","៛","%","","","","(",")","","=","","","","ឈ","","","ឬ","ទ","","","","","ភ","","ឧ","ឭ","","","","","","ឌ","ធ","អ","ះ","ញ","គ","ឡ","","","","","","","","","ឍ","ឃ","ជ","","ព","ណ","","","៕","?","","","","","",""] + }; + this.KV.BK=(function(x){ + var + empty=Array.apply(null, Array(65)).map(String.prototype.valueOf,""), + result=[], v, i, + modifiers=['default','shift','ctrl','shift-ctrl','alt','shift-alt','ctrl-alt','shift-ctrl-alt']; + for(i=modifiers.length-1;i>=0;i--) { + v = x[modifiers[i]]; + if(v || result.length > 0) { + result=(v ? v : empty).slice().concat(result); + } + } + return result; + })(this.KV.KLS); + this.KDU=0; + this.KH=''; + this.KM=0; + this.KBVER="1.3"; + this.KMBM=modCodes.RALT | modCodes.SHIFT /* 0x0018 */; + this.KVKD="T_17D2_1780 T_17D2_1781 T_17D2_1782 T_17D2_1783 T_17D2_1784 T_17D2_1785 T_17D2_1786 T_17D2_1787 T_17D2_1788 T_17D2_1789 T_17D2_178A T_17D2_178B T_17D2_178C T_17D2_178D T_17D2_178E T_17D2_178F T_17D2_1790 T_17D2_1791 T_17D2_1792 T_17D2_1793 T_17D2_1794 T_17D2_1795 T_17D2_1796 T_17D2_1797 T_17D2_1798 T_17D2_1799 T_17D2_179A T_17D2_179B T_17D2_179C T_17D2_179D T_17D2_179E T_17D2_179F T_17D2_17A0 T_17D2_17A1 T_17D2_17A2 U_0030 U_0031 U_0032 U_0033 U_0034 U_0035 U_0036 U_0037 U_0038 U_0039"; + this.KVKL={ + "tablet": { + "displayUnderlying": false, + "layer": [ + { + "id": "default", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_1", + "text": "១" + }, + { + "id": "K_2", + "text": "២" + }, + { + "id": "K_3", + "text": "៣" + }, + { + "id": "K_4", + "text": "៤" + }, + { + "id": "K_5", + "text": "៥" + }, + { + "id": "K_6", + "text": "៦" + }, + { + "id": "K_7", + "text": "៧" + }, + { + "id": "K_8", + "text": "៨" + }, + { + "id": "K_9", + "text": "៩" + }, + { + "id": "K_0", + "text": "០" + }, + { + "id": "K_HYPHEN", + "text": "ឥ" + }, + { + "id": "K_EQUAL", + "text": "ឲ" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "រ" + }, + { + "id": "K_T", + "text": "ត" + }, + { + "id": "K_Y", + "text": "យ" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ផ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឪ" + }, + { + "id": "T_new_138", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "K_BKQUOTE", + "text": "«" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "ស" + }, + { + "id": "K_D", + "text": "ដ" + }, + { + "id": "K_F", + "text": "ថ" + }, + { + "id": "K_G", + "text": "ង" + }, + { + "id": "K_H", + "text": "ហ" + }, + { + "id": "K_J", + "text": "" + }, + { + "id": "K_K", + "text": "ក" + }, + { + "id": "K_L", + "text": "ល" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឮ" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2" + }, + { + "id": "K_Z", + "text": "ឋ" + }, + { + "id": "K_X", + "text": "ខ" + }, + { + "id": "K_C", + "text": "ច" + }, + { + "id": "K_V", + "text": "វ" + }, + { + "id": "K_B", + "text": "ប" + }, + { + "id": "K_N", + "text": "ន" + }, + { + "id": "K_M", + "text": "ម" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "។" + }, + { + "id": "K_SLASH", + "text": "" + }, + { + "id": "T_new_164", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "5", + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "rightalt", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_1", + "text": "‌" + }, + { + "id": "K_2", + "text": "@" + }, + { + "id": "K_3", + "text": "" + }, + { + "id": "K_4", + "text": "$" + }, + { + "id": "K_5", + "text": "€" + }, + { + "id": "K_6", + "text": "៙" + }, + { + "id": "K_7", + "text": "៚" + }, + { + "id": "K_8", + "text": "*" + }, + { + "id": "K_9", + "text": "{" + }, + { + "id": "K_0", + "text": "}" + }, + { + "id": "K_HYPHEN", + "text": "≈" + }, + { + "id": "K_EQUAL", + "text": "" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "K_Q", + "text": "ៜ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "ឯ" + }, + { + "id": "K_R", + "text": "ឫ" + }, + { + "id": "K_T", + "text": "ឨ" + }, + { + "id": "K_Y", + "text": "[" + }, + { + "id": "K_U", + "text": "]" + }, + { + "id": "K_I", + "text": "ឦ" + }, + { + "id": "K_O", + "text": "ឱ" + }, + { + "id": "K_P", + "text": "ឰ" + }, + { + "id": "K_LBRKT", + "text": "ឩ" + }, + { + "id": "K_RBRKT", + "text": "ឳ" + }, + { + "id": "T_new_307", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "K_BKQUOTE", + "text": "‍" + }, + { + "id": "K_A", + "text": "+" + }, + { + "id": "K_S", + "text": "-" + }, + { + "id": "K_D", + "text": "×" + }, + { + "id": "K_F", + "text": "÷" + }, + { + "id": "K_G", + "text": ":" + }, + { + "id": "K_H", + "text": "‘" + }, + { + "id": "K_J", + "text": "’" + }, + { + "id": "K_K", + "text": "ឝ" + }, + { + "id": "K_L", + "text": "៘" + }, + { + "id": "K_COLON", + "text": "៖" + }, + { + "id": "K_QUOTE", + "text": "ៈ" + }, + { + "id": "K_BKSLASH", + "text": "\\" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2" + }, + { + "id": "K_Z", + "text": "<" + }, + { + "id": "K_X", + "text": ">" + }, + { + "id": "K_C", + "text": "#" + }, + { + "id": "K_V", + "text": "&" + }, + { + "id": "K_B", + "text": "ឞ" + }, + { + "id": "K_N", + "text": ";" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "," + }, + { + "id": "K_PERIOD", + "text": "." + }, + { + "id": "K_SLASH", + "text": "/" + }, + { + "id": "T_new_333", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "5", + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": " ", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_1", + "text": "!" + }, + { + "id": "K_2", + "text": "ៗ" + }, + { + "id": "K_3", + "text": "\"" + }, + { + "id": "K_4", + "text": "៛" + }, + { + "id": "K_5", + "text": "%" + }, + { + "id": "K_6", + "text": "" + }, + { + "id": "K_7", + "text": "" + }, + { + "id": "K_8", + "text": "" + }, + { + "id": "K_9", + "text": "(" + }, + { + "id": "K_0", + "text": ")" + }, + { + "id": "K_HYPHEN", + "text": "" + }, + { + "id": "K_EQUAL", + "text": "=" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "K_Q", + "text": "ឈ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "ឬ" + }, + { + "id": "K_T", + "text": "ទ" + }, + { + "id": "K_Y", + "text": "" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ភ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឧ" + }, + { + "id": "T_new_364", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "K_BKQUOTE", + "text": "»" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "" + }, + { + "id": "K_D", + "text": "ឌ" + }, + { + "id": "K_F", + "text": "ធ" + }, + { + "id": "K_G", + "text": "អ" + }, + { + "id": "K_H", + "text": "ះ" + }, + { + "id": "K_J", + "text": "ញ" + }, + { + "id": "K_K", + "text": "គ" + }, + { + "id": "K_L", + "text": "ឡ" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឭ" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_oE2" + }, + { + "id": "K_Z", + "text": "ឍ" + }, + { + "id": "K_X", + "text": "ឃ" + }, + { + "id": "K_C", + "text": "ជ" + }, + { + "id": "K_V", + "text": "" + }, + { + "id": "K_B", + "text": "ព" + }, + { + "id": "K_N", + "text": "ណ" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "៕" + }, + { + "id": "K_SLASH", + "text": "?" + }, + { + "id": "T_new_390", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": "5", + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + } + ], + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + }, + "phone": { + "layer": [ + { + "id": "default", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "sk": [ + { + "text": "ឈ", + "id": "K_Q", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1786" + }, + { + "text": "", + "id": "T_17D2_1788" + } + ] + }, + { + "id": "K_W", + "text": "", + "sk": [ + { + "text": "", + "id": "K_W", + "layer": "shift" + } + ] + }, + { + "id": "K_E", + "text": "", + "sk": [ + { + "text": "", + "id": "K_E", + "layer": "shift" + }, + { + "text": "", + "id": "K_S", + "layer": "shift" + }, + { + "text": "", + "id": "K_V", + "layer": "shift" + }, + { + "text": "ឯ", + "id": "U_17AF" + }, + { + "text": "ឰ", + "id": "U_17B0" + } + ] + }, + { + "id": "K_R", + "text": "រ", + "sk": [ + { + "text": "", + "id": "T_17D2_179A" + }, + { + "text": "ឫ", + "id": "U_17AB" + }, + { + "text": "ឬ", + "id": "U_17AC" + } + ] + }, + { + "id": "K_T", + "text": "ត", + "sk": [ + { + "text": "ទ", + "id": "K_T", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178F" + }, + { + "text": "", + "id": "T_17D2_1791", + "layer": "default" + } + ] + }, + { + "id": "K_Y", + "text": "យ", + "sk": [ + { + "text": "", + "id": "T_17D2_1799" + } + ] + }, + { + "id": "K_U", + "text": "", + "sk": [ + { + "text": "", + "id": "K_U", + "layer": "shift" + }, + { + "text": "", + "id": "K_Y", + "layer": "shift" + }, + { + "text": "ឧ", + "id": "U_17A7" + }, + { + "text": "ឪ", + "id": "U_17AA", + "layer": "shift" + }, + { + "text": "ឩ", + "id": "U_17A9", + "layer": "shift" + }, + { + "text": "ឨ", + "id": "U_17A8" + } + ] + }, + { + "id": "K_I", + "text": "", + "sk": [ + { + "text": "", + "id": "K_I", + "layer": "shift" + }, + { + "text": "ឥ", + "id": "U_17A5" + }, + { + "text": "ឦ", + "id": "U_17A6", + "layer": "shift" + } + ] + }, + { + "id": "K_O", + "text": "", + "sk": [ + { + "text": "", + "id": "K_O", + "layer": "shift" + }, + { + "text": "", + "id": "K_LBRKT" + }, + { + "text": "", + "id": "K_LBRKT", + "layer": "shift" + }, + { + "text": "", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": "ឱ", + "id": "U_17B1" + }, + { + "text": "ឲ", + "id": "U_17B2" + }, + { + "text": "ឳ", + "id": "U_17B3", + "layer": "shift" + } + ] + }, + { + "id": "K_P", + "text": "ផ", + "sk": [ + { + "text": "ភ", + "id": "K_P", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1795" + }, + { + "text": "", + "id": "T_17D2_1797", + "layer": "default" + } + ] + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "K_A", + "text": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_A", + "layer": "shift" + } + ] + }, + { + "id": "K_S", + "text": "ស", + "sk": [ + { + "text": "", + "id": "T_17D2_179F" + }, + { + "text": "ឝ", + "id": "U_179D" + }, + { + "text": "ឞ", + "id": "U_179E" + } + ] + }, + { + "id": "K_D", + "text": "ដ", + "sk": [ + { + "text": "ឌ", + "id": "K_D", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178A" + }, + { + "text": "", + "id": "T_17D2_178C", + "layer": "default" + } + ] + }, + { + "id": "K_F", + "text": "ថ", + "sk": [ + { + "text": "ធ", + "id": "K_F", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1790" + }, + { + "text": "", + "id": "T_17D2_1792", + "layer": "default" + } + ] + }, + { + "id": "K_G", + "text": "ង", + "sk": [ + { + "text": "អ", + "id": "K_G", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1784" + }, + { + "text": "", + "id": "T_17D2_17A2", + "layer": "default" + } + ] + }, + { + "id": "K_H", + "text": "ហ", + "sk": [ + { + "text": "", + "id": "T_17D2_17A0" + }, + { + "text": "ះ", + "id": "K_H", + "layer": "shift" + }, + { + "text": "ៈ", + "id": "U_17C8" + } + ] + }, + { + "id": "K_J", + "text": "ញ", + "layer": "shift", + "sk": [ + { + "text": "", + "id": "T_17D2_1789" + } + ] + }, + { + "id": "K_K", + "text": "ក", + "sk": [ + { + "text": "គ", + "id": "K_K", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1780" + }, + { + "text": "", + "id": "T_17D2_1782" + } + ] + }, + { + "id": "K_L", + "text": "ល", + "sk": [ + { + "text": "ឡ", + "id": "K_L", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_179B" + }, + { + "text": "ឭ", + "id": "U_17AD" + }, + { + "text": "ឮ", + "id": "U_17AE" + } + ] + }, + { + "id": "K_COLON", + "text": "" + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "K_Z", + "text": "ឋ", + "sk": [ + { + "text": "ឍ", + "id": "K_Z", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178B" + }, + { + "text": "", + "id": "T_17D2_178D", + "layer": "default" + } + ] + }, + { + "id": "K_X", + "text": "ខ", + "sk": [ + { + "text": "ឃ", + "id": "K_X", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1781" + }, + { + "text": "", + "id": "T_17D2_1783", + "layer": "default" + } + ] + }, + { + "id": "K_C", + "text": "ច", + "sk": [ + { + "text": "ជ", + "id": "K_C", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1785" + }, + { + "text": "", + "id": "T_17D2_1787", + "layer": "default" + } + ] + }, + { + "id": "K_V", + "text": "វ", + "sk": [ + { + "text": "", + "id": "T_17D2_179C" + } + ] + }, + { + "id": "K_B", + "text": "ប", + "sk": [ + { + "text": "ព", + "id": "K_B", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1794" + }, + { + "text": "", + "id": "T_17D2_1796", + "layer": "default" + } + ] + }, + { + "id": "K_N", + "text": "ន", + "sk": [ + { + "text": "ណ", + "id": "K_N", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1793" + }, + { + "text": "", + "id": "T_17D2_178E", + "layer": "default" + } + ] + }, + { + "id": "K_M", + "text": "ម", + "sk": [ + { + "text": "", + "id": "T_17D2_1798" + }, + { + "text": "", + "id": "K_M", + "layer": "shift" + } + ] + }, + { + "id": "K_COMMA", + "text": "", + "sk": [ + { + "text": "", + "id": "K_COMMA", + "layer": "shift" + }, + { + "text": "", + "id": "K_6", + "layer": "shift" + }, + { + "text": "", + "id": "K_7", + "layer": "shift" + }, + { + "text": "", + "id": "K_8", + "layer": "shift" + }, + { + "text": "", + "id": "K_HYPHEN", + "layer": "shift" + }, + { + "text": "", + "id": "U_17D1", + "layer": "shift" + }, + { + "text": "", + "id": "U_17DD", + "layer": "shift" + }, + { + "text": "", + "id": "U_17CE", + "layer": "shift" + } + ] + }, + { + "id": "K_QUOTE", + "text": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "", + "id": "K_SLASH" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_NUMLOCK", + "text": "១២៣", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "sk": [ + { + "text": " ", + "id": "U_0020", + "layer": "default" + } + ] + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_1", + "text": "១", + "sk": [ + { + "text": "1", + "id": "U_0031" + } + ] + }, + { + "id": "K_2", + "text": "២", + "sk": [ + { + "text": "2", + "id": "U_0032" + } + ] + }, + { + "id": "K_3", + "text": "៣", + "sk": [ + { + "text": "3", + "id": "U_0033" + } + ] + }, + { + "id": "K_4", + "text": "៤", + "sk": [ + { + "text": "4", + "id": "U_0034" + } + ] + }, + { + "id": "K_5", + "text": "៥", + "sk": [ + { + "text": "5", + "id": "U_0035" + } + ] + }, + { + "id": "K_6", + "text": "៦", + "sk": [ + { + "text": "6", + "id": "U_0036" + } + ] + }, + { + "id": "K_7", + "text": "៧", + "sk": [ + { + "text": "7", + "id": "U_0037" + } + ] + }, + { + "id": "K_8", + "text": "៨", + "sk": [ + { + "text": "8", + "id": "U_0038" + } + ] + }, + { + "id": "K_9", + "text": "៩", + "sk": [ + { + "text": "9", + "id": "U_0039" + } + ] + }, + { + "id": "K_0", + "text": "០", + "sk": [ + { + "text": "0", + "id": "U_0030" + }, + { + "text": "", + "id": "U_17D3" + } + ] + } + ] + }, + { + "id": "2", + "key": [ + { + "id": "U_0040", + "text": "@", + "sk": [ + { + "text": "©", + "id": "U_00A9" + }, + { + "text": "®", + "id": "U_00AE" + } + ] + }, + { + "id": "U_0023", + "text": "#", + "sk": [ + { + "text": "№", + "id": "U_2116" + }, + { + "text": "~", + "id": "U_007E" + } + ] + }, + { + "id": "U_17DB", + "text": "៛", + "sk": [ + { + "text": "$", + "id": "U_0024" + }, + { + "text": "฿", + "id": "U_0E3F" + }, + { + "text": "¢", + "id": "U_00A2" + }, + { + "text": "£", + "id": "U_00A3" + }, + { + "text": "¥", + "id": "U_00A5" + } + ] + }, + { + "id": "U_0026", + "text": "&" + }, + { + "id": "U_0025", + "text": "%", + "sk": [ + { + "text": "‰", + "id": "U_2030" + }, + { + "text": "‱", + "id": "U_2031" + } + ] + }, + { + "id": "U_002B", + "text": "+", + "sk": [ + { + "text": "-", + "id": "U_002D" + }, + { + "text": "×", + "id": "U_00D7" + }, + { + "text": "÷", + "id": "U_00F7" + }, + { + "text": "±", + "id": "U_00B1" + } + ] + }, + { + "id": "U_003D", + "text": "=", + "sk": [ + { + "text": "_", + "id": "U_005F" + }, + { + "text": "≠", + "id": "U_2260" + } + ] + }, + { + "id": "U_002A", + "text": "*", + "sk": [ + { + "text": "^", + "id": "U_005E" + } + ] + }, + { + "id": "U_003F", + "text": "?", + "sk": [ + { + "text": "¿", + "id": "U_00BF" + } + ] + }, + { + "id": "U_0021", + "text": "!", + "sk": [ + { + "text": "¡", + "id": "U_00A1" + } + ] + } + ] + }, + { + "id": "3", + "key": [ + { + "id": "U_2018", + "text": "‘", + "sk": [ + { + "text": "’", + "id": "U_2019" + } + ] + }, + { + "id": "U_201C", + "text": "“", + "sk": [ + { + "text": "”", + "id": "U_201D" + } + ] + }, + { + "id": "U_00AB", + "text": "«", + "sk": [ + { + "text": "»", + "id": "U_00BB" + } + ] + }, + { + "id": "U_002F", + "text": "/", + "sk": [ + { + "text": "\\", + "id": "U_005C" + }, + { + "text": "|", + "id": "U_007C" + }, + { + "text": "¦", + "id": "U_00A6" + } + ] + }, + { + "id": "U_0028", + "text": "(", + "sk": [ + { + "text": ")", + "id": "U_0029" + }, + { + "text": "[", + "id": "U_005B" + }, + { + "text": "]", + "id": "U_005D" + }, + { + "text": "{", + "id": "U_007B" + }, + { + "text": "}", + "id": "U_007D" + } + ] + }, + { + "id": "U_17D9", + "text": "៙", + "sk": [ + { + "text": "៚", + "id": "U_17DA" + }, + { + "text": "ៜ", + "id": "U_17DC" + }, + { + "text": "§", + "id": "U_00A7" + }, + { + "text": "Ø", + "id": "U_00D8" + } + ] + }, + { + "id": "U_17D7", + "text": "ៗ" + }, + { + "id": "U_003C", + "text": "<", + "sk": [ + { + "text": "≤", + "id": "U_2264" + }, + { + "text": ">", + "id": "U_003E" + }, + { + "text": "≥", + "id": "U_2265" + } + ] + }, + { + "id": "U_17D6", + "text": "៖", + "sk": [ + { + "text": ":", + "id": "U_003A" + }, + { + "text": ";", + "id": "U_003B" + }, + { + "text": "…", + "id": "U_2026" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "sp": "1" + } + ] + }, + { + "id": "4", + "key": [ + { + "id": "K_LCONTROL", + "text": "១២៣", + "width": "140", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "layer": "shift" + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + } + ], + "displayUnderlying": false, + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + } +}; + this.s_c_key_11=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','']; + this.s_c_out_12="កខគឃងចឆជឈញដឋឌឍណតថទធនបផពភមយរលវសហឡអឝឞ"; + this.s_v_gen_key_13=['','','','','','','','','','','','','','','','']; + this.s_v_gen_14="ាិីឹឺុូួើឿៀេែៃោៅ"; + this.s_v_pseudo_key_15=['','','']; + this.s_v_pseudo_16="ំះៈ"; + this.s_v_key_17=['','','','','','','','','','','','','','','','','','','']; + this.s_v_out_18="ាិីឹឺុូួើឿៀេែៃោៅំះៈ"; + this.s_v_any_19="ាិីឹឺុូួើឿៀេែៃោៅំះៈ"; + this.s_v_combo_R_20="េោុិីឹែ"; + this.s_v_combo_N_21="ាុ"; + this.s_v_combo_22="េោុិីឹែាុ"; + this.s_ind_v_key_23=['','','','','','','','','','','','','','','']; + this.s_ind_v_out_24="ឥឦឧឨឩឪឫឬឭឮឯឰឱឲឳ"; + this.s_diacritic_key_25=['','','','','','','','','','','']; + this.s_diacritic_out_26="់័៌៏៍ៈ៎៑៝ៜ្"; + this.s_c_shifter_key_27=['','']; + this.s_c_shifter_28="៉៊"; + this.s_punct_key_29=['','','','','','','','']; + this.s_punct_out_30="។៕៖ៗ៘៙៚៓"; + this.s_latin_punct_key_31=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','']; + this.s_latin_punct_out_32="«»()!\"%=?{}\\@*,×./[]‍‌+-÷:≈‘’;<>#&"; + this.s_spaces_key_33=['','','']; + this.s_spaces_out_34="​  "; + this.s_currency_key_35=['','','']; + this.s_currency_out_36="៛$€"; + this.s_digit_key_37=['','','','','','','','','','']; + this.s_digit_out_38="០១២៣៤៥៦៧៨៩"; + this.s_lek_attak_key_39=['','','','','','','','','','']; + this.s_lek_attak_out_40="៰៱៲៳៴៵៶៷៸៹"; + this.s_lunar_date_key_41=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','']; + this.s_lunar_date_out_42="᧬᧻᧹᧮᧢᧯᧰᧱᧧᧲᧳᧴᧽᧼᧨᧩᧠᧣᧭᧤᧦᧺᧡᧸᧥᧷᧵᧾᧿᧪᧫᧶"; + this.s_input_subcons_43=['','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','','']; + this.s_subcons_44="កខគឃងចឆជឈញដឋឌឍណតថទធនបផពភមយរលវឝឞសហឡអ"; + this.s_arabic_digit_key_45=['','','','','','','','','','']; + this.s_arabic_digit_out_46="0123456789"; + this.s_v_above_47="ិីឹឺើ័"; + this.s_shiftable_c_1st_48="សហអ"; + this.s_shiftable_BA_49="ប"; + this.s_shiftable_c_2nd_50="ងញមយរវនល"; + this.s_shiftable_c_2nd_with_BA_51="ងញមយរវនលប"; + this.s_c_2nd_combo_LO_52="យមងបវ"; + this.s_c_2nd_combo_MO_53="យលងរ"; + this.s_c_1st_combo_LO_54="បហអ"; + this.s_c_1st_combo_MO_55="ហសអ"; + this.s_c_combo_SA_56="បយលមនញងរវអ"; + this.s_c_combo_QA_57="ឆឈបផតទ"; + this.s_c_combo_HA_58="វឣ"; + this.s62="touch"; + this.KVS=[]; + this.gs=function(t,e) { + return this.g_main_0(t,e); + }; + this.gs=function(t,e) { + return this.g_main_0(t,e); + }; + this.g_main_0=function(t,e) { + var k=KeymanWeb,r=0,m=0; + if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_BKSP /* 0x08 */)) { + if(k.KFCM(2,t,['្',{t:'a',a:this.s_c_out_12}])&&k.KIFS(31,this.s62,t)){ + r=m=1; // Line 266 + k.KDC(2,t); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_combo_N_21},'ំ'])){ + r=m=1; // Line 229 + k.KDC(2,t); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_combo_R_20},'ះ'])){ + r=m=1; // Line 230 + k.KDC(2,t); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_B /* 0x42 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឞ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_K /* 0x4B */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឝ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_QUOTE /* 0xDE */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ៈ"); + } + else if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"ៈ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_E /* 0x45 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឯ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_I /* 0x49 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឦ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_O /* 0x4F */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឱ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_P /* 0x50 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឰ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_R /* 0x52 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឫ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_T /* 0x54 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឨ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_LBRKT /* 0xDB */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឩ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_RBRKT /* 0xDD */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឳ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_3 /* 0x33 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៑"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_Q /* 0x51 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"ៜ"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_W /* 0x57 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៝"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_EQUAL /* 0xBB */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៎"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_6 /* 0x36 */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៙"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_7 /* 0x37 */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៚"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_L /* 0x4C */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៘"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_M /* 0x4D */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៓"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_COLON /* 0xBA */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៖"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_0 /* 0x30 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"}"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_1 /* 0x31 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"‌"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_2 /* 0x32 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"@"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_8 /* 0x38 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"*"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_9 /* 0x39 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"{"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_A /* 0x41 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"+"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"#"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_D /* 0x44 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"×"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_F /* 0x46 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"÷"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_G /* 0x47 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,":"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_H /* 0x48 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"‘"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_J /* 0x4A */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"’"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_N /* 0x4E */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,";"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"-"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_U /* 0x55 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"]"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_V /* 0x56 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"&"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_X /* 0x58 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,">"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_Y /* 0x59 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"["); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_Z /* 0x5A */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"<"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_COMMA /* 0xBC */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,","); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_HYPHEN /* 0xBD */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"≈"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_PERIOD /* 0xBE */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"."); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_SLASH /* 0xBF */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"/"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_BKQUOTE /* 0xC0 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"‍"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_BKSLASH /* 0xDC */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"\\"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_4 /* 0x34 */)) { + if(1){ + r=m=1; // Line 195 + k.KDC(0,t); + k.KO(-1,t,"$"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_5 /* 0x35 */)) { + if(1){ + r=m=1; // Line 195 + k.KDC(0,t); + k.KO(-1,t,"€"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_0 /* 0x30 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៰"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_1 /* 0x31 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៱"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_2 /* 0x32 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៲"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_3 /* 0x33 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៳"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_4 /* 0x34 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៴"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_5 /* 0x35 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៵"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_6 /* 0x36 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៶"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_7 /* 0x37 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៷"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_8 /* 0x38 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៸"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_9 /* 0x39 */)) { + if(1){ + r=m=1; // Line 197 + k.KDC(0,t); + k.KO(-1,t,"៹"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_A /* 0x41 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧬"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_B /* 0x42 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧻"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧹"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_D /* 0x44 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧮"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_E /* 0x45 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧢"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_F /* 0x46 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧯"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_G /* 0x47 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧰"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_H /* 0x48 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧱"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_I /* 0x49 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧧"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_J /* 0x4A */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧲"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_K /* 0x4B */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧳"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_L /* 0x4C */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧴"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_M /* 0x4D */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧽"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_N /* 0x4E */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧼"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_O /* 0x4F */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧨"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_P /* 0x50 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧩"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_Q /* 0x51 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧠"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_R /* 0x52 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧣"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧭"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_T /* 0x54 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧤"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_U /* 0x55 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧦"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_V /* 0x56 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧺"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_W /* 0x57 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧡"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_X /* 0x58 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧸"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_Y /* 0x59 */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧥"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_Z /* 0x5A */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧷"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_COLON /* 0xBA */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧵"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_COMMA /* 0xBC */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧾"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_PERIOD /* 0xBE */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧿"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_LBRKT /* 0xDB */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧪"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_RBRKT /* 0xDD */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧫"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4018 */, keyCodes.K_QUOTE /* 0xDE */)) { + if(1){ + r=m=1; // Line 198 + k.KDC(0,t); + k.KO(-1,t,"᧶"); + } + } + else if(k.KKM(e, modCodes.RALT | modCodes.VIRTUAL_KEY /* 0x4008 */, keyCodes.K_SPACE /* 0x20 */)) { + if(1){ + r=m=1; // Line 199 + k.KDC(0,t); + k.KO(-1,t," "); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x100)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ក"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x101)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ខ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x102)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្គ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x103)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឃ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x104)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ង"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x105)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ច"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x106)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឆ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x107)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ជ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x108)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឈ"); + } + } + if(m) {} + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x109)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ញ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10A)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ដ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10B)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឋ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10C)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឌ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10D)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឍ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10E)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ណ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x10F)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ត"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x110)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ថ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x111)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ទ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x112)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ធ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x113)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ន"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x114)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ប"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x115)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ផ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x116)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ព"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x117)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ភ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x118)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ម"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x119)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្យ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11A)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្រ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11B)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ល"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11C)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្វ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11D)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឝ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11E)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឞ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x11F)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ស"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x120)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ហ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x121)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្ឡ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, 0x122)) { + if(1){ + r=m=1; // Line 262 + k.KDC(0,t); + k.KO(-1,t,"្អ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPSTAR /* 0x6A */)) { + if(1){ + r=m=1; // Line 268 + k.KDC(0,t); + k.KO(-1,t,"*"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPSTAR /* 0x6A */)) { + if(1){ + r=m=1; // Line 269 + k.KDC(0,t); + k.KO(-1,t,"*"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPPLUS /* 0x6B */)) { + if(1){ + r=m=1; // Line 270 + k.KDC(0,t); + k.KO(-1,t,"+"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPPLUS /* 0x6B */)) { + if(1){ + r=m=1; // Line 271 + k.KDC(0,t); + k.KO(-1,t,"+"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPMINUS /* 0x6D */)) { + if(1){ + r=m=1; // Line 272 + k.KDC(0,t); + k.KO(-1,t,"-"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPMINUS /* 0x6D */)) { + if(1){ + r=m=1; // Line 273 + k.KDC(0,t); + k.KO(-1,t,"-"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPDOT /* 0x6E */)) { + if(1){ + r=m=1; // Line 274 + k.KDC(0,t); + k.KO(-1,t,"."); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPDOT /* 0x6E */)) { + if(1){ + r=m=1; // Line 275 + k.KDC(0,t); + k.KO(-1,t,"."); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_NPSLASH /* 0x6F */)) { + if(1){ + r=m=1; // Line 276 + k.KDC(0,t); + k.KO(-1,t,"/"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_NPSLASH /* 0x6F */)) { + if(1){ + r=m=1; // Line 277 + k.KDC(0,t); + k.KO(-1,t,"/"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_SPACE /* 0x20 */)) { + if(k.KFCM(1,t,['​'])){ + r=m=1; // Line 225 + k.KDC(1,t); + k.KO(-1,t," "); + } + else if(1){ + r=m=1; // Line 199 + k.KDC(0,t); + k.KO(-1,t,"​"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_SPACE /* 0x20 */)) { + if(1){ + r=m=1; // Line 199 + k.KDC(0,t); + k.KO(-1,t," "); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_1 /* 0x31 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"!"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_QUOTE /* 0xDE */)) { + if(k.KFCM(3,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ'])){ + r=m=1; // Line 244 + k.KDC(3,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្អ៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54}])){ + r=m=1; // Line 245 + k.KDC(3,t); + k.KO(-1,t,"ល្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55}])){ + r=m=1; // Line 246 + k.KDC(3,t); + k.KO(-1,t,"ម្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56}])){ + r=m=1; // Line 247 + k.KDC(3,t); + k.KO(-1,t,"ស្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៉"); + k.KB(t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ'])){ + r=m=1; // Line 248 + k.KDC(3,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្ហ៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['អ','្','ង'])){ + r=m=1; // Line 249 + k.KDC(3,t); + k.KO(-1,t,"អ្ង៉"); + k.KB(t); + } + else if(k.KFCM(3,t,['អ','្','វ'])){ + r=m=1; // Line 250 + k.KDC(3,t); + k.KO(-1,t,"អ្វ៉"); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_c_shifter_28}])){ + r=m=1; // Line 215 + k.KDC(1,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_shiftable_c_1st_48}])){ + r=m=1; // Line 239 + k.KDC(1,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៉"); + k.KB(t); + } + else if(1){ + r=m=1; // Line 192 + k.KDC(0,t); + k.KO(-1,t,"៉"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_3 /* 0x33 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"\""); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_4 /* 0x34 */)) { + if(1){ + r=m=1; // Line 195 + k.KDC(0,t); + k.KO(-1,t,"៛"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_5 /* 0x35 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"%"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_7 /* 0x37 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"័"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_QUOTE /* 0xDE */)) { + if(k.KFCM(2,t,['្',{t:'a',a:this.s_c_out_12}])){ + r=m=1; // Line 214 + k.KDC(2,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_out_12,2,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_v_gen_14}])){ + r=m=1; // Line 211 + k.KDC(1,t); + k.KIO(-1,this.s_v_gen_14,1,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_v_pseudo_16}])){ + r=m=1; // Line 212 + k.KDC(1,t); + k.KIO(-1,this.s_v_pseudo_16,1,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_c_shifter_28}])){ + r=m=1; // Line 213 + k.KDC(1,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KB(t); + } + else if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"់"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_9 /* 0x39 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"("); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_0 /* 0x30 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,")"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_8 /* 0x38 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៏"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_EQUAL /* 0xBB */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"="); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_COMMA /* 0xBC */)) { + if(1){ + r=m=1; // Line 206 + k.KDC(0,t); + k.KO(-1,t,"ុំ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_HYPHEN /* 0xBD */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឥ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_PERIOD /* 0xBE */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"។"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_SLASH /* 0xBF */)) { + if(k.KFCM(3,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52}])){ + r=m=1; // Line 254 + k.KDC(3,t); + k.KO(-1,t,"ល្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៊"); + k.KB(t); + } + else if(k.KFCM(3,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53}])){ + r=m=1; // Line 255 + k.KDC(3,t); + k.KO(-1,t,"ម្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៊"); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_c_shifter_28}])){ + r=m=1; // Line 215 + k.KDC(1,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KB(t); + } + else if(k.KFCM(1,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51}])){ + r=m=1; // Line 240 + k.KDC(1,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៊"); + k.KB(t); + } + else if(1){ + r=m=1; // Line 192 + k.KDC(0,t); + k.KO(-1,t,"៊"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_0 /* 0x30 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"០"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_1 /* 0x31 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"១"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_2 /* 0x32 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"២"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_3 /* 0x33 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៣"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_4 /* 0x34 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៤"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_5 /* 0x35 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៥"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_6 /* 0x36 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៦"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_7 /* 0x37 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៧"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_8 /* 0x38 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៨"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_9 /* 0x39 */)) { + if(1){ + r=m=1; // Line 196 + k.KDC(0,t); + k.KO(-1,t,"៩"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_COLON /* 0xBA */)) { + if(1){ + r=m=1; // Line 205 + k.KDC(0,t); + k.KO(-1,t,"ោះ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_COLON /* 0xBA */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ើ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_COMMA /* 0xBC */)) { + if(1){ + r=m=1; // Line 207 + k.KDC(0,t); + k.KO(-1,t,"ុះ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_EQUAL /* 0xBB */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឲ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_PERIOD /* 0xBE */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"៕"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_SLASH /* 0xBF */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"?"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_2 /* 0x32 */)) { + if(1){ + r=m=1; // Line 193 + k.KDC(0,t); + k.KO(-1,t,"ៗ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_A /* 0x41 */)) { + if(1){ + r=m=1; // Line 203 + k.KDC(0,t); + k.KO(-1,t,"ាំ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_B /* 0x42 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ព"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ជ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_D /* 0x44 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឌ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_E /* 0x45 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ែ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_F /* 0x46 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ធ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_G /* 0x47 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"អ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_H /* 0x48 */)) { + if(k.KFCM(1,t,['ះ'])){ + r=m=1; // Line 219 + k.KDC(1,t); + k.KO(-1,t,"ៈ"); + } + else if(k.KFCM(1,t,['ៈ'])){ + r=m=1; // Line 220 + k.KDC(1,t); + k.KO(-1,t,"ះ"); + } + else if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ះ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_I /* 0x49 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ី"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_J /* 0x4A */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ញ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_K /* 0x4B */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"គ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_L /* 0x4C */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឡ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_M /* 0x4D */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ំ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_N /* 0x4E */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ណ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_O /* 0x4F */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ៅ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_P /* 0x50 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ភ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_Q /* 0x51 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឈ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_R /* 0x52 */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឬ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ៃ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_T /* 0x54 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ទ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_U /* 0x55 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ូ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_V /* 0x56 */)) { + if(1){ + r=m=1; // Line 204 + k.KDC(0,t); + k.KO(-1,t,"េះ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_W /* 0x57 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ឺ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_X /* 0x58 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឃ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_Y /* 0x59 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ួ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_Z /* 0x5A */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឍ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_LBRKT /* 0xDB */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ៀ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_BKSLASH /* 0xDC */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឮ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_RBRKT /* 0xDD */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឪ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_6 /* 0x36 */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៍"); + } + } + if(m) {} + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_HYPHEN /* 0xBD */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"៌"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_BKQUOTE /* 0xC0 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"«"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_A /* 0x41 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ា"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_B /* 0x42 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ប"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_C /* 0x43 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ច"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_D /* 0x44 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ដ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_E /* 0x45 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"េ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_F /* 0x46 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ថ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_G /* 0x47 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ង"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_H /* 0x48 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ហ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_I /* 0x49 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ិ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_J /* 0x4A */)) { + if(1){ + r=m=1; // Line 191 + k.KDC(0,t); + k.KO(-1,t,"្"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_K /* 0x4B */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ក"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_L /* 0x4C */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ល"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_M /* 0x4D */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ម"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_N /* 0x4E */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ន"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_O /* 0x4F */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ោ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_P /* 0x50 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ផ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_Q /* 0x51 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឆ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_R /* 0x52 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"រ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_S /* 0x53 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ស"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_T /* 0x54 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ត"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_U /* 0x55 */)) { + if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ា','ំ'])){ + r=m=1; // Line 234 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ា','ំ'])){ + r=m=1; // Line 235 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ុ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_V /* 0x56 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"វ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_W /* 0x57 */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ឹ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_X /* 0x58 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ខ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_Y /* 0x59 */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"យ"); + } + } + else if(k.KKM(e, modCodes.VIRTUAL_KEY /* 0x4000 */, keyCodes.K_Z /* 0x5A */)) { + if(1){ + r=m=1; // Line 188 + k.KDC(0,t); + k.KO(-1,t,"ឋ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_LBRKT /* 0xDB */)) { + if(1){ + r=m=1; // Line 189 + k.KDC(0,t); + k.KO(-1,t,"ឿ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_BKSLASH /* 0xDC */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឭ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_RBRKT /* 0xDD */)) { + if(1){ + r=m=1; // Line 190 + k.KDC(0,t); + k.KO(-1,t,"ឧ"); + } + } + else if(k.KKM(e, modCodes.SHIFT | modCodes.VIRTUAL_KEY /* 0x4010 */, keyCodes.K_BKQUOTE /* 0xC0 */)) { + if(1){ + r=m=1; // Line 194 + k.KDC(0,t); + k.KO(-1,t,"»"); + } + } + if(m==1) { + + k.KDC(-1,t); + r=this.g_normalise_1(t,e); + m=2; + } + return r; + }; + this.g_normalise_1=function(t,e) { + var k=KeymanWeb,r=1,m=0; + if(k.KFCM(7,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ','ំ','ា','ំ'])){ + m=1; // Line 376 + k.KDC(7,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ','ំ','ា','ំ'])){ + m=1; // Line 381 + k.KDC(7,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ','ំ','ា','ំ'])){ + m=1; // Line 386 + k.KDC(7,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ស','្','ប','ុ','ំ','ា','ំ'])){ + m=1; // Line 391 + k.KDC(7,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ','ំ','ា','ំ'])){ + m=1; // Line 396 + k.KDC(7,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ','ំ','ា','ំ'])){ + m=1; // Line 401 + k.KDC(7,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['អ','្','ង','ុ','ំ','ា','ំ'])){ + m=1; // Line 406 + k.KDC(7,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['អ','្','វ','ុ','ំ','ា','ំ'])){ + m=1; // Line 411 + k.KDC(7,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ហ','្','ប','ុ','ំ','ា','ំ'])){ + m=1; // Line 416 + k.KDC(7,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ហ','្',{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ំ','ា','ំ'])){ + m=1; // Line 422 + k.KDC(7,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ','ំ','ា','ំ'])){ + m=1; // Line 429 + k.KDC(7,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(7,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ','ំ','ា','ំ'])){ + m=1; // Line 434 + k.KDC(7,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['្','ដ',{t:'a',a:this.s_v_combo_N_21},'ំ','្','រ'])){ + m=1; // Line 340 + k.KDC(6,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_N_21,3,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['្','ដ',{t:'a',a:this.s_v_combo_R_20},'ះ','្','រ'])){ + m=1; // Line 341 + k.KDC(6,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_R_20,3,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s_v_combo_N_21},'ំ','្','ដ'])){ + m=1; // Line 344 + k.KDC(6,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_N_21,3,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s_v_combo_R_20},'ះ','្','ដ'])){ + m=1; // Line 345 + k.KDC(6,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_R_20,3,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s_v_combo_N_21},'ំ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 350 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,6,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_N_21,3,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['្','រ',{t:'a',a:this.s_v_combo_R_20},'ះ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 351 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,6,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_combo_R_20,3,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ','ា','ំ'])){ + m=1; // Line 374 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ','ា','ំ'])){ + m=1; // Line 379 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ','ា','ំ'])){ + m=1; // Line 384 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្','ប','ុ','ា','ំ'])){ + m=1; // Line 389 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ','ា','ំ'])){ + m=1; // Line 394 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ','ា','ំ'])){ + m=1; // Line 399 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','ង','ុ','ា','ំ'])){ + m=1; // Line 404 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','វ','ុ','ា','ំ'])){ + m=1; // Line 409 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ហ','្','ប','ុ','ា','ំ'])){ + m=1; // Line 414 + k.KDC(6,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ប"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ហ','្',{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ា','ំ'])){ + m=1; // Line 420 + k.KDC(6,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ','ា','ំ'])){ + m=1; // Line 427 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ','ា','ំ'])){ + m=1; // Line 432 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'៊',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 454 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_gen_14,5,t); + k.KIO(-1,this.s_v_pseudo_16,6,t); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'៊',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 455 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_gen_14,5,t); + k.KIO(-1,this.s_v_pseudo_16,6,t); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','៉','ា','ំ'])){ + m=1; // Line 489 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'៉','ា','ំ'])){ + m=1; // Line 490 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'៉','ា','ំ'])){ + m=1; // Line 491 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្','ប','៉','ា','ំ'])){ + m=1; // Line 492 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'៉','ា','ំ'])){ + m=1; // Line 493 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','៉','ា','ំ'])){ + m=1; // Line 494 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','ង','៉','ា','ំ'])){ + m=1; // Line 495 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','វ','៉','ា','ំ'])){ + m=1; // Line 496 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ា','ុ','ំ'])){ + m=1; // Line 503 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ','ំ','ា'])){ + m=1; // Line 504 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ា','ុ','ំ'])){ + m=1; // Line 506 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ','ំ','ា'])){ + m=1; // Line 507 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ា','ុ','ំ'])){ + m=1; // Line 509 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ','ំ','ា'])){ + m=1; // Line 510 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ា','ុ','ំ'])){ + m=1; // Line 512 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ','ំ','ា'])){ + m=1; // Line 513 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ា','ុ','ំ'])){ + m=1; // Line 515 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ','ំ','ា'])){ + m=1; // Line 516 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','ង','ា','ុ','ំ'])){ + m=1; // Line 518 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','ង','ុ','ំ','ា'])){ + m=1; // Line 519 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','វ','ា','ុ','ំ'])){ + m=1; // Line 521 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['អ','្','វ','ុ','ំ','ា'])){ + m=1; // Line 522 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KO(-1,t,"ុ"); + k.KO(-1,t,"ា"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ា','ុ','ំ'])){ + m=1; // Line 529 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ','ំ','ា'])){ + m=1; // Line 530 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ា','ុ','ំ'])){ + m=1; // Line 532 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ','ំ','ា'])){ + m=1; // Line 533 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','េ','ុ','ី'])){ + m=1; // Line 541 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ','េ','ី'])){ + m=1; // Line 542 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','៉','េ','ី'])){ + m=1; // Line 543 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'េ','ុ','ី'])){ + m=1; // Line 545 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ','េ','ី'])){ + m=1; // Line 546 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'៉','េ','ី'])){ + m=1; // Line 547 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'េ','ុ','ី'])){ + m=1; // Line 549 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ','េ','ី'])){ + m=1; // Line 550 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'៉','េ','ី'])){ + m=1; // Line 551 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'េ','ុ','ី'])){ + m=1; // Line 553 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ','េ','ី'])){ + m=1; // Line 554 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'៉','េ','ី'])){ + m=1; // Line 555 + k.KDC(6,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','េ','ុ','ី'])){ + m=1; // Line 557 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ','េ','ី'])){ + m=1; // Line 558 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊ើ"); + } + else if(k.KFCM(6,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','៉','េ','ី'])){ + m=1; // Line 559 + k.KDC(6,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','ង','េ','ុ','ី'])){ + m=1; // Line 561 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','ង','ុ','េ','ី'])){ + m=1; // Line 562 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','ង','៉','េ','ី'])){ + m=1; // Line 563 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','វ','េ','ុ','ី'])){ + m=1; // Line 565 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','វ','ុ','េ','ី'])){ + m=1; // Line 566 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊ើ"); + } + else if(k.KFCM(6,t,['អ','្','វ','៉','េ','ី'])){ + m=1; // Line 567 + k.KDC(6,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'េ','ុ','ី'])){ + m=1; // Line 575 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ','េ','ី'])){ + m=1; // Line 576 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'៊','េ','ី'])){ + m=1; // Line 577 + k.KDC(6,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'េ','ុ','ី'])){ + m=1; // Line 579 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ','េ','ី'])){ + m=1; // Line 580 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'៊','េ','ី'])){ + m=1; // Line 581 + k.KDC(6,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(6,t,['្','យ','្',{t:'a',a:this.s_c_out_12},'េ','ឺ'])){ + m=1; // Line 631 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_out_12,4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(6,t,['្','យ','្',{t:'a',a:this.s_c_out_12},'េ','ឹ'])){ + m=1; // Line 632 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_out_12,4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(6,t,['្','យ','្',{t:'a',a:this.s_c_out_12},'េ','ី'])){ + m=1; // Line 633 + k.KDC(6,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_out_12,4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_combo_N_21},'ំ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 331 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,5,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KIO(-1,this.s_v_combo_N_21,2,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_combo_R_20},'ះ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 332 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,5,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KIO(-1,this.s_v_combo_R_20,2,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(5,t,['្','ដ',{t:'a',a:this.s_v_any_19},'្','រ'])){ + m=1; // Line 339 + k.KDC(5,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_any_19,3,t); + } + else if(k.KFCM(5,t,['្','រ',{t:'a',a:this.s_v_any_19},'្','ដ'])){ + m=1; // Line 343 + k.KDC(5,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_any_19,3,t); + } + else if(k.KFCM(5,t,['្','រ',{t:'a',a:this.s_c_shifter_28},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 347 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,5,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_c_shifter_28,3,t); + } + else if(k.KFCM(5,t,['្','រ',{t:'a',a:this.s_v_any_19},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 349 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,5,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + k.KIO(-1,this.s_v_any_19,3,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 373 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 375 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 378 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 380 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 383 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 385 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + if(m) {} + else if(k.KFCM(5,t,['ស','្','ប','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 388 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ស','្','ប',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 390 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 393 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 395 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 398 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 400 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['អ','្','ង','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 403 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['អ','្','ង',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 405 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['អ','្','វ','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 408 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['អ','្','វ',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 410 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ហ','្','ប','ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 413 + k.KDC(5,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ហ','្','ប',{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 415 + k.KDC(5,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ហ','្',{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 419 + k.KDC(5,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ហ','្',{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 421 + k.KDC(5,t); + k.KO(-1,t,"ហ"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 426 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 428 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 431 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 433 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ','ំ','ា','ំ'])){ + m=1; // Line 441 + k.KDC(5,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ំ','ា','ំ'])){ + m=1; // Line 448 + k.KDC(5,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_2nd_combo_LO_52},'៊',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 452 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_LO_52,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_2nd_combo_MO_53},'៊',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 453 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_2nd_combo_MO_53,3,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['្',{t:'a',a:this.s_shiftable_c_2nd_50},'៊',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 463 + k.KDC(5,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_50,2,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_gen_14,4,t); + k.KIO(-1,this.s_v_pseudo_16,5,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_QA_57},'្','អ','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 481 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_QA_57,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"អ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ល','្',{t:'a',a:this.s_c_1st_combo_LO_54},'៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 482 + k.KDC(5,t); + k.KO(-1,t,"ល"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_LO_54,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ម','្',{t:'a',a:this.s_c_1st_combo_MO_55},'៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 483 + k.KDC(5,t); + k.KO(-1,t,"ម"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_1st_combo_MO_55,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ស','្','ប','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 484 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ប៉"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ស','្',{t:'a',a:this.s_c_combo_SA_56},'៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 485 + k.KDC(5,t); + k.KO(-1,t,"ស"); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_c_combo_SA_56,3,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,[{t:'a',a:this.s_c_combo_HA_58},'្','ហ','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 486 + k.KDC(5,t); + k.KIO(-1,this.s_c_combo_HA_58,1,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"ហ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['អ','្','ង','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 487 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"ង៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['អ','្','វ','៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 488 + k.KDC(5,t); + k.KO(-1,t,"អ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វ៊"); + k.KIO(-1,this.s_v_above_47,5,t); + } + else if(k.KFCM(5,t,['ព','័','ន','្','ឋ'])){ + m=1; // Line 619 + k.KDC(5,t); + k.KO(-1,t,"ព"); + k.KO(-1,t,"័"); + k.KO(-1,t,"ន"); + k.KO(-1,t,"្ធ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 312 + k.KDC(4,t); + k.KIO(-1,this.s_v_gen_14,3,t); + k.KIO(-1,this.s_v_pseudo_16,4,t); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_v_combo_N_21},'ំ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 325 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,4,t); + k.KIO(-1,this.s_v_combo_N_21,1,t); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_v_combo_R_20},'ះ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 326 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,4,t); + k.KIO(-1,this.s_v_combo_R_20,1,t); + k.KO(-1,t,"ះ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_any_19},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 330 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,4,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + k.KIO(-1,this.s_v_any_19,2,t); + } + else if(k.KFCM(4,t,['្','ដ','្','រ'])){ + m=1; // Line 336 + k.KDC(4,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + } + else if(k.KFCM(4,t,['្','រ','្','ដ'])){ + m=1; // Line 337 + k.KDC(4,t); + k.KO(-1,t,"្ត"); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + } + else if(k.KFCM(4,t,['្','រ','្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 348 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,4,t); + k.KO(-1,t,"្"); + k.KO(-1,t,"រ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 362 + k.KDC(4,t); + k.KIO(-1,this.s_v_gen_14,2,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ','ា','ំ'])){ + m=1; // Line 439 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ា','ំ'])){ + m=1; // Line 446 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'៊',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 460 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_gen_14,3,t); + k.KIO(-1,this.s_v_pseudo_16,4,t); + } + else if(k.KFCM(4,t,['្',{t:'a',a:this.s_shiftable_c_2nd_50},'៊',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 462 + k.KDC(4,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_shiftable_c_2nd_50,2,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,4,t); + } + else if(k.KFCM(4,t,['ប','្','យ',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 467 + k.KDC(4,t); + k.KO(-1,t,"ប្យ"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ស','្','ប',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 468 + k.KDC(4,t); + k.KO(-1,t,"ស្ប"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ឆ','្','ប',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 469 + k.KDC(4,t); + k.KO(-1,t,"ឆ្ប"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ប','្','យ',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 470 + k.KDC(4,t); + k.KO(-1,t,"ប្យ"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ស','្','ប',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 471 + k.KDC(4,t); + k.KO(-1,t,"ស្ប"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,['ឆ','្','ប',{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 472 + k.KDC(4,t); + k.KO(-1,t,"ឆ្ប"); + k.KIO(-1,this.s_c_shifter_28,4,t); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'៉',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 477 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_gen_14,3,t); + k.KIO(-1,this.s_v_pseudo_16,4,t); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ា','ុ','ំ'])){ + m=1; // Line 500 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ','ំ','ា'])){ + m=1; // Line 501 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ា','ុ','ំ'])){ + m=1; // Line 526 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ','ំ','ា'])){ + m=1; // Line 527 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KO(-1,t,"ា"); + k.KO(-1,t,"ំ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'េ','ុ','ី'])){ + m=1; // Line 537 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ','េ','ី'])){ + m=1; // Line 538 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_1st_48},'៉','េ','ី'])){ + m=1; // Line 539 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'េ','ុ','ី'])){ + m=1; // Line 571 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'ុ','េ','ី'])){ + m=1; // Line 572 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(4,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'៊','េ','ី'])){ + m=1; // Line 573 + k.KDC(4,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉ើ"); + } + else if(k.KFCM(4,t,['ព','ន','្','ឋ'])){ + m=1; // Line 618 + k.KDC(4,t); + k.KO(-1,t,"ព"); + k.KO(-1,t,"ន"); + k.KO(-1,t,"្ធ"); + } + else if(k.KFCM(4,t,['្','យ','េ','ឺ'])){ + m=1; // Line 627 + k.KDC(4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(4,t,['្','យ','េ','ឹ'])){ + m=1; // Line 628 + k.KDC(4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(4,t,['្','យ','េ','ី'])){ + m=1; // Line 629 + k.KDC(4,t); + k.KO(-1,t,"ឿ"); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_gen_14}])){ + m=1; // Line 308 + k.KDC(3,t); + k.KIO(-1,this.s_v_gen_14,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 309 + k.KDC(3,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 310 + k.KDC(3,t); + k.KIO(-1,this.s_v_gen_14,2,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 311 + k.KDC(3,t); + k.KIO(-1,this.s_v_gen_14,2,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + } + else if(k.KFCM(3,t,['្',{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 320 + k.KDC(3,t); + k.KIO(-1,this.s_v_gen_14,2,t); + k.KIO(-1,this.s_v_pseudo_16,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_any_19},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 324 + k.KDC(3,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,3,t); + k.KIO(-1,this.s_v_any_19,1,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_c_shifter_28},'្',{t:'a',a:this.s_subcons_44}])){ + m=1; // Line 355 + k.KDC(3,t); + k.KO(-1,t,"្"); + k.KIO(-1,this.s_subcons_44,3,t); + k.KIO(-1,this.s_c_shifter_28,1,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 360 + k.KDC(3,t); + k.KIO(-1,this.s_c_shifter_28,3,t); + k.KIO(-1,this.s_v_gen_14,1,t); + k.KIO(-1,this.s_v_pseudo_16,2,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_c_shifter_28},{t:'a',a:this.s_v_any_19},{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 361 + k.KDC(3,t); + k.KIO(-1,this.s_c_shifter_28,3,t); + k.KIO(-1,this.s_v_any_19,2,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_1st_48},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 438 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_1st_48},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 440 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,2,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},'ុ',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 445 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_2nd_with_BA_51},{t:'a',a:this.s_v_above_47},'ុ'])){ + m=1; // Line 447 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_2nd_with_BA_51,1,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,2,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_2nd_50},'៊',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 459 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_2nd_50,1,t); + k.KO(-1,t,"៉"); + k.KIO(-1,this.s_v_above_47,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_shiftable_c_1st_48},'៉',{t:'a',a:this.s_v_above_47}])){ + m=1; // Line 476 + k.KDC(3,t); + k.KIO(-1,this.s_shiftable_c_1st_48,1,t); + k.KO(-1,t,"៊"); + k.KIO(-1,this.s_v_above_47,3,t); + } + else if(k.KFCM(3,t,[{t:'a',a:this.s_c_out_12},{t:'a',a:this.s_v_gen_14},'៌'])){ + m=1; // Line 585 + k.KDC(3,t); + k.KIO(-1,this.s_c_out_12,1,t); + k.KO(-1,t,"៌"); + k.KIO(-1,this.s_v_gen_14,2,t); + } + else if(k.KFCM(3,t,['ណ','្','ត'])){ + m=1; // Line 589 + k.KDC(3,t); + k.KO(-1,t,"ណ"); + k.KO(-1,t,"្ដ"); + } + else if(k.KFCM(3,t,['ន','្','ដ'])){ + m=1; // Line 590 + k.KDC(3,t); + k.KO(-1,t,"ន"); + k.KO(-1,t,"្ត"); + } + else if(k.KFCM(3,t,['ទ','្','ប'])){ + m=1; // Line 594 + k.KDC(3,t); + k.KO(-1,t,"ឡ"); + } + else if(k.KFCM(3,t,['ប','្','ញ'])){ + m=1; // Line 596 + k.KDC(3,t); + k.KO(-1,t,"ឫ"); + } + else if(k.KFCM(3,t,['ព','្','ញ'])){ + m=1; // Line 602 + k.KDC(3,t); + k.KO(-1,t,"ឭ"); + } + else if(k.KFCM(3,t,['ព','្','ឋ'])){ + m=1; // Line 605 + k.KDC(3,t); + k.KO(-1,t,"ឰ"); + } + else if(k.KFCM(3,t,['ដ','្','ធ'])){ + m=1; // Line 613 + k.KDC(3,t); + k.KO(-1,t,"ដ្ឋ"); + } + else if(k.KFCM(3,t,['ទ','្','ឋ'])){ + m=1; // Line 614 + k.KDC(3,t); + k.KO(-1,t,"ទ្ធ"); + } + else if(k.KFCM(3,t,['ឪ','្','យ'])){ + m=1; // Line 622 + k.KDC(3,t); + k.KO(-1,t,"ឱ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"យ"); + } + else if(k.KFCM(3,t,['ឳ','្','យ'])){ + m=1; // Line 623 + k.KDC(3,t); + k.KO(-1,t,"ឱ"); + k.KO(-1,t,"្"); + k.KO(-1,t,"យ"); + } + else if(k.KFCM(3,t,['ញ','្','វ'])){ + m=1; // Line 625 + k.KDC(3,t); + k.KO(-1,t,"ព"); + k.KO(-1,t,"្"); + k.KO(-1,t,"វា"); + } + else if(k.KFCM(2,t,['េ','ា'])){ + m=1; // Line 291 + k.KDC(2,t); + k.KO(-1,t,"ោ"); + } + else if(k.KFCM(2,t,['ា','េ'])){ + m=1; // Line 292 + k.KDC(2,t); + k.KO(-1,t,"ោ"); + } + else if(k.KFCM(2,t,['េ','ី'])){ + m=1; // Line 293 + k.KDC(2,t); + k.KO(-1,t,"ើ"); + } + else if(k.KFCM(2,t,['ី','េ'])){ + m=1; // Line 294 + k.KDC(2,t); + k.KO(-1,t,"ើ"); + } + else if(k.KFCM(2,t,['ំ','ុ'])){ + m=1; // Line 298 + k.KDC(2,t); + k.KO(-1,t,"ុំ"); + } + else if(k.KFCM(2,t,['ំ','ា'])){ + m=1; // Line 299 + k.KDC(2,t); + k.KO(-1,t,"ាំ"); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_gen_14},{t:'a',a:this.s_v_gen_14}])){ + m=1; // Line 304 + k.KDC(2,t); + k.KIO(-1,this.s_v_gen_14,2,t); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_pseudo_16},{t:'a',a:this.s_v_pseudo_16}])){ + m=1; // Line 313 + k.KDC(2,t); + k.KIO(-1,this.s_v_pseudo_16,2,t); + } + if(m) {} + else if(k.KFCM(2,t,['្','្'])){ + m=1; // Line 318 + k.KDC(2,t); + k.KO(-1,t,"្"); + } + else if(k.KFCM(2,t,['្',{t:'a',a:this.s_v_any_19}])){ + m=1; // Line 319 + k.KDC(2,t); + k.KIO(-1,this.s_v_any_19,2,t); + } + else if(k.KFCM(2,t,[{t:'a',a:this.s_v_any_19},{t:'a',a:this.s_c_shifter_28}])){ + m=1; // Line 359 + k.KDC(2,t); + k.KIO(-1,this.s_c_shifter_28,2,t); + k.KIO(-1,this.s_v_any_19,1,t); + } + else if(k.KFCM(2,t,['ឫ','ុ'])){ + m=1; // Line 597 + k.KDC(2,t); + k.KO(-1,t,"ឬ"); + } + else if(k.KFCM(2,t,['ឭ','ា'])){ + m=1; // Line 599 + k.KDC(2,t); + k.KO(-1,t,"ញ"); + } + else if(k.KFCM(2,t,['ឮ','ា'])){ + m=1; // Line 600 + k.KDC(2,t); + k.KO(-1,t,"ញ"); + } + else if(k.KFCM(2,t,['ឭ','ុ'])){ + m=1; // Line 603 + k.KDC(2,t); + k.KO(-1,t,"ឮ"); + } + else if(k.KFCM(2,t,['ឧ','ិ'])){ + m=1; // Line 607 + k.KDC(2,t); + k.KO(-1,t,"ឱ"); + } + else if(k.KFCM(2,t,['ឧ','៌'])){ + m=1; // Line 608 + k.KDC(2,t); + k.KO(-1,t,"ឱ"); + } + else if(k.KFCM(2,t,['ឧ','៍'])){ + m=1; // Line 609 + k.KDC(2,t); + k.KO(-1,t,"ឱ"); + } + return r; + }; +} diff --git a/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.kmp b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.kmp new file mode 100644 index 0000000000..c3449523e3 Binary files /dev/null and b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.kmp differ diff --git a/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.kmx b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.kmx new file mode 100644 index 0000000000..d8495d187c Binary files /dev/null and b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/build/khmer_angkor.kmx differ diff --git a/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/khmer_angkor.kpj b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/khmer_angkor.kpj new file mode 100644 index 0000000000..4b239df20c --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/khmer_angkor.kpj @@ -0,0 +1,187 @@ + + + + $PROJECTPATH\build + True + True + False + keyboard + + + + id_f347675c33d2e6b1c705c787fad4941a + khmer_angkor.kmn + source\khmer_angkor.kmn + 1.3 + .kmn +
+ Khmer Angkor + © 2015-2022 SIL International + More than just a Khmer Unicode keyboard. +
+
+ + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + khmer_angkor.kps + source\khmer_angkor.kps + + .kps +
+ Khmer Angkor + © 2015-2022 SIL International +
+
+ + id_8a1efc7c4ab7cfece8aedd847679ca27 + khmer_angkor.ico + source\khmer_angkor.ico + + .ico + id_f347675c33d2e6b1c705c787fad4941a + + + id_8dc195db32d1fd0514de0ad51fff5df0 + khmer_angkor.js + source\..\build\khmer_angkor.js + + .js + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_10596632fcbf4138d24bcccf53e6ae01 + khmer_angkor.kvk + source\..\build\khmer_angkor.kvk + + .kvk + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_0a851f95ce553ecd62cbee6c32ced68f + khmer_angkor.kmx + source\..\build\khmer_angkor.kmx + + .kmx + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_d8b6eb05f4b7e2945c10e04c1f49e4c8 + keyboard_layout.png + source\welcome\keyboard_layout.png + + .png + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_724e5b4c63f10bc0abf7077f7c3172fc + welcome.htm + source\welcome\welcome.htm + + .htm + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_35857cb2b54f123612735ec948400082 + FONTLOG.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\FONTLOG.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_7e3afe5bb59b888b08b48cd5817d8de4 + Mondulkiri-B.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-B.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_b9734e80f86c69ea5ae4dfa9f0083d09 + Mondulkiri-BI.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-BI.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_25abe4d2b0abc03a5be5b666a8de776e + Mondulkiri-I.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-I.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_b766568498108eee46ed1601ff69c47d + Mondulkiri-R.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-R.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_84544d04133cab3dbfc86b91ad1a4e17 + OFL.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\OFL.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_0c33fbefd1c20f487b1bea2343b3bb2c + OFL-FAQ.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\OFL-FAQ.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_a59d89fca36a310147645fa2604e521b + KAK_Documentation_EN.pdf + source\welcome\KAK_Documentation_EN.pdf + + .pdf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_5643c4cd3933b3ada0b4af6579305ec4 + KAK_Documentation_KH.pdf + source\welcome\KAK_Documentation_KH.pdf + + .pdf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_acb0dd94c60e345d999670e999cbd159 + image002.png + source\welcome\image002.png + + .png + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_4edf70bc019f05b5ad39a2ea727ad547 + khmer_busra_kbd.ttf + source\..\..\..\shared\fonts\khmer\busrakbd\khmer_busra_kbd.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_bc823844e4399751e1867016801f7327 + splash.gif + source\splash.gif + + .gif + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + +
+
diff --git a/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/source/khmer_angkor.kps b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/source/khmer_angkor.kps new file mode 100644 index 0000000000..1278aa479c --- /dev/null +++ b/developer/src/kmc-keyboard-info/test/fixtures/multiple-email-addresses/source/khmer_angkor.kps @@ -0,0 +1,49 @@ + + + + 15.0.266.0 + 7.0 + + + ..\LICENSE.md + + + + Khmer Angkor + © 2015-2022 SIL International + Multiple Authors + + https://keyman.com/keyboards/khmer_angkor + Khmer Unicode keyboard layout based on the NiDA keyboard layout. Automatically corrects many common keying errors. + + + + ..\LICENSE.md + File LICENSE.md + 0 + .md + + + ..\build\khmer_angkor.kmx + Keyboard Khmer Angkor + 0 + .kmx + + + ..\build\khmer_angkor.js + Keyboard Khmer Angkor + 0 + .js + + + + + Khmer Angkor + khmer_angkor + 1.3 + + Central Khmer (Khmer, Cambodia) + + + + diff --git a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler-messages.ts b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler-messages.ts index 8a9c2652ba..2d205f8f47 100644 --- a/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler-messages.ts +++ b/developer/src/kmc-keyboard-info/test/test-keyboard-info-compiler-messages.ts @@ -56,7 +56,7 @@ describe('KeyboardInfoCompilerMessages', function () { assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_FileDoesNotExist).includes(KeymanFileTypes.Binary.WebKeyboard), KeymanFileTypes.Binary.WebKeyboard+' not found in the message'); }); - + // ERROR_FileDoesNotExist (.kmp fileSize) it('should generate ERROR_FileDoesNotExist error if .kmp file does not exist', async function() { @@ -141,7 +141,7 @@ describe('KeyboardInfoCompilerMessages', function () { assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_LicenseFileIsMissing).includes(licenseFilename), licenseFilename+' not found in the message'); }); - + // ERROR_LicenseFileIsDamaged (error on decode) it('should generate ERROR_LicenseFileIsDamaged error if license file throws error on decode', async function() { @@ -197,7 +197,7 @@ describe('KeyboardInfoCompilerMessages', function () { assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_LicenseIsNotValid).includes(licenseFilename), licenseFilename+' not found in the message'); }); - + // ERROR_CannotBuildWithoutKmpFile it('should generate ERROR_CannotBuildWithoutKmpFile error if .kmp file is not in sources', async function() { @@ -227,7 +227,7 @@ describe('KeyboardInfoCompilerMessages', function () { assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.ERROR_CannotBuildWithoutKmpFile), `ERROR_CannotBuildWithoutKmpFile not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); }); - + // ERROR_NoLicenseFound it('should generate ERROR_NoLicenseFound error if licence file is not in .kps options', async function() { @@ -257,7 +257,7 @@ describe('KeyboardInfoCompilerMessages', function () { assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.ERROR_NoLicenseFound), `ERROR_NoLicenseFound not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); }); - + // ERROR_FontFileMetaDataIsInvalid it('should generate ERROR_FontFileMetaDataIsInvalid error if font file meta data throws an error', async function() { @@ -285,7 +285,38 @@ describe('KeyboardInfoCompilerMessages', function () { `ERROR_FontFileMetaDataIsInvalid not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); assert.isTrue(nodeCompilerMessage(callbacks, KeyboardInfoCompilerMessages.ERROR_FontFileMetaDataIsInvalid).includes(kmpJsonData.files[0].name), kmpJsonData.files[0].name+' not found in the message'); - }); + }); + + // ERROR_InvalidAuthorEmail + + it('should generate ERROR_InvalidAuthorEmail error if multiple email addresses are listed in .kps', async function() { + const jsFilename = makePathToFixture('multiple-email-addresses', 'build', 'khmer_angkor.js'); + const kpsFilename = makePathToFixture('multiple-email-addresses', 'source', 'khmer_angkor.kps'); + const kmpFilename = makePathToFixture('multiple-email-addresses', 'build', 'khmer_angkor.kmp'); + + const sources = { + kmpFilename, + sourcePath: 'release/k/multiple-email-addresses', + kpsFilename, + jsFilename: jsFilename, + forPublishing: true, + }; + + const compiler = new KeyboardInfoCompiler(); + assert.isTrue(await compiler.init(callbacks, {sources})); + let result: KeyboardInfoCompilerResult = null; + try { + result = await compiler.run(kmpFilename, null); + } catch(e) { + callbacks.printMessages(); + throw e; + } + assert.isNull(result); + + assert.isTrue(callbacks.hasMessage(KeyboardInfoCompilerMessages.ERROR_InvalidAuthorEmail), + `ERROR_InvalidAuthorEmail not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); + }); + }); function nodeCompilerMessage(ncb: TestCompilerCallbacks, code: number): string { diff --git a/developer/src/kmc-kmn/package.json b/developer/src/kmc-kmn/package.json index db1794badd..98f2c6f22f 100644 --- a/developer/src/kmc-kmn/package.json +++ b/developer/src/kmc-kmn/package.json @@ -41,8 +41,7 @@ "chalk": "^2.4.2", "mocha": "^8.4.0", "sinon-chai": "^3.7.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "mocha": { "spec": "build/test/**/test-*.js", diff --git a/developer/src/kmc-ldml/package.json b/developer/src/kmc-ldml/package.json index dea5ffb5b2..ef1a17bd68 100644 --- a/developer/src/kmc-ldml/package.json +++ b/developer/src/kmc-ldml/package.json @@ -28,7 +28,6 @@ "@keymanapp/keyman-version": "*", "@keymanapp/kmc-kmn": "*", "@keymanapp/ldml-keyboard-constants": "*", - "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#7a188a1e26f8f36a175d95b67ffece8702363dfc", "semver": "^7.5.2" }, "devDependencies": { @@ -40,8 +39,7 @@ "c8": "^7.12.0", "chalk": "^2.4.2", "mocha": "^8.4.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "mocha": { "spec": "build/test/**/test-*.js", diff --git a/developer/src/kmc-model-info/package.json b/developer/src/kmc-model-info/package.json index fdc8082171..23297a07ba 100644 --- a/developer/src/kmc-model-info/package.json +++ b/developer/src/kmc-model-info/package.json @@ -40,8 +40,7 @@ "c8": "^7.12.0", "chalk": "^2.4.2", "mocha": "^8.4.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "mocha": { "spec": "build/test/**/test-*.js", diff --git a/developer/src/kmc-model-info/src/model-info-compiler.ts b/developer/src/kmc-model-info/src/model-info-compiler.ts index b6a4af3064..09e9fc45c8 100644 --- a/developer/src/kmc-model-info/src/model-info-compiler.ts +++ b/developer/src/kmc-model-info/src/model-info-compiler.ts @@ -7,7 +7,7 @@ import { minKeymanVersion } from "./min-keyman-version.js"; import { ModelInfoFile } from "./model-info-file.js"; import { CompilerCallbacks, CompilerOptions, KeymanCompiler, KeymanCompilerArtifact, KeymanCompilerArtifacts, KeymanCompilerResult, KmpJsonFile } from "@keymanapp/common-types"; import { ModelInfoCompilerMessages } from "./model-info-compiler-messages.js"; -import { KeymanUrls, validateMITLicense } from "@keymanapp/developer-utils"; +import { KeymanUrls, isValidEmail, validateMITLicense } from "@keymanapp/developer-utils"; /* c8 ignore start */ /** @@ -192,6 +192,11 @@ export class ModelInfoCompiler implements KeymanCompiler { return null; } + if(!isValidEmail(match[2])) { + this.callbacks.reportMessage(ModelInfoCompilerMessages.Error_InvalidAuthorEmail({email:author.url})); + return null; + } + model_info.authorEmail = match[2]; } diff --git a/developer/src/kmc-model/build.sh b/developer/src/kmc-model/build.sh index 36b9f74e3e..c549648b45 100755 --- a/developer/src/kmc-model/build.sh +++ b/developer/src/kmc-model/build.sh @@ -33,6 +33,7 @@ builder_parse "$@" #------------------------------------------------------------------------------------------------------------------- function do_build() { + tsc -b ./tools/tsconfig.json npm run build } diff --git a/developer/src/kmc-model/package.json b/developer/src/kmc-model/package.json index 831677a1ac..529e0aaf52 100644 --- a/developer/src/kmc-model/package.json +++ b/developer/src/kmc-model/package.json @@ -33,7 +33,7 @@ "@keymanapp/common-types": "*", "@keymanapp/keyman-version": "*", "@keymanapp/models-types": "*", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", @@ -43,8 +43,7 @@ "c8": "^7.12.0", "chalk": "^2.4.2", "esbuild": "^0.15.7", - "mocha": "^10.0.0", - "ts-node": "^10.9.1" + "mocha": "^10.0.0" }, "mocha": { "spec": "build/test/**/test-*.js", diff --git a/developer/src/kmc-model/tools/create-override-script-regexp.ts b/developer/src/kmc-model/tools/create-override-script-regexp.ts index 3860432058..38f6ade0a6 100644 --- a/developer/src/kmc-model/tools/create-override-script-regexp.ts +++ b/developer/src/kmc-model/tools/create-override-script-regexp.ts @@ -1,5 +1,3 @@ -#!/usr/bin/env npx ts-node - /** * Prints a JavaScript regular expression suitable for use in the * **overrideScriptDefaults** word breaker decorator. @@ -67,7 +65,7 @@ export const HAS_SOUTHEAST_ASIAN_LETTER = /[${characterClasses}]/;`); ////////////////////////////////// Helpers /////////////////////////////////// function* unicodeData() { - let unicodeDataFile = readFileSync(path.join(UCD_DIR, "UnicodeData.txt"), "UTF-8"); + let unicodeDataFile = readFileSync(path.join(UCD_DIR, "UnicodeData.txt"), "utf8"); for (let line of unicodeDataFile.split("\n")) { if (line.trim() == "") { continue; @@ -82,7 +80,7 @@ function* unicodeData() { } function* blocks() { - let blocksFile = readFileSync(path.join(UCD_DIR, "Blocks.txt"), "UTF-8"); + let blocksFile = readFileSync(path.join(UCD_DIR, "Blocks.txt"), "utf8"); for (let line of blocksFile.split("\n")) { if (line.trim() === "") { continue; diff --git a/developer/src/kmc-model/tools/tsconfig.json b/developer/src/kmc-model/tools/tsconfig.json new file mode 100644 index 0000000000..e01c780bc4 --- /dev/null +++ b/developer/src/kmc-model/tools/tsconfig.json @@ -0,0 +1,13 @@ +{ + "extends": "../tsconfig.json", + + "compilerOptions": { + "outDir": "../build/tools/", + "rootDir": ".", + "baseUrl": "../.", + "allowSyntheticDefaultImports": true, + }, + "include": [ + "./*.ts" + ] +} diff --git a/developer/src/kmc-package/package.json b/developer/src/kmc-package/package.json index fb88591fdc..22bc7b024d 100644 --- a/developer/src/kmc-package/package.json +++ b/developer/src/kmc-package/package.json @@ -31,8 +31,7 @@ "dependencies": { "@keymanapp/common-types": "*", "jszip": "^3.7.0", - "marked": "^7.0.0", - "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" + "marked": "^7.0.0" }, "devDependencies": { "@keymanapp/developer-test-helpers": "*", @@ -41,8 +40,7 @@ "c8": "^7.12.0", "chalk": "^2.4.2", "mocha": "^8.4.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "mocha": { "spec": "build/test/**/test-*.js", diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index ff2901c102..0d4740de9e 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -1,4 +1,4 @@ -import * as xml2js from 'xml2js'; +import { xml2js } from '@keymanapp/common-types'; import JSZip from 'jszip'; import KEYMAN_VERSION from "@keymanapp/keyman-version"; diff --git a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts index 1b286686ed..560f766d89 100644 --- a/developer/src/kmc-package/src/compiler/package-compiler-messages.ts +++ b/developer/src/kmc-package/src/compiler/package-compiler-messages.ts @@ -134,5 +134,9 @@ export class CompilerMessages { static ERROR_FileRecordIsMissingName = SevError | 0x001F; static Error_FileRecordIsMissingName = (o:{description:string}) => m(this.ERROR_FileRecordIsMissingName, `File record in the package with description '${o.description}' is missing a filename.`); + + static ERROR_InvalidAuthorEmail = SevError | 0x0020; + static Error_InvalidAuthorEmail = (o:{email:string}) => m(this.ERROR_InvalidAuthorEmail, + `Invalid author email: ${def(o.email)}`); } diff --git a/developer/src/kmc-package/src/compiler/package-validation.ts b/developer/src/kmc-package/src/compiler/package-validation.ts index ce919d4a64..41072516ba 100644 --- a/developer/src/kmc-package/src/compiler/package-validation.ts +++ b/developer/src/kmc-package/src/compiler/package-validation.ts @@ -1,6 +1,7 @@ import { KmpJsonFile, CompilerCallbacks, CompilerOptions, KeymanFileTypes } from '@keymanapp/common-types'; import { CompilerMessages } from './package-compiler-messages.js'; import { keymanEngineForWindowsFiles, keymanForWindowsInstallerFiles, keymanForWindowsRedistFiles } from './redist-files.js'; +import { isValidEmail } from '@keymanapp/developer-utils'; // The keyboard ID SHOULD adhere to this pattern: const KEYBOARD_ID_PATTERN_PACKAGE = /^[a-z_][a-z0-9_]*\.(kps|kmp)$/; @@ -208,6 +209,21 @@ export class PackageValidation { return false; } + if(file.info?.author?.url) { + // we strip the mailto: from the .kps file for the .model_info + const match = file.info.author.url.match(/^(mailto\:)?(.+)$/); + /* c8 ignore next 3 */ + if (match === null) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidAuthorEmail({email:file.info.author.url})); + return null; + } + if(!isValidEmail(match[2])) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidAuthorEmail({email:file.info.author.url})); + return null; + } + + } + return true; } } diff --git a/developer/src/kmc-package/test/fixtures/invalid/error_invalid_author_email.kps b/developer/src/kmc-package/test/fixtures/invalid/error_invalid_author_email.kps new file mode 100644 index 0000000000..8d48305286 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/error_invalid_author_email.kps @@ -0,0 +1,32 @@ + + + + 15.0.266.0 + 7.0 + + + + Invalid Email Address + © 2019 National Research Council Canada + Eddie Antonio Santos + 1.0 + + + + basic.kmx + Keyboard Basic + 0 + .kmx + + + + + Basic + basic + 1.0 + + Central Khmer (Khmer, Cambodia) + + + + diff --git a/developer/src/kmc-package/test/fixtures/invalid/error_invalid_author_email_multiple.kps b/developer/src/kmc-package/test/fixtures/invalid/error_invalid_author_email_multiple.kps new file mode 100644 index 0000000000..4e85402559 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/invalid/error_invalid_author_email_multiple.kps @@ -0,0 +1,32 @@ + + + + 15.0.266.0 + 7.0 + + + + Invalid Email Address + © 2019 National Research Council Canada + Eddie Antonio Santos + 1.0 + + + + basic.kmx + Keyboard Basic + 0 + .kmx + + + + + Basic + basic + 1.0 + + Central Khmer (Khmer, Cambodia) + + + + diff --git a/developer/src/kmc-package/test/test-messages.ts b/developer/src/kmc-package/test/test-messages.ts index 082576eff0..2f14921237 100644 --- a/developer/src/kmc-package/test/test-messages.ts +++ b/developer/src/kmc-package/test/test-messages.ts @@ -224,4 +224,16 @@ describe('CompilerMessages', function () { CompilerMessages.ERROR_InvalidPackageFile); }); + // ERROR_InvalidAuthorEmail + + it('should generate ERROR_InvalidAuthorEmail if author email address has multiple addresses', async function() { + await testForMessage(this, ['invalid', 'error_invalid_author_email_multiple.kps'], + CompilerMessages.ERROR_InvalidAuthorEmail); + }); + + it('should generate ERROR_InvalidAuthorEmail if author email address is formatted incorrectly', async function() { + await testForMessage(this, ['invalid', 'error_invalid_author_email.kps'], + CompilerMessages.ERROR_InvalidAuthorEmail); + }); + }); diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json index d782e0f7d9..bcaa008e00 100644 --- a/developer/src/kmc/package.json +++ b/developer/src/kmc/package.json @@ -61,8 +61,7 @@ "c8": "^7.12.0", "esbuild": "^0.15.8", "mocha": "^8.4.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" + "typescript": "^5.4.5" }, "mocha": { "spec": "build/test/**/test-*.js", diff --git a/developer/src/kmcmplib/meson.build b/developer/src/kmcmplib/meson.build index c996981f47..d494e884d9 100644 --- a/developer/src/kmcmplib/meson.build +++ b/developer/src/kmcmplib/meson.build @@ -5,7 +5,7 @@ # project('kmcmplib', 'cpp', 'c', - version: run_command(find_program('getversion.bat', 'getversion.sh')).stdout().strip(), + version: run_command(find_program('getversion.bat', 'getversion.sh'), check: true).stdout().strip(), license: 'MIT', default_options : ['buildtype=release', 'cpp_std=c++14', diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index a7a603c7b3..8d0f11af8f 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -1208,7 +1208,11 @@ int GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store) { } KMX_BOOL IsValidKeyboardVersion(KMX_WCHAR *dpString) { // I4140 - /* version format \d+(\.\d+)* e.g. 9.0.3, 1.0, 1.2.3.4, 6.2.1.4.6.4, blank is not allowed */ + /** + version format: /^\d+(\.\d+)*$/ + e.g. 9.0.3, 1.0, 1.2.3.4, 6.2.1.4.6.4, 11.22.3 are all ok; + empty string is not permitted; whitespace is not permitted + */ do { if (!iswdigit(*dpString)) { diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index e3e4efcd48..ce9c27b488 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -54,6 +54,10 @@ if cpp_compiler.get_id() == 'emscripten' # emscripten < 3.1.44 does not include `wasmExports` links += ['-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] endif + + # For Google Test + add_global_arguments('-pthread', language: [ 'cpp', 'c' ] ) + endif icu = subproject('icu-for-uset', default_options: [ 'default_library=static', 'cpp_std=c++17', 'warning_level=0', 'werror=false']) @@ -101,7 +105,7 @@ if cpp_compiler.get_id() == 'emscripten' cpp_args: defns, include_directories: inc, link_args: links + lib_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: icuuc_dep) if get_option('buildtype') == 'release' @@ -116,3 +120,7 @@ if cpp_compiler.get_id() == 'emscripten' endif endif + +gtest = subproject('gtest') +gtest_dep = gtest.get_variable('gtest_main_dep') +gmock_dep = gtest.get_variable('gmock_dep') diff --git a/developer/src/kmcmplib/subprojects/.gitignore b/developer/src/kmcmplib/subprojects/.gitignore index 99479aa952..0c5134859c 100644 --- a/developer/src/kmcmplib/subprojects/.gitignore +++ b/developer/src/kmcmplib/subprojects/.gitignore @@ -2,3 +2,4 @@ /*.zip /*.tgz /packagecache +/googletest-* diff --git a/developer/src/kmcmplib/subprojects/gtest.wrap b/developer/src/kmcmplib/subprojects/gtest.wrap new file mode 100644 index 0000000000..adb8a9a6d9 --- /dev/null +++ b/developer/src/kmcmplib/subprojects/gtest.wrap @@ -0,0 +1,16 @@ +[wrap-file] +directory = googletest-1.14.0 +source_url = https://github.com/google/googletest/archive/refs/tags/v1.14.0.tar.gz +source_filename = gtest-1.14.0.tar.gz +source_hash = 8ad598c73ad796e0d8280b082cebd82a630d73e73cd3c70057938a6501bba5d7 +patch_filename = gtest_1.14.0-2_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/gtest_1.14.0-2/get_patch +patch_hash = 4ec7f767364386a99f7b2d61678287a73ad6ba0f9998be43b51794c464a63732 +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/gtest_1.14.0-2/gtest-1.14.0.tar.gz +wrapdb_version = 1.14.0-2 + +[provide] +gtest = gtest_dep +gtest_main = gtest_main_dep +gmock = gmock_dep +gmock_main = gmock_main_dep diff --git a/developer/src/kmcmplib/tests/gtest-compiler-test.cpp b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp new file mode 100644 index 0000000000..61d6c03aa9 --- /dev/null +++ b/developer/src/kmcmplib/tests/gtest-compiler-test.cpp @@ -0,0 +1,460 @@ +#include +#include "../include/kmcompx.h" +#include "../include/kmcmplibapi.h" +#include "../src/kmx_u16.h" +#include "../src/compfile.h" +#include "../src/CompMsg.h" +#include "../../common/include/kmn_compiler_errors.h" +#include "../../../../common/include/km_types.h" +#include "../../../../common/include/kmx_file.h" + +PKMX_WCHAR strtowstr(PKMX_STR in); +PKMX_STR wstrtostr(PKMX_WCHAR in); +KMX_BOOL AddCompileError(KMX_DWORD msg); +KMX_DWORD ProcessBeginLine(PFILE_KEYBOARD fk, PKMX_WCHAR p); +KMX_DWORD ValidateMatchNomatchOutput(PKMX_WCHAR p); +KMX_BOOL IsValidKeyboardVersion(KMX_WCHAR *dpString); +KMX_DWORD GetXStringImpl(PKMX_WCHAR tstr, PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_WCHAR const * token, + PKMX_WCHAR output, int max, int offset, PKMX_WCHAR *newp, int isUnicode +); +KMX_DWORD GetRHS(PFILE_KEYBOARD fk, PKMX_WCHAR p, PKMX_WCHAR buf, int bufsize, int offset, int IsUnicode); +bool hasPreamble(std::u16string result); + +extern kmcmp_CompilerMessageProc msgproc; + +namespace kmcmp { + extern int nErrors; + extern int ErrChr; + extern int BeginLine[4]; + KMX_BOOL AddCompileWarning(char* buf); +} + +#define ERR_EXTRA_LIB_LEN 256 +extern char ErrExtraLIB[ERR_EXTRA_LIB_LEN]; + +class CompilerTest : public testing::Test { + protected: + FILE_KEYBOARD fileKeyboard; + + CompilerTest() {} + ~CompilerTest() override {} + void SetUp() override { + initGlobals(); + initFileKeyboard(fileKeyboard); + } + void TearDown() override { + deleteFileKeyboard(fileKeyboard); + } + + void initGlobals() { + msgproc = NULL; + szText_stub[0] = '\0'; + kmcmp::nErrors = 0; + kmcmp::ErrChr = 0; + ErrExtraLIB[0] = '\0'; + kmcmp::BeginLine[BEGIN_ANSI] = -1; + kmcmp::BeginLine[BEGIN_UNICODE] = -1; + kmcmp::BeginLine[BEGIN_NEWCONTEXT] = -1; + kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = -1; + } + + void initFileKeyboard(FILE_KEYBOARD &fk) { + fk.KeyboardID = 0; + fk.version = VERSION_90; + fk.dpStoreArray = nullptr; + fk.dpGroupArray = nullptr; + fk.cxStoreArray = 0; + fk.cxGroupArray = 0; + fk.StartGroup[0] = 0; + fk.StartGroup[1] = 0; + fk.dwHotKey = 0; + fk.szName[0] = u'\0'; + fk.szLanguageName[0] = u'\0'; + fk.szCopyright[0] = u'\0'; + fk.szMessage[0] = u'\0'; + fk.lpBitmap = nullptr; + fk.dwBitmapSize = 0; + fk.dwFlags = 0; + fk.currentGroup = 0; + fk.currentStore = 0; + fk.cxDeadKeyArray = 0; + fk.dpDeadKeyArray = nullptr; + fk.cxVKDictionary = 0; + fk.dpVKDictionary = nullptr; + fk.extra = nullptr; + } + + void deleteFileKeyboard(FILE_KEYBOARD &fk) { + if (fk.dpStoreArray) { delete[] fk.dpStoreArray; } + if (fk.dpGroupArray) { delete[] fk.dpGroupArray; } + if (fk.lpBitmap) { delete fk.lpBitmap; } + if (fk.dpDeadKeyArray) { delete[] fk.dpDeadKeyArray; } + if (fk.dpVKDictionary) { delete fk.dpVKDictionary; } + if (fk.extra) { delete fk.extra; } + } + + public: + static KMX_CHAR szText_stub[]; + + static int msgproc_true_stub(int line, uint32_t dwMsgCode, const char* szText, void* context) { + strcpy(szText_stub, szText); + return 1; + }; + + static int msgproc_false_stub(int line, uint32_t dwMsgCode, const char* szText, void* context) { + strcpy(szText_stub, szText); + return 0; + }; +}; + +#define COMPILE_ERROR_MAX_LEN (SZMAX_ERRORTEXT + 1 + 280) +KMX_CHAR CompilerTest::szText_stub[COMPILE_ERROR_MAX_LEN]; + +TEST_F(CompilerTest, strtowstr_test) { + EXPECT_EQ(0, u16cmp(u"hello", strtowstr((PKMX_STR)"hello"))); + EXPECT_EQ(0, u16cmp(u"", strtowstr((PKMX_STR)""))); +}; + +TEST_F(CompilerTest, wstrtostr_test) { + EXPECT_EQ(0, strcmp("hello", wstrtostr((PKMX_WCHAR)u"hello"))); + EXPECT_EQ(0, strcmp("", wstrtostr((PKMX_WCHAR)u""))); +}; + +TEST_F(CompilerTest, AddCompileWarning_test) { + msgproc = msgproc_false_stub; + const char *const WARNING_TEXT = "warning"; + EXPECT_EQ(0, kmcmp::nErrors); + EXPECT_FALSE(kmcmp::AddCompileWarning((PKMX_CHAR)WARNING_TEXT)); + EXPECT_EQ(0, strcmp(WARNING_TEXT, szText_stub)); + EXPECT_EQ(0, kmcmp::nErrors); +}; + +TEST_F(CompilerTest, AddCompileError_test) { + msgproc = msgproc_true_stub; + kmcmp::ErrChr = 0; + ErrExtraLIB[0] = '\0'; + KMX_CHAR expected[COMPILE_ERROR_MAX_LEN]; + + // CERR_FATAL + EXPECT_EQ(0, kmcmp::nErrors); + EXPECT_EQ(CERR_FATAL, CERR_CannotCreateTempfile & CERR_FATAL); + EXPECT_TRUE(AddCompileError(CERR_CannotCreateTempfile)); + EXPECT_EQ(0, strcmp(GetCompilerErrorString(CERR_CannotCreateTempfile), szText_stub)); + EXPECT_EQ(1, kmcmp::nErrors); + + // CERR_ERROR + EXPECT_EQ(CERR_ERROR, CERR_InvalidLayoutLine & CERR_ERROR); + EXPECT_FALSE(AddCompileError(CERR_InvalidLayoutLine)); + EXPECT_EQ(0, strcmp(GetCompilerErrorString(CERR_InvalidLayoutLine), szText_stub)); + EXPECT_EQ(2, kmcmp::nErrors); + + // Unknown + const KMX_DWORD UNKNOWN_ERROR = 0x00004FFF; // top of range ERROR + EXPECT_EQ(CERR_ERROR, UNKNOWN_ERROR & CERR_ERROR); + EXPECT_FALSE(AddCompileError(UNKNOWN_ERROR)); + sprintf(expected, "Unknown error %x", UNKNOWN_ERROR); + EXPECT_EQ(0, strcmp(expected, szText_stub)); + EXPECT_EQ(3, kmcmp::nErrors); + + // ErrChr + const int ERROR_CHAR_INDEX = 42; + kmcmp::ErrChr = ERROR_CHAR_INDEX ; + EXPECT_EQ(CERR_ERROR, CERR_InvalidLayoutLine & CERR_ERROR); + EXPECT_FALSE(AddCompileError(CERR_InvalidLayoutLine)); + sprintf(expected, "%s character offset: %d", GetCompilerErrorString(CERR_InvalidLayoutLine), ERROR_CHAR_INDEX); + EXPECT_EQ(0, strcmp(expected, szText_stub)); + kmcmp::ErrChr = 0; + EXPECT_EQ(4, kmcmp::nErrors); + + // ErrExtraLIB + const char *const EXTRA_LIB_TEXT = " extra lib"; + strcpy(ErrExtraLIB, EXTRA_LIB_TEXT); + EXPECT_EQ(CERR_ERROR, CERR_InvalidLayoutLine & CERR_ERROR); + EXPECT_FALSE(AddCompileError(CERR_InvalidLayoutLine)); + sprintf(expected, "%s%s", GetCompilerErrorString(CERR_InvalidLayoutLine), EXTRA_LIB_TEXT); + EXPECT_EQ(0, strcmp(expected, szText_stub)); + ErrExtraLIB[0] = '\0'; + EXPECT_EQ(5, kmcmp::nErrors); + + // msgproc returns FALSE + msgproc = msgproc_false_stub; + EXPECT_EQ(CERR_ERROR, CERR_InvalidLayoutLine & CERR_ERROR); + EXPECT_TRUE(AddCompileError(CERR_InvalidLayoutLine)); + EXPECT_EQ(0, strcmp(GetCompilerErrorString(CERR_InvalidLayoutLine), szText_stub)); + EXPECT_EQ(6, kmcmp::nErrors); +}; + +TEST_F(CompilerTest, ProcessBeginLine_test) { + KMX_WCHAR str[LINESIZE]; + + // CERR_NoTokensFound + str[0] = '\0'; + EXPECT_EQ(CERR_NoTokensFound, ProcessBeginLine(&fileKeyboard, str)); + + // CERR_InvalidToken + u16cpy(str, u"abc >"); + EXPECT_EQ(CERR_InvalidToken, ProcessBeginLine(&fileKeyboard, str)); + + // CERR_RepeatedBegin, BEGIN_UNICODE + kmcmp::BeginLine[BEGIN_UNICODE] = 0; // not -1 + u16cpy(str, u" unicode>"); + EXPECT_EQ(CERR_RepeatedBegin, ProcessBeginLine(&fileKeyboard, str)); + kmcmp::BeginLine[BEGIN_UNICODE] = -1; + + // CERR_RepeatedBegin, BEGIN_ANSI + kmcmp::BeginLine[BEGIN_ANSI] = 0; // not -1 + u16cpy(str, u" ansi>"); + EXPECT_EQ(CERR_RepeatedBegin, ProcessBeginLine(&fileKeyboard, str)); + kmcmp::BeginLine[BEGIN_ANSI] = -1; + + // CERR_RepeatedBegin, BEGIN_NEWCONTEXT + kmcmp::BeginLine[BEGIN_NEWCONTEXT] = 0; // not -1 + u16cpy(str, u" newContext>"); + EXPECT_EQ(CERR_RepeatedBegin, ProcessBeginLine(&fileKeyboard, str)); + kmcmp::BeginLine[BEGIN_NEWCONTEXT] = -1; + + // CERR_RepeatedBegin, BEGIN_POSTKEYSTROKE + kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = 0; // not -1 + u16cpy(str, u" postKeystroke>"); + EXPECT_EQ(CERR_RepeatedBegin, ProcessBeginLine(&fileKeyboard, str)); + kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = -1; +}; + +TEST_F(CompilerTest, ValidateMatchNomatchOutput_test) { + EXPECT_EQ(CERR_None, ValidateMatchNomatchOutput(NULL)); + EXPECT_EQ(CERR_None, ValidateMatchNomatchOutput((PKMX_WCHAR)u"")); + const KMX_WCHAR context[] = { 'a', 'b', 'c', UC_SENTINEL, CODE_CONTEXT, 'd', 'e', 'f' }; + EXPECT_EQ(CERR_ContextAndIndexInvalidInMatchNomatch, ValidateMatchNomatchOutput((PKMX_WCHAR)context)); + const KMX_WCHAR contextex[] = { 'a', 'b', 'c', UC_SENTINEL, CODE_CONTEXTEX, 'd', 'e', 'f' }; + EXPECT_EQ(CERR_ContextAndIndexInvalidInMatchNomatch, ValidateMatchNomatchOutput((PKMX_WCHAR)contextex)); + const KMX_WCHAR index[] = { 'a', 'b', 'c', UC_SENTINEL, CODE_INDEX, 'd', 'e', 'f' }; + EXPECT_EQ(CERR_ContextAndIndexInvalidInMatchNomatch, ValidateMatchNomatchOutput((PKMX_WCHAR)index)); + const KMX_WCHAR sentinel[] = { 'a', 'b', 'c', UC_SENTINEL, 'd', 'e', 'f' }; + EXPECT_EQ(CERR_None, ValidateMatchNomatchOutput((PKMX_WCHAR)sentinel)); +}; + +// KMX_DWORD ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str) +// KMX_DWORD ProcessGroupLine(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// int kmcmp::cmpkeys(const void *key, const void *elem) +// KMX_DWORD ProcessGroupFinish(PFILE_KEYBOARD fk) +// KMX_DWORD ProcessStoreLine(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// bool resizeStoreArray(PFILE_KEYBOARD fk) +// bool resizeKeyArray(PFILE_GROUP gp, int increment) +// KMX_DWORD AddStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, const KMX_WCHAR * str, KMX_DWORD *dwStoreID) +// KMX_DWORD AddDebugStore(PFILE_KEYBOARD fk, KMX_WCHAR const * str) +// KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE sp) +// int GetCompileTargetsFromTargetsStore(const KMX_WCHAR* store) + +TEST_F(CompilerTest, IsValidKeyboardVersion_test) { + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u" ")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"\t")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u" 1.1")); + EXPECT_TRUE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.1")); + EXPECT_TRUE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.0")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.")); + EXPECT_TRUE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.2.3")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"a")); + EXPECT_FALSE(IsValidKeyboardVersion((KMX_WCHAR *)u"1.a")); +}; + +// KMX_DWORD kmcmp::AddCompilerVersionStore(PFILE_KEYBOARD fk) +// KMX_DWORD CheckStatementOffsets(PFILE_KEYBOARD fk, PFILE_GROUP gp, PKMX_WCHAR context, PKMX_WCHAR output, PKMX_WCHAR key) +// KMX_BOOL CheckContextStatementPositions(PKMX_WCHAR context) +// KMX_DWORD CheckUseStatementsInOutput(PKMX_WCHAR output) +// KMX_DWORD CheckVirtualKeysInOutput(PKMX_WCHAR output) +// KMX_DWORD InjectContextToReadonlyOutput(PKMX_WCHAR pklOut) +// KMX_DWORD CheckOutputIsReadonly(const PFILE_KEYBOARD fk, const PKMX_WCHAR output) +// KMX_DWORD ProcessKeyLine(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_BOOL IsUnicode) +// KMX_DWORD ProcessKeyLineImpl(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_BOOL IsUnicode, PKMX_WCHAR pklIn, PKMX_WCHAR pklKey, PKMX_WCHAR pklOut) +// KMX_DWORD ExpandKp_ReplaceIndex(PFILE_KEYBOARD fk, PFILE_KEY k, KMX_DWORD keyIndex, int nAnyIndex) +// KMX_DWORD ExpandKp(PFILE_KEYBOARD fk, PFILE_KEY kpp, KMX_DWORD storeIndex) +// PKMX_WCHAR GetDelimitedString(PKMX_WCHAR *p, KMX_WCHAR const * Delimiters, KMX_WORD Flags) +// LinePrefixType GetLinePrefixType(PKMX_WCHAR *p) +// int LineTokenType(PKMX_WCHAR *str) +// KMX_BOOL StrValidChrs(PKMX_WCHAR q, KMX_WCHAR const * chrs) +// KMX_DWORD GetXString(PFILE_KEYBOARD fk, PKMX_WCHAR str, KMX_WCHAR const * token, +// PKMX_WCHAR output, int max, int offset, PKMX_WCHAR *newp, int /*isVKey*/, int isUnicode +// ) + +TEST_F(CompilerTest, GetXStringImpl_test) { + KMX_WCHAR tstr[128]; + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR output[GLOBAL_BUFSIZE]; + PKMX_WCHAR newp = NULL; + + // CERR_BufferOverflow, max=0 + EXPECT_EQ(CERR_BufferOverflow, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 0, 0, &newp, FALSE)); + + // CERR_None, no token + str[0] = '\0'; + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // CERR_NoTokensFound, empty + u16cpy(str, u""); + EXPECT_EQ(CERR_NoTokensFound, GetXStringImpl(tstr, &fileKeyboard, str, u"c", output, 80, 0, &newp, FALSE)); + + // CERR_NoTokensFound, whitespace + u16cpy(str, u" "); + EXPECT_EQ(CERR_NoTokensFound, GetXStringImpl(tstr, &fileKeyboard, str, u"c", output, 80, 0, &newp, FALSE)); +} + +// tests strings starting with 'x' or 'd' +TEST_F(CompilerTest, GetXStringImpl_type_xd_test) { + KMX_WCHAR tstr[128]; + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR output[GLOBAL_BUFSIZE]; + PKMX_WCHAR newp = NULL; + + // hex 32-bit + u16cpy(str, u"x10330"); // Gothic A + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + const KMX_WCHAR tstr_GothicA[] = { 0xD800, 0xDF30, 0 }; // see UTF32ToUTF16 + EXPECT_EQ(0, u16cmp(tstr_GothicA, tstr)); + + // decimal 8-bit + u16cpy(str, u"d18"); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(u"\u0012", tstr)); + + // hex capital 8-bit + u16cpy(str, u"X12"); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(u"\u0012", tstr)); + + // hex 32-bit, CERR_InvalidCharacter + u16cpy(str, u"x110000"); + EXPECT_EQ(CERR_InvalidCharacter, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // dk, valid + u16cpy(str, u"dk(A)"); + EXPECT_EQ(0, (int)fileKeyboard.cxDeadKeyArray); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + const KMX_WCHAR tstr_dk_valid[] = { UC_SENTINEL, CODE_DEADKEY, 1, 0 }; // setup deadkeys + EXPECT_EQ(0, u16cmp(tstr_dk_valid, tstr)); + fileKeyboard.cxDeadKeyArray = 0; + + // deadkey, valid + u16cpy(str, u"deadkey(A)"); + EXPECT_EQ(0, (int)fileKeyboard.cxDeadKeyArray); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + const KMX_WCHAR tstr_deadkey_valid[] = { UC_SENTINEL, CODE_DEADKEY, 1, 0 }; // setup deadkeys + EXPECT_EQ(0, u16cmp(tstr_deadkey_valid, tstr)); + fileKeyboard.cxDeadKeyArray = 0; + + // dk, CERR_InvalidDeadkey, bad character + u16cpy(str, u"dk(%)"); + EXPECT_EQ(CERR_InvalidDeadkey, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // dk, CERR_InvalidDeadkey, no close delimiter => NULL + u16cpy(str, u"dk("); + EXPECT_EQ(CERR_InvalidDeadkey, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // dk, CERR_InvalidDeadkey, empty delimiters => empty string + u16cpy(str, u"dk()"); + EXPECT_EQ(CERR_InvalidDeadkey, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); +} + +// tests strings starting with double quote +TEST_F(CompilerTest, GetXStringImpl_type_double_quote_test) { + KMX_WCHAR tstr[128]; + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR output[GLOBAL_BUFSIZE]; + PKMX_WCHAR newp = NULL; + + // valid + u16cpy(str, u"\"abc\""); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(u"abc", tstr)); + + // CERR_UnterminatedString + u16cpy(str, u"\"abc"); + EXPECT_EQ(CERR_UnterminatedString, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // CERR_ExtendedStringTooLong + u16cpy(str, u"\"abc\""); + EXPECT_EQ(CERR_ExtendedStringTooLong, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 2, 0, &newp, FALSE)); // max reduced to force error + + // CERR_StringInVirtualKeySection *** TODO *** +} + +// tests strings starting with single quote +TEST_F(CompilerTest, GetXStringImpl_type_single_quote_test) { + KMX_WCHAR tstr[128]; + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR output[GLOBAL_BUFSIZE]; + PKMX_WCHAR newp = NULL; + + // valid + u16cpy(str, u"\'abc\'"); + EXPECT_EQ(CERR_None, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + EXPECT_EQ(0, u16cmp(u"abc", tstr)); + + // CERR_UnterminatedString + u16cpy(str, u"\'abc"); + EXPECT_EQ(CERR_UnterminatedString, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 80, 0, &newp, FALSE)); + + // CERR_ExtendedStringTooLong + u16cpy(str, u"\'abc\'"); + EXPECT_EQ(CERR_ExtendedStringTooLong, GetXStringImpl(tstr, &fileKeyboard, str, u"", output, 2, 0, &newp, FALSE)); // max reduced to force error + + // CERR_StringInVirtualKeySection *** TODO *** +} + +// KMX_DWORD process_baselayout(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_platform(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_if_synonym(KMX_DWORD dwSystemID, PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_if(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_reset(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_expansion(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx, int max) +// KMX_DWORD process_set_synonym(KMX_DWORD dwSystemID, PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_set(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// KMX_DWORD process_save(PFILE_KEYBOARD fk, PKMX_WCHAR q, PKMX_WCHAR tstr, int *mx) +// int xatoi(PKMX_WCHAR *p) +// int GetGroupNum(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// KMX_DWORD ProcessEthnologueStore(PKMX_WCHAR p) +// KMX_DWORD ProcessHotKey(PKMX_WCHAR p, KMX_DWORD *hk) +// void SetChecksum(PKMX_BYTE buf, PKMX_DWORD CheckSum, KMX_DWORD sz) +// KMX_BOOL kmcmp::CheckStoreUsage(PFILE_KEYBOARD fk, int storeIndex, KMX_BOOL fIsStore, KMX_BOOL fIsOption, KMX_BOOL fIsCall) +// KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataSize) +// KMX_DWORD ReadLine(KMX_BYTE* infile, int sz, int& offset, PKMX_WCHAR wstr, KMX_BOOL PreProcess) + +TEST_F(CompilerTest, GetRHS_test) { + KMX_WCHAR str[LINESIZE]; + KMX_WCHAR tstr[128]; + + // CERR_NoTokensFound, empty string + str[0] = '\0'; + EXPECT_EQ(CERR_NoTokensFound, GetRHS(&fileKeyboard, str, tstr, 80, 0, FALSE)); + + // CERR_NoTokensFound, no '>' + u16cpy(str, u"abc"); + EXPECT_EQ(CERR_NoTokensFound, GetRHS(&fileKeyboard, str, tstr, 80, 0, FALSE)); + + // CERR_None + u16cpy(str, u"> nul c\n"); + EXPECT_EQ(CERR_None, GetRHS(&fileKeyboard, str, tstr, 80, 0, FALSE)); +} + +// void safe_wcsncpy(PKMX_WCHAR out, PKMX_WCHAR in, int cbMax) +// KMX_BOOL IsSameToken(PKMX_WCHAR *p, KMX_WCHAR const * token) +// static bool endsWith(const std::string& str, const std::string& suffix) +// KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD FileSize, PKMX_BYTE *Buf) +// int atoiW(PKMX_WCHAR p) +// KMX_DWORD kmcmp::CheckUTF16(int n) +// KMX_DWORD kmcmp::UTF32ToUTF16(int n, int *n1, int *n2) +// KMX_DWORD BuildVKDictionary(PFILE_KEYBOARD fk) +// int GetVKCode(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// int GetDeadKey(PFILE_KEYBOARD fk, PKMX_WCHAR p) +// void kmcmp::RecordDeadkeyNames(PFILE_KEYBOARD fk) +// KMX_BOOL kmcmp::IsValidCallStore(PFILE_STORE fs) + +TEST_F(CompilerTest, hasPreamble_test) { + EXPECT_FALSE(hasPreamble(u"")); + EXPECT_FALSE(hasPreamble(u"\uFEFE")); // not \uFEFF + EXPECT_TRUE(hasPreamble(u"\uFEFF")); + EXPECT_FALSE(hasPreamble(u"a\uFEFF")); +} + +// bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) +// PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, KMX_DWORD dwSystemID) diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index 1ae32590e2..f7e029917e 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -20,7 +20,7 @@ kmcompxtest = executable('kmcompxtest', ['kmcompxtest.cpp','util_filesystem.cpp' include_directories: inc, name_suffix: name_suffix, link_args: links + tests_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: icuuc_dep, ) @@ -129,7 +129,7 @@ if get_option('full_test') endif -common_test_files = [ meson.source_root() / '../../../common/include/test_color.cpp' ] +common_test_files = [ meson.global_source_root() / '../../../common/include/test_color.cpp' ] # Test the API endpoints @@ -138,7 +138,7 @@ apitest = executable('api-test', ['api-test.cpp','util_filesystem.cpp','util_cal include_directories: inc, name_suffix: name_suffix, link_args: links + tests_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: icuuc_dep ) @@ -149,8 +149,21 @@ usetapitest = executable('uset-api-test', 'uset-api-test.cpp', common_test_files include_directories: inc, name_suffix: name_suffix, link_args: links + tests_links, - objects: lib.extract_all_objects(), + objects: lib.extract_all_objects(recursive: false), dependencies: icuuc_dep, ) test('uset-api-test', usetapitest) + +# Google Test + +gtestcompilertest = executable('gtest-compiler-test', 'gtest-compiler-test.cpp', + cpp_args: defns + flags, + include_directories: inc, + name_suffix: name_suffix, + link_args: links + tests_links, + objects: lib.extract_all_objects(), + dependencies: [ icuuc_dep, gtest_dep, gmock_dep ], + ) + +test('gtest-compiler-test', gtestcompilertest) diff --git a/developer/src/kmconvert/Keyman.Developer.System.ImportWindowsKeyboard.pas b/developer/src/kmconvert/Keyman.Developer.System.ImportWindowsKeyboard.pas index fb0e22541c..f5384903d9 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.ImportWindowsKeyboard.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.ImportWindowsKeyboard.pas @@ -80,12 +80,13 @@ uses BCP47Tag, Keyman.Developer.System.ImportKeyboardDLL, Keyman.Developer.System.GenerateKeyboardIcon, - Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter, + Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter, Keyman.System.KeyboardUtils, KeymanVersion, KeyboardParser, kmxfileconsts, RegistryKeys, + TempFileManager, utilfiletypes; { TImportWindowsKeyboard } @@ -206,6 +207,8 @@ end; function TImportWindowsKeyboard.Execute: Boolean; var FTemplate: TKeyboardProjectTemplate; + DestinationOSKTempFile: TTempFile; + DestinationOSKFilename: string; begin if not ForceDirectories(FDestinationPath) then Exit(Fail('The destination path '+FDestinationPath+' could not be created.')); @@ -216,6 +219,7 @@ begin // Create a new folder in destination path + DestinationOSKTempFile := nil; FTemplate := TKeyboardProjectTemplate.Create(FDestinationPath, Format(FKeyboardIDTemplate, [FBaseKeyboardID]), FTargets); try // @@ -251,27 +255,43 @@ begin FProjectFilename := FTemplate.ProjectFilename; + if FTemplate.OSKFilename = '' then + begin + // We need an OSK file in order to generate the touch layout. OSKFilename + // will be empty if the user selects a touch-only target. So we'll use a + // temp file in this scenario. + DestinationOSKTempFile := TTempFileManager.Get(Ext_VisualKeyboardSource); + DestinationOSKFilename := DestinationOSKTempFile.Name; + end + else + DestinationOSKFilename := FTemplate.OSKFilename; + + // Run importkeyboard into destination file; this replaces the keyboard template // file that has been generated - if not ImportKeyboard(FTemplate.KeyboardFilename, FTemplate.OSKFilename) then + if not ImportKeyboard(FTemplate.KeyboardFilename, DestinationOSKFilename) then Exit(Fail('Unable to run importkeyboard on '+FSourceKLID)); // Replace .ico with a new one based on the language id // TODO: this goes in the keyboard project template generation I think - if not GenerateIcon(FTemplate.IconFilename) then - Exit(Fail('Unable to generate an icon for '+FTemplate.KeyboardFilename)); + if FTemplate.IconFilename <> '' then + if not GenerateIcon(FTemplate.IconFilename) then + Exit(Fail('Unable to generate an icon for '+FTemplate.KeyboardFilename)); // Load the source .kmn and add bitmap, copyright, visualkeyboard, touch layout fields + // note, we use FTemplate.OSKFilename here deliberately, because we only + // generate OSK-related fields if they are in use InjectSystemStores(FTemplate.KeyboardFilename, FTemplate.OSKFilename, FTemplate.IconFilename, FTemplate.TouchLayoutFilename); if FTemplate.TouchLayoutFilename <> '' then begin // Take the generated OSK and convert it into a default touch layout - if not ConvertOSKToTouchLayout(FTemplate.OSKFilename, FTemplate.TouchLayoutFilename) then + if not ConvertOSKToTouchLayout(DestinationOSKFilename, FTemplate.TouchLayoutFilename) then Exit(Fail('Unable to create a default touch layout based on the OSK for '+FTemplate.KeyboardFilename)); end; finally FreeAndNil(FTemplate); + FreeAndNil(DestinationOSKTempFile); end; Result := True; @@ -302,8 +322,10 @@ begin try kp.FileName := KeyboardFilename; kp.LoadFromFile(KeyboardFilename); - kp.Features.Add(kfIcon); - kp.Features.Add(kfOSK); + if IconFilename <> '' then + kp.Features.Add(kfIcon); + if OSKFilename <> '' then + kp.Features.Add(kfOSK); if (TouchKeymanTargets+[ktAny]) * FTargets <> [] then kp.Features.Add(kfTouchLayout); // TODO: Are these file settings actually doing anything? Or is it controlled @@ -311,8 +333,10 @@ begin // if we change filenames for any reason in the future kp.SetSystemStoreValue(ssName, Format(FNameTemplate, [FBaseName])); kp.SetSystemStoreValue(ssVersion, SKeymanKeyboardVersion); - kp.SetSystemStoreValue(ssVisualKeyboard, ExtractFileName(OSKFilename)); - kp.SetSystemStoreValue(ssBitmap, ExtractFilename(IconFilename)); + if OSKFilename <> '' then + kp.SetSystemStoreValue(ssVisualKeyboard, ExtractFileName(OSKFilename)); + if IconFilename <> '' then + kp.SetSystemStoreValue(ssBitmap, ExtractFilename(IconFilename)); kp.SetSystemStoreValue(ssLayoutFile, ExtractFileName(TouchLayoutFilename)); kp.SetSystemStoreValue(ssTargets, KeymanTargetsToString(FTargets)); kp.SetSystemStoreValue(ssCopyright, FCopyright); @@ -383,11 +407,11 @@ end; function TImportWindowsKeyboard.ConvertOSKToTouchLayout(const OSKFilename, TouchLayoutFilename: string): Boolean; var - converter: TTouchLayoutToVisualKeyboardConverter; + converter: TVisualKeyboardToTouchLayoutConverter; FNewLayout: string; ss: TStringStream; begin - converter := TTouchLayoutToVisualKeyboardConverter.Create(OSKFilename); + converter := TVisualKeyboardToTouchLayoutConverter.Create(OSKFilename); try if converter.Execute(FNewLayout) then begin diff --git a/developer/src/kmconvert/Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter.pas b/developer/src/kmconvert/Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter.pas similarity index 94% rename from developer/src/kmconvert/Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter.pas rename to developer/src/kmconvert/Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter.pas index 0f9a2861fe..5f19d98a2a 100644 --- a/developer/src/kmconvert/Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter.pas +++ b/developer/src/kmconvert/Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter.pas @@ -1,4 +1,4 @@ -unit Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter; +unit Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter; interface @@ -8,7 +8,7 @@ uses VisualKeyboard; type - TTouchLayoutToVisualKeyboardConverter = class + TVisualKeyboardToTouchLayoutConverter = class private FOwnsVisualKeyboard: Boolean; FVisualKeyboard: TVisualKeyboard; @@ -34,7 +34,7 @@ uses Unicode, VKeys; -constructor TTouchLayoutToVisualKeyboardConverter.Create( +constructor TVisualKeyboardToTouchLayoutConverter.Create( const AFileName: string); begin inherited Create; @@ -43,7 +43,7 @@ begin FVisualKeyboard.LoadFromFile(AFileName); end; -constructor TTouchLayoutToVisualKeyboardConverter.Create( +constructor TVisualKeyboardToTouchLayoutConverter.Create( AVisualKeyboard: TVisualKeyboard); begin inherited Create; @@ -51,7 +51,7 @@ begin FVisualKeyboard := AVisualKeyboard; end; -constructor TTouchLayoutToVisualKeyboardConverter.Create( +constructor TVisualKeyboardToTouchLayoutConverter.Create( const AStream: TStream); begin inherited Create; @@ -60,21 +60,21 @@ begin FVisualKeyboard.LoadFromStream(AStream); end; -destructor TTouchLayoutToVisualKeyboardConverter.Destroy; +destructor TVisualKeyboardToTouchLayoutConverter.Destroy; begin if FOwnsVisualKeyboard then FreeAndNil(FVisualKeyboard); inherited Destroy; end; -function TTouchLayoutToVisualKeyboardConverter.Execute( +function TVisualKeyboardToTouchLayoutConverter.Execute( var KeymanTouchLayout: string): Boolean; begin KeymanTouchLayout := ImportFromKVK; Result := KeymanTouchLayout <> ''; end; -function TTouchLayoutToVisualKeyboardConverter.ImportFromKVK: string; +function TVisualKeyboardToTouchLayoutConverter.ImportFromKVK: string; type TKeyData = record Data: TOnScreenKeyboardKeyData; @@ -296,7 +296,7 @@ end; * each layer on each given platform. Will rewrite the K_LCONTROL key * or remove it if there are no extended layers. *} -function TTouchLayoutToVisualKeyboardConverter.SetupModifierKeysForImportedLayout(TouchLayout: string): string; +function TVisualKeyboardToTouchLayoutConverter.SetupModifierKeysForImportedLayout(TouchLayout: string): string; procedure SetupModifierKeysForPlatform(p: TTouchLayoutPlatform); var l, l2: TTouchLayoutLayer; diff --git a/developer/src/kmconvert/kmconvert.dpr b/developer/src/kmconvert/kmconvert.dpr index ee650baf2f..9430142e6f 100644 --- a/developer/src/kmconvert/kmconvert.dpr +++ b/developer/src/kmconvert/kmconvert.dpr @@ -68,7 +68,7 @@ uses Keyman.Developer.System.KMConvertParameters in 'Keyman.Developer.System.KMConvertParameters.pas', Keyman.Developer.System.ImportKeyboardDLL in 'Keyman.Developer.System.ImportKeyboardDLL.pas', ScanCodeMap in '..\..\..\common\windows\delphi\general\ScanCodeMap.pas', - Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter in 'Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter.pas', + Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter in 'Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter.pas', OnScreenKeyboardData in '..\..\..\common\windows\delphi\visualkeyboard\OnScreenKeyboardData.pas', TouchLayout in '..\TIKE\oskbuilder\TouchLayout.pas', TouchLayoutDefinitions in '..\TIKE\oskbuilder\TouchLayoutDefinitions.pas', diff --git a/developer/src/kmconvert/kmconvert.dproj b/developer/src/kmconvert/kmconvert.dproj index 0fed3d1be3..5a99fcb0f8 100644 --- a/developer/src/kmconvert/kmconvert.dproj +++ b/developer/src/kmconvert/kmconvert.dproj @@ -174,7 +174,7 @@ - + diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index 5c3ed8bce5..e053d72b96 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -131,7 +131,7 @@ function test_server() { # eslint . tsc --build test # c8 --reporter=lcov --reporter=text - mocha + mocha --recursive build/test } function publish_server() { diff --git a/developer/src/server/package.json b/developer/src/server/package.json index 74fc7ed621..2f5ff64bb5 100644 --- a/developer/src/server/package.json +++ b/developer/src/server/package.json @@ -32,12 +32,7 @@ "@types/ws": "^8.2.2", "copyfiles": "^2.4.1", "mocha": "^10.0.0", - "ts-node": "^10.9.1", "tsc-watch": "^4.5.0", - "typescript": "^4.9.5" - }, - "mocha": { - "require": "ts-node/register", - "spec": "build/**/*.test.js" + "typescript": "^5.4.5" } } diff --git a/developer/src/tike/child/Keyman.Developer.UI.UframeWordlistEditor.pas b/developer/src/tike/child/Keyman.Developer.UI.UframeWordlistEditor.pas index 08dcdc92ee..60fb9464c2 100644 --- a/developer/src/tike/child/Keyman.Developer.UI.UframeWordlistEditor.pas +++ b/developer/src/tike/child/Keyman.Developer.UI.UframeWordlistEditor.pas @@ -186,7 +186,15 @@ function TframeWordlistEditor.DoOpenFile: Boolean; begin if FileExists(FileName) then begin - FWordlist.LoadFromFile(FileName); + try + FWordlist.LoadFromFile(FileName); + except + on E:EEncodingError do + begin + ShowMessage('Error loading '+FileName+': '+E.Message); + Exit(False); + end; + end; FModified := False; end; UpdateData; diff --git a/developer/src/tike/child/UfrmDebug.pas b/developer/src/tike/child/UfrmDebug.pas index 85b2edc281..d40993af4c 100644 --- a/developer/src/tike/child/UfrmDebug.pas +++ b/developer/src/tike/child/UfrmDebug.pas @@ -705,11 +705,21 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); UpdateDeadkeys; end; - procedure DoBackspace(BackspaceType: km_core_backspace_type); + procedure DoBackspace(BackspaceType: km_core_backspace_type; ExpectedValue: NativeUInt); var m, n: Integer; dk: TDeadKeyInfo; state: TMemoSelectionState; + + function AssertionMessage: string; + begin + Result := 'Assertion failed. Extra data: '+ + 'BackspaceType='+IntToStr(Ord(BackspaceType))+'; '+ + 'ExpectedValue=U+'+IntToHex(ExpectedValue, 4)+'; '+ + 'memo.SelStart='+IntToStr(memo.SelStart)+'; '+ + 'memo.SelLength='+IntToStr(memo.SelLength)+'; '+ + 'memo.Text='+Copy(memo.Text, 1, 256); + end; begin // Offset is zero-based, but string is 1-based. Beware! state := SaveMemoSelectionState; @@ -721,7 +731,7 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); // If the memo has a selection, we have given Core an empty context, // which forces it to emit a KM_CORE_BT_UNKNOWN backspace, which is // exactly what we want here. We just delete the selection - Assert(BackspaceType = KM_CORE_BT_UNKNOWN); + Assert(BackspaceType = KM_CORE_BT_UNKNOWN, AssertionMessage); memo.SelText := ''; RealignMemoSelectionState(state); Exit; @@ -730,8 +740,8 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); case BackspaceType of KM_CORE_BT_MARKER: begin - Assert(m >= 1); - Assert(memo.Text[m] = #$FFFC); + Assert(m >= 1, AssertionMessage); + Assert(memo.Text[m] = #$FFFC, AssertionMessage); dk := FDeadkeys.GetFromPosition(m-1); Assert(Assigned(dk)); dk.Delete; @@ -739,8 +749,8 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); end; KM_CORE_BT_CHAR: begin - Assert(m >= 1); - Assert(memo.Text[m] <> #$FFFC); + Assert(m >= 1, AssertionMessage); + Assert(memo.Text[m] <> #$FFFC, AssertionMessage); // Delete surrogate pairs if (m > 1) and Uni_IsSurrogate2(memo.Text[m]) and @@ -759,7 +769,7 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); while (m >= 1) and (memo.Text[m] = #$FFFC) do begin dk := FDeadkeys.GetFromPosition(m-1); - Assert(Assigned(dk)); + Assert(Assigned(dk), AssertionMessage); dk.Delete; Dec(m); end; @@ -776,13 +786,13 @@ procedure TfrmDebug.ExecuteEventAction(n: Integer); while (m >= 1) and (memo.Text[m] = #$FFFC) do begin dk := FDeadkeys.GetFromPosition(m-1); - Assert(Assigned(dk)); + Assert(Assigned(dk), AssertionMessage); dk.Delete; Dec(m); end; end; else - Assert(False, 'Unrecognised backspace type'); + Assert(False, AssertionMessage); // Unrecognised backspace type end; memo.Text := Copy(memo.Text, 1, m) + Copy(memo.Text, n+1, MaxInt); @@ -904,7 +914,7 @@ begin KM_CORE_IT_CHAR: DoChar(Text); KM_CORE_IT_MARKER: DoDeadkey(dwData); KM_CORE_IT_ALERT: DoBell; - KM_CORE_IT_BACK: DoBackspace(km_core_backspace_type(dwData)); + KM_CORE_IT_BACK: DoBackspace(km_core_backspace_type(dwData), nExpectedValue); KM_CORE_IT_PERSIST_OPT: ; //TODO KM_CORE_IT_CAPSLOCK: ; //TODO KM_CORE_IT_INVALIDATE_CONTEXT: ; // no-op diff --git a/developer/src/tike/child/UfrmKeymanWizard.pas b/developer/src/tike/child/UfrmKeymanWizard.pas index d8a62db410..baa906e836 100644 --- a/developer/src/tike/child/UfrmKeymanWizard.pas +++ b/developer/src/tike/child/UfrmKeymanWizard.pas @@ -453,13 +453,13 @@ type procedure ConfirmSaveOfOldEditorWindows; procedure ConfirmSaveOfOldEditorWindow(FeatureID: TKeyboardParser_FeatureID; FModified: Boolean; const FOldFilename: string; DoSave: TProc; DoLoad: TProc); - procedure LoadFeature(ID: TKeyboardParser_FeatureID); + function LoadFeature(ID: TKeyboardParser_FeatureID): Boolean; function FeatureTab(kf: TKeyboardParser_FeatureID): TTabSheet; procedure InitFeatureTab(ID: TKeyboardParser_FeatureID); procedure FeatureModified(Sender: TObject); function SaveFeature(ID: TKeyboardParser_FeatureID): Boolean; procedure SelectTouchLayoutTemplate(APromptChange: Boolean); - procedure LoadTouchLayout; // I4034 + function LoadTouchLayout: Boolean; // I4034 function GetFontInfo(Index: TKeyboardFont): TKeyboardFontInfo; // I4057 procedure SetFontInfo(Index: TKeyboardFont; const Value: TKeyboardFontInfo); // I4057 @@ -1628,7 +1628,7 @@ begin FLayoutSetup := FOldLayoutSetup; end; -procedure TfrmKeymanWizard.LoadFeature(ID: TKeyboardParser_FeatureID); +function TfrmKeymanWizard.LoadFeature(ID: TKeyboardParser_FeatureID): Boolean; begin if FKeyboardParser.Features.ContainsKey(ID) then begin @@ -1651,7 +1651,8 @@ begin end; kfTouchLayout: begin - LoadTouchLayout; // I4034 + if not LoadTouchLayout then + Exit(False); end; else begin @@ -1667,6 +1668,7 @@ begin end; end; FFeature[ID].Modified := False; + Result := True; end; function TfrmKeymanWizard.SaveFeature(ID: TKeyboardParser_FeatureID): Boolean; @@ -2007,7 +2009,12 @@ begin LoadSettings; for kf in FKeyboardParser.Features.Keys do - LoadFeature(kf); + begin + if not LoadFeature(kf) then + begin + Exit(False); + end; + end; if FKeyboardParser.IsComplex then // I4557 pagesLayout.ActivePage := pageLayoutCode; @@ -3163,18 +3170,17 @@ begin FFeature[kfTouchLayout].Modified := True; end; -procedure TfrmKeymanWizard.LoadTouchLayout; // I4034 +function TfrmKeymanWizard.LoadTouchLayout: Boolean; // I4034 begin if pagesTouchLayout.ActivePage = pageTouchLayoutDesign then begin - if not frameTouchLayout.Load(FFeature[kfTouchLayout].Filename, False, False) then - begin - pagesTouchLayout.ActivePage := pageTouchLayoutCode; - frameTouchLayoutSource.LoadFromFile(FFeature[kfTouchLayout].Filename, tffUTF8); - end; + Result := frameTouchLayout.Load(FFeature[kfTouchLayout].Filename, False, False); end else + begin frameTouchLayoutSource.LoadFromFile(FFeature[kfTouchLayout].Filename, tffUTF8); + Result := True; + end; end; procedure TfrmKeymanWizard.SaveTouchLayout; // I3885 diff --git a/developer/src/tike/child/UfrmPackageEditor.pas b/developer/src/tike/child/UfrmPackageEditor.pas index 39ab96b5f8..5aa02b50b3 100644 --- a/developer/src/tike/child/UfrmPackageEditor.pas +++ b/developer/src/tike/child/UfrmPackageEditor.pas @@ -1802,7 +1802,7 @@ begin cmdKeyboardRemoveLanguage.Enabled := e; cmdKeyboardEditLanguage.Enabled := e; - e := (lbKeyboards.ItemIndex >= 0) and (gridKeyboardLanguages.Row > 0); + e := (lbKeyboards.ItemIndex >= 0) and (gridKeyboardExamples.Row > 0); gridKeyboardExamples.Enabled := e; cmdKeyboardRemoveExample.Enabled := e; cmdKeyboardEditExample.Enabled := e; diff --git a/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas b/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas index a983139209..a5853faba0 100644 --- a/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas +++ b/developer/src/tike/debug/Keyman.System.Debug.DebugEvent.pas @@ -33,6 +33,7 @@ type TDebugEventActionData = class ActionType: km_core_action_type; dwData: Integer; + nExpectedValue: NativeUInt; Text: WideString; end; @@ -198,6 +199,7 @@ begin event.EventType := etAction; event.Action.ActionType := KM_CORE_IT_BACK; event.Action.dwData := expected_type; + event.Action.nExpectedValue := expected_value; Add(event); end; diff --git a/developer/src/tike/main/UfrmMain.pas b/developer/src/tike/main/UfrmMain.pas index 44b5873245..8fa7a45c9b 100644 --- a/developer/src/tike/main/UfrmMain.pas +++ b/developer/src/tike/main/UfrmMain.pas @@ -1477,8 +1477,12 @@ begin if n >= 0 then Result.ProjectFile := FGlobalProject.Files[n]; - (Result as frmClass).OpenFile(FFileName); LockWindowUpdate(0); + + if not (Result as frmClass).OpenFile(FFileName) then + begin + Result.Release; + end; end; procedure TfrmKeymanDeveloper.HelpTopic(s: string); diff --git a/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas b/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas index 4b0405a32f..1bc2c5c8de 100644 --- a/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas +++ b/developer/src/tike/oskbuilder/UframeTouchLayoutBuilder.pas @@ -90,7 +90,7 @@ type procedure cefCommand(Sender: TObject; const command: string; params: TStringList); procedure cefLoadEnd(Sender: TObject); - procedure RegisterSource; + procedure RegisterSources(const AState: string); procedure CharMapDragDrop(Sender, Source: TObject; X, Y: Integer); procedure CharMapDragOver(Sender, Source: TObject; X, Y: Integer; State: TDragState; var Accept: Boolean); @@ -140,7 +140,7 @@ uses xmldoc, Keyman.Developer.System.HelpTopics, - Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter, + Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter, CharacterDragObject, CharMapDropTool, @@ -238,15 +238,17 @@ begin modWebHttpServer.AppSource.UnregisterSource(FFilename+'#state'); end; -procedure TframeTouchLayoutBuilder.RegisterSource; +procedure TframeTouchLayoutBuilder.RegisterSources(const AState: string); begin if FFilename <> '' then modWebHttpServer.AppSource.RegisterSource(FFilename, FSavedLayoutJS); + if (FFileName <> '') and (AState <> '') then + modWebHttpServer.AppSource.RegisterSource(FFilename + '#state', AState, True); end; procedure TframeTouchLayoutBuilder.ImportFromKVK(const KVKFileName: string); // I3945 var - converter: TTouchLayoutToVisualKeyboardConverter; + converter: TVisualKeyboardToTouchLayoutConverter; FVK: TVisualKeyboard; FJS: string; begin @@ -268,7 +270,7 @@ begin end; end; - converter := TTouchLayoutToVisualKeyboardConverter.Create(FVK); + converter := TVisualKeyboardToTouchLayoutConverter.Create(FVK); try if not converter.Execute(FJS) then begin @@ -332,71 +334,85 @@ begin end; UnregisterSources; - try - if ALoadFromString then + if ALoadFromString then + begin + FNewLayoutJS := AFilename; + FFilename := GetNextFilename; + end + else + begin + if ALoadFromTemplate or (AFileName = '') or not FileExists(AFileName) then begin - FNewLayoutJS := AFilename; + FBaseFileName := FTemplateFileName; FFilename := GetNextFilename; end else begin - if ALoadFromTemplate or (AFileName = '') or not FileExists(AFileName) then - begin - FBaseFileName := FTemplateFileName; - FFilename := GetNextFilename; - end - else - begin - FBaseFileName := AFileName; - FFilename := AFileName; - end; - - with TStringList.Create do - try - LoadFromFile(FBaseFileName, TEncoding.UTF8); - FNewLayoutJS := Text; - finally - Free; - end; + FBaseFileName := AFileName; + FFilename := AFileName; end; - FTouchLayout := TTouchLayout.Create; // I3642 + with TStringList.Create do try - if not FTouchLayout.Load(FNewLayoutJS) then + LoadFromFile(FBaseFileName, TEncoding.UTF8); + FNewLayoutJS := Text; + finally + Free; + end; + end; + + FTouchLayout := TTouchLayout.Create; // I3642 + try + if not FTouchLayout.Load(FNewLayoutJS) then + begin + FLastError := FTouchLayout.LoadError; // I4083 + FLastErrorOffset := FTouchLayout.LoadErrorOffset; // I4083 + FFilename := FLastFilename; + RegisterSources(FState); + Exit(False); + end + else + begin + if (FSavedLayoutJS <> '') and ALoadFromTemplate then begin - FLastError := FTouchLayout.LoadError; // I4083 - FLastErrorOffset := FTouchLayout.LoadErrorOffset; // I4083 - FFilename := FLastFilename; - Exit(False); + FOldLayout := TTouchLayout.Create; + try + FOldLayout.Load(FSavedLayoutJS); + if FTouchLayout.Merge(FOldLayout) + then FSavedLayoutJS := FTouchLayout.Save(False) + else FSavedLayoutJS := FNewLayoutJS; + finally + FOldLayout.Free; + end; end else - begin - if (FSavedLayoutJS <> '') and ALoadFromTemplate then - begin - FOldLayout := TTouchLayout.Create; - try - FOldLayout.Load(FSavedLayoutJS); - if FTouchLayout.Merge(FOldLayout) - then FSavedLayoutJS := FTouchLayout.Save(False) - else FSavedLayoutJS := FNewLayoutJS; - finally - FOldLayout.Free; - end; - end - else - FSavedLayoutJS := FNewLayoutJS; - end; - finally - FTouchLayout.Free; + FSavedLayoutJS := FNewLayoutJS; end; - finally - RegisterSource; - if (FFileName <> '') and (FState <> '') then - modWebHttpServer.AppSource.RegisterSource(FFilename + '#state', FState, True); + FTouchLayout.Free; end; + if (FFileName <> '') and modWebHttpServer.AppSource.IsSourceRegistered(FFileName) then + begin + // If two .kmn files are loaded which both reference the same + // .keyman-touch-layout file, it's safest to just block it. This is a rare + // scenario, as most keyboard projects have a single .kmn, and it usually + // indicates a project may be in a bit of chaos anyway. + ShowMessage( + 'The touch layout is already opened for editing in another keyboard '+ + 'editor. Please close the other keyboard editor before opening this one '+ + 'again.'); + + // We want to prevent this window unregistering the sources it doesn't own + // when it is destroyed immediately after this, which we can do by blanking + // the filename. + FFileName := ''; + Exit(False); + end; + + RegisterSources(FState); + try DoLoad; except diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index b4bcd29e1f..d3a1935315 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -225,7 +225,7 @@ uses Keyman.System.HttpServer.Base in '..\..\..\common\windows\delphi\web\Keyman.System.HttpServer.Base.pas', Keyman.Developer.System.HttpServer.AppSource in 'http\Keyman.Developer.System.HttpServer.AppSource.pas', Keyman.UI.FontUtils in 'main\Keyman.UI.FontUtils.pas', - Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter in '..\kmconvert\Keyman.Developer.System.TouchLayoutToVisualKeyboardConverter.pas', + Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter in '..\kmconvert\Keyman.Developer.System.VisualKeyboardToTouchLayoutConverter.pas', Keyman.Developer.System.Project.kmnProjectFileAction in 'project\Keyman.Developer.System.Project.kmnProjectFileAction.pas', Keyman.Developer.System.Project.kpsProjectFileAction in 'project\Keyman.Developer.System.Project.kpsProjectFileAction.pas', Keyman.Developer.UI.Project.UfrmNewProjectParameters in 'project\Keyman.Developer.UI.Project.UfrmNewProjectParameters.pas' {frmNewProjectParameters}, diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index b3cd83bdfe..b343fc8037 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -468,7 +468,7 @@ - + diff --git a/developer/src/tike/xml/app/editor/editor.js b/developer/src/tike/xml/app/editor/editor.js index d8a076def4..73c48a2237 100644 --- a/developer/src/tike/xml/app/editor/editor.js +++ b/developer/src/tike/xml/app/editor/editor.js @@ -199,6 +199,13 @@ async function loadSettings() { }; context.updateExecutionPoint = function (row) { + if(!editor) { + // At debugger startup time, it initializes asynchronously at the same + // time as the editor, which means that in some circumstances the + // debugger will call to SetExecutionPointLine before the editor has + // loaded. The safest thing to do here is just exit. #11586 + return; + } if(row >= 0) { executionPoint = editor.deltaDecorations( executionPoint, diff --git a/developer/src/tike/xml/help/contexthelp.xml b/developer/src/tike/xml/help/contexthelp.xml index aa57f676ef..62bd8677ae 100644 --- a/developer/src/tike/xml/help/contexthelp.xml +++ b/developer/src/tike/xml/help/contexthelp.xml @@ -2,13 +2,99 @@ + +

(TODO: add documentation on group)

+
+
-

The Filter allows a user to reduce the number of characters displayed in the character map. The standard filter options used are by font name or block name.

+

The Filter allows a user to reduce the number of characters displayed in the character map. The standard filter options used are by font name or block name.

+

The Character Map Grid displays each character in a square, with a visual representation of the character and the Unicode value displayed at the bottom of the square. Each font group is divided into a block headed by the font name.

+ + +

The Filter allows a user to reduce the number of characters displayed in the + character map. The standard filter options used are by font name or block name.

+
+ + +

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, + XXXX is the starting Unicode value and YYYY is the finishing Unicode value.

+
+ + +

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, + XXXX is the starting Unicode value and YYYY is the finishing Unicode value.

+
+ + +

The filter format for a range is: [U+]XXXX-[U+]YYYY, where U+ is optional, + XXXX is the starting Unicode value and YYYY is the finishing Unicode value.

+
+ + +

The Filter allows a user to reduce the number of characters displayed in the + character map. The standard filter options used are by font name or block name.

+
+ + +

">" placed at the start of an entry will only show characters in the currently + selected Character Map font. This is helpful when trying to determine which characters + a given font supports.



+

Example: >LAO



+

finds all characters with names starting in "LAO" in the current font

+
+ + +

"<" placed at the start of an entry will search Unicode block names instead of + character names. This is helpful when searching for characters within related + blocks



+

Example: <Thai



+

finds the Thai Unicode block

+
+ + +

Using "*" in an entry serves as a wildcard for any number of places in that entry. + For example, searching for "greek*alpha" will find characters whose Unicode names begin + with the word "Greek" and contain the word "Alpha" any number of places later. + This is helpful when searching for characters that share a common element in + their names (e.g. capital).

+
+ + +

Using "?" anywhere in an entry serves as a wildcard for that single place in the entry. + For example, searching for "s???e" will return both the SPACE and the SMILE characters, + among others.



+
+ + +

Example: 1000-119F



+

finds all characters between U+1000 and U+119F (inclusive) - + the Myanmar alphabet in this case

+
+ + +

Example: LATIN * LETTER [AEIOU]



+

finds all all Latin A,E,I,O or U vowel combinations

+
+ + +

"$" placed at the end of an entry will match from the end of a Unicode character name. + This option works best when used with "*" or "?".



+

Example: LATIN * LETTER A$



+

finds only "a" and "A"

+
+ + +

Click OK to save changes and close the dialog.

+
+ + +

Click Cancel to close the dialog without saving changes.

+
+ + + + + + + + + KeymanWeb - On-Screen Keyboard Rendering + + + + + + + + + + + + + + + +

KeymanWeb - On-Screen Keyboard Rendering

+

See this tool's README.md for details.

+

Saved render comparison tool here.

+

This page is designed for use in testing the local version of a keyboard. Please select compiled + keyboard package to render: +

+

+ +

+

+

+ +

+

+

+ Return to tools index page. +

+
+ + + +
+
+ + + diff --git a/web/test.sh b/web/test.sh index e3cbaef60f..12216512c9 100755 --- a/web/test.sh +++ b/web/test.sh @@ -27,32 +27,6 @@ builder_parse "$@" # Browser-based tests: common configs & kill-switches -# DO_BROWSER_TEST_SUITE=true - -# if [[ $VERSION_ENVIRONMENT == test ]]; then -# # Implied: CONFIG=CI.conf.js because `-CI` parameter is passed. -# # -# # If we are running a TeamCity test build, for now, only run BrowserStack -# # tests when on a PR branch with a title including "(web)" or with the label -# # test-browserstack. This is because the BrowserStack tests are currently -# # unreliable, and the false positive failures are masking actual failures. -# # -# # We do not run BrowserStack tests on master, beta, or stable-x.y test -# # builds. -# DO_BROWSER_TEST_SUITE=false -# if builder_pull_get_details; then -# if [[ $builder_pull_title =~ \(web\) ]] || builder_pull_has_label test-browserstack; then -# DO_BROWSER_TEST_SUITE=true -# fi -# fi -# fi - -# if [[ $DO_BROWSER_TEST_SUITE == false ]]; then -# builder_warn "Skipping action test:engine - this CI build does not appear to be for a Web PR." -# builder_finish_action success test:engine -# exit 0 -# fi - # Select the right CONFIG file. WTR_CONFIG= if builder_has_option --ci; then diff --git a/web/tsconfig.base.json b/web/tsconfig.base.json index e0ed00a89a..e17b4deece 100644 --- a/web/tsconfig.base.json +++ b/web/tsconfig.base.json @@ -11,7 +11,6 @@ "target": "es5", // Other settings - declaration files, sourcemapping, and other miscellaneous bits. - "allowJs": false, "declaration": true, "experimentalDecorators": true, "importHelpers": true,