From dd284665fb078acfeaf3e5f30581d001e3d9361b Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 5 Apr 2024 15:03:50 +0700 Subject: [PATCH 1/9] fix(ios): deletion of selected text --- .../KeymanEngine/Classes/Keyboard/InputViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift index 172385c6fc..faa7d21e61 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift @@ -351,7 +351,7 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate { if let selected = textDocumentProxy.selectedText { if selected.count > 0 { - textDocumentProxy.deleteBackward() + textDocumentProxy.insertText(""); hasDeletedSelection = true } } From b689fc270614a56777d3957b048dd3e2ca86c583 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Wed, 10 Apr 2024 08:51:07 +0700 Subject: [PATCH 2/9] fix(ios): workaround for empty-string insert not erasing selected text --- .../Keyboard/InputViewController.swift | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift index faa7d21e61..0ecb3ee9f1 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift @@ -347,18 +347,40 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate { perform(#selector(self.enableInputClickSound), with: nil, afterDelay: 0.1) } - var hasDeletedSelection = false + var deleteSelection = false if let selected = textDocumentProxy.selectedText { if selected.count > 0 { - textDocumentProxy.insertText(""); - hasDeletedSelection = true + deleteSelection = true } } - if numCharsToDelete <= 0 || hasDeletedSelection { - textDocumentProxy.insertText(newText) + if deleteSelection && newText == "" { + /* + if deleteSelection && newText == "", we have a backspace on + selected text. Sadly, .insertText("")... does nothing. Why, Apple!? + + The one silver lining: Apple makes it impossible for users to select text + in a way that splits character clusters. + + So, we can just insert something that won't combine, like a ZWNJ, and then delete it. + */ + let beforeManipulation = textDocumentProxy.documentContextBeforeInput ?? "" + textDocumentProxy.insertText("\u{200c}") + textDocumentProxy.deleteBackward() + + let afterManipulation = textDocumentProxy.documentContextBeforeInput ?? "" + + // For good measure, a canary to signal if our selected-text backspace handling + // goes awry. + if beforeManipulation != afterManipulation { + os_log(.error, log: KeymanEngineLogger.engine, "Could not cleanly execute backspace for selected text") + } + sendContextUpdate() + return + } else if numCharsToDelete <= 0 || deleteSelection { + textDocumentProxy.insertText(newText) sendContextUpdate() return } From 7df368a143159ec9644fa7ef2759fb778b6a6869 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 11 Apr 2024 11:36:10 +0700 Subject: [PATCH 3/9] fix(web): now auto-scrolls if target element would be obscured by OSK on device rotation --- web/src/app/browser/src/keymanEngine.ts | 42 ++++++++++--------- .../browser/src/utils/rotationProcessor.ts | 9 ++++ 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 6b95cb49a0..fac77150c7 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -73,29 +73,33 @@ export default class KeymanEngine extends KeymanEngineBase).activationTrigger = e; - if(this.config.hostDevice.touchable) { - if(!e || !target || !this.osk) { - return; - } - - // Get the absolute position of the caret - const y = getAbsoluteY(e); - const t = window.pageYOffset; - let dy = y-t; - if(y >= t) { - dy -= (window.innerHeight - this.osk._Box.offsetHeight - e.offsetHeight - 2); - if(dy < 0) { - dy=0; - } - } - // Hide OSK, then scroll, then re-anchor OSK with absolute position (on end of scroll event) - if(dy != 0) { - window.scrollTo(0, dy + t); - } + if(this.config.hostDevice.touchable && target) { + this.ensureElementVisibility(e); } }); } + public ensureElementVisibility(e: HTMLElement) { + if(!e || !this.osk) { + return; + } + + // Get the absolute position of the caret + const y = getAbsoluteY(e); + const t = window.pageYOffset; + let dy = y-t; + if(y >= t) { + dy -= (window.innerHeight - this.osk._Box.offsetHeight - e.offsetHeight - 2); + if(dy < 0) { + dy=0; + } + } + // Hide OSK, then scroll, then re-anchor OSK with absolute position (on end of scroll event) + if(dy != 0) { + window.scrollTo(0, dy + t); + } + } + public get util() { return this._util; } diff --git a/web/src/app/browser/src/utils/rotationProcessor.ts b/web/src/app/browser/src/utils/rotationProcessor.ts index 1228153497..e8141d1ab1 100644 --- a/web/src/app/browser/src/utils/rotationProcessor.ts +++ b/web/src/app/browser/src/utils/rotationProcessor.ts @@ -58,6 +58,15 @@ export class RotationProcessor { window.clearInterval(this.updateTimer); this.rotState = null; } + + const target = this.keyman.contextManager.activeTarget; + if(target) { + // This seems to help with scrolling accuracy in iOS Safari; + // the scroll tends to consistently go too far without it. + window.setTimeout(() => { + this.keyman.ensureElementVisibility(target.getElement()); + }, 0); + } } // Used by both Android and iOS. From d6df7c29f4e1920df58fbfbef66977055c9e103d Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Fri, 12 Apr 2024 10:50:06 +0700 Subject: [PATCH 4/9] fix(ios): better delete pattern --- .../Keyboard/InputViewController.swift | 89 ++++++++++++------- 1 file changed, 57 insertions(+), 32 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift index 0ecb3ee9f1..0662c55855 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift @@ -333,6 +333,60 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate { } } } + + func deleteSelection() -> Bool { + if let selected = textDocumentProxy.selectedText, selected.count > 0 { + /* + Since we're doing some funky text manipulation, it's best to add + a "canary" check in case something does go awry with it in + the future. + */ + let beforeManipulation = textDocumentProxy.documentContextBeforeInput ?? "" + + /* + Apple has some special nuances to its text deletion that our internal + Web engine does not emulate. + + .deleteBackward() behaviors: + - If there is selected text immediately following a space (U+0020), + it will delete that space IN ADDITION to the selected text. + - If there is selected text that starts mid-word, it will NOT delete + the preceding character. + + Compare to Web: Web states an exact number of characters to delete + before the start of the currently-selected range... and it does this + completely unaware of the nuances listed above for .deleteBackward(). + Keyman keyboard rules are likewise unaware of Apple's nuances. + + In order to maintain proper synchronization between app context and + internal Web-engine context, we need to force selected-text deletion + to NEVER delete preceding spaces. Any attempts to adjust and include + the aforementioned nuance will need considerable design work to "get + right" due to the risk for adverse affects with Keyman keyboard rules. + + .insertText() is great for this... when the string isn't empty. If + it is, well, "sorry, out of luck." That said, we can just insert + something that won't combine, like a ZWNJ, and then delete it. + + The silver lining: Apple makes it impossible for users to select text + in a way that splits character clusters. This implies that it's + impossible for an inserted ZWNJ to combine with existing context, + making this operation safe. + */ + textDocumentProxy.insertText("\u{200c}") + textDocumentProxy.deleteBackward() + + let afterManipulation = textDocumentProxy.documentContextBeforeInput ?? "" + + // And now to finish our 'canary' check. + if beforeManipulation != afterManipulation { + os_log(.error, log: KeymanEngineLogger.engine, "Could not cleanly execute backspace for selected text") + } + + return true + } + return false + } func insertText(_ keymanWeb: KeymanWebViewController, numCharsToDelete: Int, newText: String) { if keymanWeb.isSubKeysMenuVisible { @@ -347,39 +401,10 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate { perform(#selector(self.enableInputClickSound), with: nil, afterDelay: 0.1) } - var deleteSelection = false + // `true` if there was selected text to be deleted + let deletedSelection = self.deleteSelection() - if let selected = textDocumentProxy.selectedText { - if selected.count > 0 { - deleteSelection = true - } - } - - if deleteSelection && newText == "" { - /* - if deleteSelection && newText == "", we have a backspace on - selected text. Sadly, .insertText("")... does nothing. Why, Apple!? - - The one silver lining: Apple makes it impossible for users to select text - in a way that splits character clusters. - - So, we can just insert something that won't combine, like a ZWNJ, and then delete it. - */ - let beforeManipulation = textDocumentProxy.documentContextBeforeInput ?? "" - - textDocumentProxy.insertText("\u{200c}") - textDocumentProxy.deleteBackward() - - let afterManipulation = textDocumentProxy.documentContextBeforeInput ?? "" - - // For good measure, a canary to signal if our selected-text backspace handling - // goes awry. - if beforeManipulation != afterManipulation { - os_log(.error, log: KeymanEngineLogger.engine, "Could not cleanly execute backspace for selected text") - } - sendContextUpdate() - return - } else if numCharsToDelete <= 0 || deleteSelection { + if numCharsToDelete <= 0 || deletedSelection { textDocumentProxy.insertText(newText) sendContextUpdate() return From 650e86d5ac79ef256f4d8efbfa1f2712e6ad766c Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Wed, 17 Apr 2024 15:03:36 +0700 Subject: [PATCH 5/9] docs(ios): applies comment suggestions from code review --- .../Keyboard/InputViewController.swift | 43 +++++++++---------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift index 0662c55855..5fec149e1b 100644 --- a/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift +++ b/ios/engine/KMEI/KeymanEngine/Classes/Keyboard/InputViewController.swift @@ -344,34 +344,29 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate { let beforeManipulation = textDocumentProxy.documentContextBeforeInput ?? "" /* - Apple has some special nuances to its text deletion that our internal - Web engine does not emulate. + We have a problem to resolve here: we cannot simply delete the selection + with either .deleteBackward() or .insertText(""): - .deleteBackward() behaviors: - If there is selected text immediately following a space (U+0020), - it will delete that space IN ADDITION to the selected text. - - If there is selected text that starts mid-word, it will NOT delete - the preceding character. + .deleteBackward() will delete that space IN ADDITION to the selected text. + - Unlike .insertText("-any-string-here"), .insertText("") does nothing; + it does not replace the selection with the new string. - Compare to Web: Web states an exact number of characters to delete - before the start of the currently-selected range... and it does this - completely unaware of the nuances listed above for .deleteBackward(). - Keyman keyboard rules are likewise unaware of Apple's nuances. + Our policy (#9073) on handling the backspace key when there is a + text selection is to just delete the selection. We have to override + the special case of space being deleted by .deleteBackward() ourselves. + Additionally, the internal Web engine cannot anticipate the special + case and requires precise and consistent backspace handling in line + with our policy in order to keep the context on both sides synchronized. - In order to maintain proper synchronization between app context and - internal Web-engine context, we need to force selected-text deletion - to NEVER delete preceding spaces. Any attempts to adjust and include - the aforementioned nuance will need considerable design work to "get - right" due to the risk for adverse affects with Keyman keyboard rules. + As .insertText() does not delete the selection if the string to + be inserted is empty, we insert something that won't combine, + like a ZWNJ, and then delete it. - .insertText() is great for this... when the string isn't empty. If - it is, well, "sorry, out of luck." That said, we can just insert - something that won't combine, like a ZWNJ, and then delete it. - - The silver lining: Apple makes it impossible for users to select text - in a way that splits character clusters. This implies that it's - impossible for an inserted ZWNJ to combine with existing context, - making this operation safe. + iOS does not allow users to select text in a way that splits + character clusters. This implies that it's impossible for an + inserted ZWNJ to combine with existing context, making this + operation safe. */ textDocumentProxy.insertText("\u{200c}") textDocumentProxy.deleteBackward() @@ -404,6 +399,8 @@ open class InputViewController: UIInputViewController, KeymanWebDelegate { // `true` if there was selected text to be deleted let deletedSelection = self.deleteSelection() + // If text was selected, we generally act as if the context is nil - no back + // deletions allowed, so we skip that section. if numCharsToDelete <= 0 || deletedSelection { textDocumentProxy.insertText(newText) sendContextUpdate() From db514e0dc5e3e349a8bd113bb614e64d472a97cf Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 18 Apr 2024 09:05:30 +0700 Subject: [PATCH 6/9] fix(common): update emoji stripping for Unicode 15.1 Fixes #11220. Replaces the sed script for matching emoji in ios prepRelease with a tiny node project that uses emoji-regex package, to reduce future maintenance. Also strips lingering emoji and :xxx: emoji shortcut strings from HISTORY.md. Note that :xxx: are not stripped programatically at this time. --- HISTORY.md | 74 ++++++++++---------- ios/tools/prepRelease.sh | 5 +- package-lock.json | 29 ++++++++ package.json | 1 + resources/build/version/package.json | 1 + resources/build/version/src/reportHistory.ts | 7 +- resources/tools/strip-emoji/index.js | 18 +++++ resources/tools/strip-emoji/package.json | 14 ++++ 8 files changed, 106 insertions(+), 43 deletions(-) create mode 100644 resources/tools/strip-emoji/index.js create mode 100644 resources/tools/strip-emoji/package.json diff --git a/HISTORY.md b/HISTORY.md index 68fa259f91..f40c4195a5 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -3,9 +3,9 @@ ## 17.0.307 beta 2024-04-12 * fix(common): specify title explicitly when opening PR with hub (#11173) -* refactor(web): better centralizes OSK layout internals to prepare for optimization efforts 🪠 (#11176) -* feat(web): VisualKeyboard layout-reflow optimization 🪠 (#11177) -* change(web): OSK optimization, improved responsiveness 🪠 (#11140) +* refactor(web): better centralizes OSK layout internals to prepare for optimization efforts (#11176) +* feat(web): VisualKeyboard layout-reflow optimization (#11177) +* change(web): OSK optimization, improved responsiveness (#11140) * (#11216) ## 17.0.306 beta 2024-04-11 @@ -16,9 +16,9 @@ ## 17.0.305 beta 2024-04-10 * (#11169) -* change(web): merges split async method in gesture engine 🪠 (#11142) +* change(web): merges split async method in gesture engine (#11142) * fix(web): blocks nextLayer for keys quickly typed when multitapping to new layer when final tap is held (#11189) -* refactor(web): OSK spacebar-label updates now managed by layer object 🪠 (#11175) +* refactor(web): OSK spacebar-label updates now managed by layer object (#11175) ## 17.0.304 beta 2024-04-09 @@ -38,14 +38,14 @@ * feat(core): support modifiers=other (#11118) * chore(core): dx better err message on embedded test vkeys (#11119) -* fix(web): key preview stickiness 🪠 (#10778) -* fix(web): early gesture-match abort when unable to extend existing gestures 🪠 (#10836) -* fix(web): infinite model-match replacement looping 🪠 (#10838) -* fix(web): proper gesture-match sequencing 🪠 (#10840) -* change(web): input-event sequentialization 🪠 (#10843) -* fix(web): proper linkage of sources to events 🪠 (#10960) +* fix(web): key preview stickiness (#10778) +* fix(web): early gesture-match abort when unable to extend existing gestures (#10836) +* fix(web): infinite model-match replacement looping (#10838) +* fix(web): proper gesture-match sequencing (#10840) +* change(web): input-event sequentialization (#10843) +* fix(web): proper linkage of sources to events (#10960) * fix(developer): handle buffer boundaries in four cases (#11137) -* chore(linux): Build packages for next Ubuntu version separately :cherries: (#11153) +* chore(linux): Build packages for next Ubuntu version separately (#11153) * fix(common): upgrade sentry-cli to 2.31.0 (#11151) * fix(android/app): Track previous device orientation for SystemKeyboard (#11134) * (#11129) @@ -2024,8 +2024,8 @@ * fix(developer): package editor no longer loses RTL flag for LMs (#8607) * chore(common): use mac /usr/bin/stat rather than homebrew version (#8925) * chore(ios): replace fv cert (#8923) -* fix(core): Fix compilation if hotdoc is installed :cherries: (#8929) -* chore(linux): Move some files to keyman-config :cherries: (#8930) +* fix(core): Fix compilation if hotdoc is installed (#8929) +* chore(linux): Move some files to keyman-config (#8930) * fix(core): Fix compiling with GCC 13 (#8932) ## 16.0.139 stable 2023-03-16 @@ -2262,7 +2262,7 @@ * fix(developer): URL parameters should be UTF-8 (#7631) * fix(android/engine): Use IME package name if query permission denied (#7668) * fix(linux): Fix keyboard icon in system tray (#7678) -* chore(linux): Update debian changelog :cherries: (#7682) +* chore(linux): Update debian changelog (#7682) * chore(linux): Update Debian standards version (#7683) ## 16.0.100 beta 2022-11-10 @@ -2476,7 +2476,7 @@ ## 16.0.70 alpha 2022-09-26 * fix(android): Add language name when installing default lexical-model (#7347) -* chore(common): Add 15.0 stable entries to HISTORY.md :cherries: (#7350) +* chore(common): Add 15.0 stable entries to HISTORY.md (#7350) ## 16.0.69 alpha 2022-09-21 @@ -2666,7 +2666,7 @@ ## 16.0.37 alpha 2022-07-22 * fix(linux): Catch PermissionError exception (#6968) -* chore(linux): Update Debian changelog :cherries: (#6973) +* chore(linux): Update Debian changelog (#6973) * chore(linux): Add support for Ubuntu 22.10 "Kinetic Kudu" (#6975) * fix(developer): reduce timeouts if server shut down (#6943) * fix(linux): Implement refresh after keyboard installation (#6956) @@ -2859,12 +2859,12 @@ ## 15.0.269 stable 2022-08-29 * chore(linux): Update debian changelog (#7040) -* feat(linux): Replace deprecated distutils :cherries: (#7052) +* feat(linux): Replace deprecated distutils (#7052) * fix(developer): compiler emitting garbage for readonly groups (#7014) * chore: Change platform advocates per discussion (#7114) * fix(windows): remove saving and restoring context kbd options (#7107) * fix(windows): Add invalidate context action to non-updatable parse (#7108) -* fix(android/engine): :cherries: Lower the max height for landscape orientation (#7128) +* fix(android/engine): Lower the max height for landscape orientation (#7128) ## 15.0.268 stable 2022-08-04 @@ -2881,10 +2881,10 @@ * fix(ios): ignore CFBundleShortVersionString (#6935) * fix(web): context-only rule effects, set(&layer) from physical keystrokes ️ (#6949) * chore(web): remove invalid warning msg (#6951) -* fix(common): Fix `delete` :cherries: (#6966) -* fix(linux): Another attempt at fixing postinst script :cherries: (#6961) -* fix(linux): Fix uninstallation when using fcitx5 :cherries: (#6964) -* fix(linux): Catch PermissionError exception :cherries: (#6969) +* fix(common): Fix `delete` (#6966) +* fix(linux): Another attempt at fixing postinst script (#6961) +* fix(linux): Fix uninstallation when using fcitx5 (#6964) +* fix(linux): Catch PermissionError exception (#6969) * chore(linux): Update Debian changelog (#6972) * fix(developer): kmdecomp virtual character key output (#6945) * fix(developer): crash on exit when checking for updates (#6946) @@ -2896,7 +2896,7 @@ ## 15.0.266 stable 2022-07-08 -* fix(linux): Fix post-install script :cherries: (#6895) +* fix(linux): Fix post-install script (#6895) * fix(web): improve `console.error()` reporting (#6904) * fix(web): ncaps rules not matching on touch (#6913) @@ -3346,7 +3346,7 @@ ## 15.0.183 alpha 2022-01-21 -* chore(linux): Update changelogs for 14.0.284 :cherries: (#6132) +* chore(linux): Update changelogs for 14.0.284 (#6132) * chore(linux): Revert workaround for Python bug (#6133) ## 15.0.182 alpha 2022-01-21 @@ -3465,12 +3465,12 @@ ## 15.0.162 alpha 2021-12-05 -* chore(linux): Update changelogs for 14.0.283 :package: :cherries: (#6008) +* chore(linux): Update changelogs for 14.0.283 (#6008) ## 15.0.161 alpha 2021-12-04 -* chore(linux): Allow to specify debian revision :package: :cherries: (#5999) -* chore(linux): Remove lintian warning :package: :cherries: (#6000) +* chore(linux): Allow to specify debian revision (#5999) +* chore(linux): Remove lintian warning (#6000) ## 15.0.160 alpha 2021-12-03 @@ -3656,7 +3656,7 @@ * fix(android/engine): Remove unnecessary permissions from Manifest (#5752) * feat(developer): touch layout testing (#5723) * fix(web): popup positioning (#5742) -* chore(linux): Update changelog files for 14.0.282 :cherries: (#5794) +* chore(linux): Update changelog files for 14.0.282 (#5794) ## 15.0.124 alpha 2021-10-04 @@ -4472,8 +4472,8 @@ ## 14.0.285 stable 2022-01-20 -* fix(linux): Fix lintian errors :cherries: (#6107) -* fix(linux): Add workaround for Python bug :cherries: (#6125) +* fix(linux): Fix lintian errors (#6107) +* fix(linux): Add workaround for Python bug (#6125) * fix(web): Use regex to determine display layer and functional layers (#6123) ## 14.0.284 stable 2022-01-11 @@ -4484,12 +4484,12 @@ * fix(android/engine): Fix font paths (#5990) * chore(linux): Remove lintian warning (#5993) * chore(linux): Allow to specify debian revision (#5998) -* chore(linux): Update changelogs for 14.0.283 :package: (#6007) -* fix(linux): fix release version number for Sentry reporting :cherries: (#6053) +* chore(linux): Update changelogs for 14.0.283 (#6007) +* fix(linux): fix release version number for Sentry reporting (#6053) * chore(common): Check in crowdin strings for Spanish (Latin America) (#6060) -* fix(linux): fix release version number for Sentry reporting :cherries: (#6069) +* fix(linux): fix release version number for Sentry reporting (#6069) * chore(android/samples): Add -no-daemon flag to KMSample2 build script (#6083) -* fix(linux): Fix attribute error :cherries: (#6087) +* fix(linux): Fix attribute error (#6087) ## 14.0.283 stable 2021-11-17 @@ -4497,7 +4497,7 @@ * chore(linux): copy Keyman for Linux 15 reference to 14 (#5764) * fix(linux): Fix debian package script (#5772) * chore(common): Enhance cherry-pick labeling (#5773) -* fix(common): Fix cherry-pick labeling (:cherries:) (#5783) +* fix(common): Fix cherry-pick labeling () (#5783) * fix(linux): Don't crash displaying keyboard details (#5757) * chore(linux): Update changelog files for 14.0.282 (#5793) * fix(linux): Don't crash with non-keyboard package file (#5754) @@ -6016,7 +6016,7 @@ ## 14.0.81 alpha 2020-05-27 -* refactor(resources): convert gosh into npm package 🙃 (#3159) +* refactor(resources): convert gosh into npm package (#3159) * chore(common,web): use consistent TypeScript dep on all packages (#3158) * chore(common/resources): add `common/models` to build trigger definitions (#3144) * fix(common/resources): adds package-lock.json for gosh package (#3171) diff --git a/ios/tools/prepRelease.sh b/ios/tools/prepRelease.sh index c36ad97b45..0da59d2174 100755 --- a/ios/tools/prepRelease.sh +++ b/ios/tools/prepRelease.sh @@ -50,9 +50,8 @@ get_version_notes "ios" "${BUILD_NUMBER}" "$TIER" > $CHANGELOG_PATH echo "* Minor fixes and performance improvements" >> $CHANGELOG_PATH assertFileExists "${CHANGELOG_PATH}" -# Strip emoji to make Apple happy -emoji="\U1f300-\U1f5ff\U1f900-\U1f9ff\U1f600-\U1f64f\U1f680-\U1f6ff\U2600-\U26ff\U2700-\U27bf\U1f1e6-\U1f1ff\U1f191-\U1f251\U1f004\U1f0cf\U1f170-\U1f171\U1f17e-\U1f17f\U1f18e\U3030\U2b50\U2b55\U2934-\U2935\U2b05-\U2b07\U2b1b-\U2b1c\U3297\U3299\U303d\U00a9\U00ae\U2122\U23f3\U24c2\U23e9-\U23ef\U25b6\U23f8-\U23fa" -LC_ALL=UTF-8 sed -e "s/[$(printf $emoji)]//g" < "$CHANGELOG_PATH" > "$CHANGELOG_PATH.1" +# Strip emoji as App Store does not allow emoji in changelogs +node "$KEYMAN_ROOT/resources/tools/strip-emoji" < "$CHANGELOG_PATH" > "$CHANGELOG_PATH.1" mv -f "$CHANGELOG_PATH.1" "$CHANGELOG_PATH" assertFileExists "${CHANGELOG_PATH}" diff --git a/package-lock.json b/package-lock.json index 67ce85d1db..918bbca135 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "name": "root", "workspaces": [ "resources/gosh", + "resources/tools/strip-emoji", "resources/build/version", "core/include/ldml", "developer/src/common/web/test-helpers", @@ -11672,6 +11673,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/stripemoji": { + "resolved": "resources/tools/strip-emoji", + "link": true + }, "node_modules/supports-color": { "version": "7.2.0", "license": "MIT", @@ -12702,6 +12707,7 @@ "dependencies": { "@actions/core": "^1.9.1", "@actions/github": "^2.1.0", + "emoji-regex": "^10.3.0", "typescript": "^4.9.5", "yargs": "^17.7.2" }, @@ -12739,6 +12745,11 @@ "node": ">=12" } }, + "resources/build/version/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, "resources/build/version/node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -12760,6 +12771,11 @@ "node": ">=8" } }, + "resources/build/version/node_modules/string-width/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "resources/build/version/node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", @@ -12803,6 +12819,19 @@ "gosh": "gosh.js" } }, + "resources/tools/strip-emoji": { + "name": "stripemoji", + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0" + } + }, + "resources/tools/strip-emoji/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, "web": { "name": "keyman", "license": "MIT", diff --git a/package.json b/package.json index aab36c3bc1..51fc2b99e5 100644 --- a/package.json +++ b/package.json @@ -25,6 +25,7 @@ "scripts": {}, "workspaces": [ "resources/gosh", + "resources/tools/strip-emoji", "resources/build/version", "core/include/ldml", "developer/src/common/web/test-helpers", diff --git a/resources/build/version/package.json b/resources/build/version/package.json index 57264f02e4..5d55f9798f 100644 --- a/resources/build/version/package.json +++ b/resources/build/version/package.json @@ -4,6 +4,7 @@ "dependencies": { "@actions/core": "^1.9.1", "@actions/github": "^2.1.0", + "emoji-regex": "^10.3.0", "typescript": "^4.9.5", "yargs": "^17.7.2" }, diff --git a/resources/build/version/src/reportHistory.ts b/resources/build/version/src/reportHistory.ts index 2fae1ab628..e3c670265f 100644 --- a/resources/build/version/src/reportHistory.ts +++ b/resources/build/version/src/reportHistory.ts @@ -4,6 +4,7 @@ import { GitHub } from '@actions/github'; import { findLastHistoryPR, getAssociatedPR} from './graphql/queries.js'; import { spawnChild } from './util/spawnAwait.js'; +import emojiRegex from 'emoji-regex'; const getPullRequestInformation = async ( octokit: GitHub, base: string @@ -125,10 +126,10 @@ export const reportHistory = async ( } else { let git_tag = 'Next Version', git_tag_data = 'Next Version'; const re = /#(\d+)/; - const emojiRE = /[\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}]/gu; + const emojiRE = emojiRegex(); for(const commit of new_commits) { if(!useGitHubPRInfo) { - const git_pr_title = (await spawnChild('git', ['log', '--format=%b', '-n', '1', commit])).replace(emojiRE, ' ').trim(); + const git_pr_title = (await spawnChild('git', ['log', '--format=%b', '-n', '1', commit])).replaceAll(emojiRE, ' ').trim(); if(git_pr_title.match(/^auto\:/)) continue; const git_pr_data = (await spawnChild('git', ['log', '--format=%s', '-n', '1', commit])).trim(); const this_git_tag = (await spawnChild('git', ['tag', '--points-at', commit])).trim(); @@ -164,7 +165,7 @@ export const reportHistory = async ( if(pulls.find(p => p.number == pr.number) == undefined) { pr.tag_data = git_tag_data; pr.version = git_tag; - pr.title = pr.title.replace(emojiRE, ' ').trim(); + pr.title = pr.title.replaceAll(emojiRE, ' ').trim(); pulls.push(pr); } } diff --git a/resources/tools/strip-emoji/index.js b/resources/tools/strip-emoji/index.js new file mode 100644 index 0000000000..c255983208 --- /dev/null +++ b/resources/tools/strip-emoji/index.js @@ -0,0 +1,18 @@ + +const emojiRegex = require('emoji-regex'); + +process.stdin.setEncoding('utf-8'); + +// We will concatenate all strings and assume we are not processing a huge file, +// so we don't break in the middle of a UTF-8 sequence or split emoji sequences +// in half. + +let stream = ''; +process.stdin.on('readable', () => { + const data = process.stdin.read(); + if(data) { + stream += data; + } else { + process.stdout.write(stream.replaceAll(emojiRegex(), '')); + } +}); \ No newline at end of file diff --git a/resources/tools/strip-emoji/package.json b/resources/tools/strip-emoji/package.json new file mode 100644 index 0000000000..d28aebcc39 --- /dev/null +++ b/resources/tools/strip-emoji/package.json @@ -0,0 +1,14 @@ +{ + "name": "stripemoji", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0" + } +} From 006151f0542f5dc62516c8cae52d7aab01b5d087 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Thu, 18 Apr 2024 10:15:22 +0700 Subject: [PATCH 7/9] fix(web): longpress cancellation on extra touch should not cancel the extra touch --- .../gestures/matchers/gestureMatcher.ts | 4 ++-- .../headless/gestures/specs/gestureModel.ts | 2 +- .../headless/gestures/isolatedGestureSpecs.ts | 2 +- .../osk/src/input/gestures/browser/flick.ts | 2 +- .../osk/src/input/gestures/specsForLayout.ts | 17 +++++++++-------- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts index 8a3c14fc6e..9f909631ac 100644 --- a/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts +++ b/common/web/gesture-recognizer/src/engine/headless/gestures/matchers/gestureMatcher.ts @@ -49,7 +49,7 @@ export class GestureMatcher implements PredecessorMatch< public get sources(): GestureSource[] { return this.pathMatchers.map((pathMatch, index) => { - if(this.model.contacts[index].resetOnResolve) { + if(this.model.contacts[index].resetOnInstantFulfill) { return undefined; } else { return pathMatch.source; @@ -106,7 +106,7 @@ export class GestureMatcher implements PredecessorMatch< if(source && entry == source) { // Due to internal delays that can occur when an incoming tap triggers // completion of a previously-existing gesture but is not included in it - // (`resetOnResolve` mechanics), it is technically possible for a very + // (`resetOnInstantFulfill` mechanics), it is technically possible for a very // quick tap to be 'complete' by the time we start trying to match // against it on some devices. We should still try in such cases. return source; diff --git a/common/web/gesture-recognizer/src/engine/headless/gestures/specs/gestureModel.ts b/common/web/gesture-recognizer/src/engine/headless/gestures/specs/gestureModel.ts index 8deb7b8b4a..4ce6484449 100644 --- a/common/web/gesture-recognizer/src/engine/headless/gestures/specs/gestureModel.ts +++ b/common/web/gesture-recognizer/src/engine/headless/gestures/specs/gestureModel.ts @@ -116,7 +116,7 @@ export interface GestureModel { * Only takes effect if a model instantly resolves or rejects upon being considered for * inclusion in the model. */ - resetOnResolve?: boolean, + resetOnInstantFulfill?: boolean, /** * Indicates that the corresponding GestureSource should be terminated whenever this GestureModel * is successfully matched. diff --git a/common/web/gesture-recognizer/src/test/auto/headless/gestures/isolatedGestureSpecs.ts b/common/web/gesture-recognizer/src/test/auto/headless/gestures/isolatedGestureSpecs.ts index f47d405736..afb3bc9365 100644 --- a/common/web/gesture-recognizer/src/test/auto/headless/gestures/isolatedGestureSpecs.ts +++ b/common/web/gesture-recognizer/src/test/auto/headless/gestures/isolatedGestureSpecs.ts @@ -124,7 +124,7 @@ export const SimpleTapModel: GestureModel = { endOnResolve: true }, { model: specs.InstantResolutionModel, - resetOnResolve: true + resetOnInstantFulfill: true } ], resolutionAction: { diff --git a/web/src/engine/osk/src/input/gestures/browser/flick.ts b/web/src/engine/osk/src/input/gestures/browser/flick.ts index e07c749efa..9778fa7650 100644 --- a/web/src/engine/osk/src/input/gestures/browser/flick.ts +++ b/web/src/engine/osk/src/input/gestures/browser/flick.ts @@ -118,7 +118,7 @@ export default class Flick implements GestureHandler { let source: GestureSource = baseSource; sequence.on('complete', () => { - previewHost.cancel() + previewHost?.cancel() }); this.sequence.on('stage', (result) => { diff --git a/web/src/engine/osk/src/input/gestures/specsForLayout.ts b/web/src/engine/osk/src/input/gestures/specsForLayout.ts index 9284265e3a..d8fc419a55 100644 --- a/web/src/engine/osk/src/input/gestures/specsForLayout.ts +++ b/web/src/engine/osk/src/input/gestures/specsForLayout.ts @@ -643,7 +643,8 @@ export function longpressModel(params: GestureParams, allowShortcut: boolean, al }, endOnResolve: false }, { - model: instantContactRejectionModel() + model: instantContactRejectionModel(), + resetOnInstantFulfill: true } ], resolutionAction: { @@ -815,7 +816,7 @@ export function flickMidModel(params: GestureParams): GestureModel { endOnReject: true, }, { model: instantContactRejectionModel(), - resetOnResolve: true, + resetOnInstantFulfill: true, } ], rejectionActions: { @@ -917,7 +918,7 @@ export function flickEndModel(params: GestureParams): GestureModel { }, { model: instantContactResolutionModel(), - resetOnResolve: true + resetOnInstantFulfill: true } ], rejectionActions: { @@ -980,7 +981,7 @@ export function multitapEndModel(params: GestureParams): GestureModel { endOnResolve: true }, { model: instantContactResolutionModel(), - resetOnResolve: true + resetOnInstantFulfill: true } ], rejectionActions: { @@ -1015,7 +1016,7 @@ export function initialTapModel(params: GestureParams): GestureModel { endOnResolve: true }, { model: instantContactResolutionModel(), - resetOnResolve: true + resetOnInstantFulfill: true } ], sustainWhenNested: true, @@ -1046,7 +1047,7 @@ export function simpleTapModel(params: GestureParams): GestureModel { endOnResolve: true }, { model: instantContactResolutionModel(), - resetOnResolve: true + resetOnInstantFulfill: true } ], sustainWhenNested: true, @@ -1160,7 +1161,7 @@ export function modipressHoldModel(params: GestureParams): GestureModel { }, // The incoming tap belongs to a different gesture; we just care to know that it // happened. - resetOnResolve: true + resetOnInstantFulfill: true } ], // To be clear: any time modipress-hold is triggered and the timer duration elapses, @@ -1284,7 +1285,7 @@ export function modipressMultitapEndModel(params: GestureParams): GestureModel Date: Thu, 18 Apr 2024 11:58:13 +0700 Subject: [PATCH 8/9] change(web): adjusts multitap timings --- web/src/engine/osk/src/input/gestures/specsForLayout.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/engine/osk/src/input/gestures/specsForLayout.ts b/web/src/engine/osk/src/input/gestures/specsForLayout.ts index 9284265e3a..4731816a53 100644 --- a/web/src/engine/osk/src/input/gestures/specsForLayout.ts +++ b/web/src/engine/osk/src/input/gestures/specsForLayout.ts @@ -109,8 +109,8 @@ export const DEFAULT_GESTURE_PARAMS: GestureParams = { noiseTolerance: 10 }, multitap: { - waitLength: 500, - holdLength: 500 + waitLength: 300, + holdLength: 150 }, // Note: all actual runtime values are determined at runtime based upon row height. // See `VisualKeyboard.refreshLayout`, CTRL-F "Step 3". From eba5b8a605bc4c518ca376dda96bcb7d466e2792 Mon Sep 17 00:00:00 2001 From: Keyman Build Agent Date: Thu, 18 Apr 2024 14:04:44 -0400 Subject: [PATCH 9/9] auto: increment beta version to 17.0.311 --- HISTORY.md | 8 ++++++++ VERSION.md | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/HISTORY.md b/HISTORY.md index e06635e252..06724796fa 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,13 @@ # Keyman Version History +## 17.0.310 beta 2024-04-18 + +* fix(web): now auto-scrolls if target element would be hidden after device rotation (#11210) +* fix(ios): deletion of selected text (#11179) +* fix(common): update emoji stripping for Unicode 15.1 (#11242) +* change(web): adjusts multitap timings (#11246) +* (#11245) + ## 17.0.309 beta 2024-04-17 * fix(ios): sample build script --debug detection (#10953) diff --git a/VERSION.md b/VERSION.md index cd50a0da6f..6b9c05fb33 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.310 \ No newline at end of file +17.0.311 \ No newline at end of file