mirror of
https://github.com/chatwoot/chatwoot.git
synced 2026-06-13 21:01:16 +08:00
# Pull Request Template ## Description This PR includes, On Windows, pressing **Ctrl+Enter** in the reply editor was inserting an unintended line break before sending. This led to two issues: * **Unexpected blank lines** After adding a line break with Shift+Enter and removing it with Backspace, the editor looked correct. However, sending with Ctrl+Enter reintroduced a hidden break, resulting in an extra blank line in the final message. * **Selected text being replaced** When text was selected and Ctrl+Enter was pressed, the selection was replaced with a line break instead of being sent. Fixes https://linear.app/chatwoot/issue/CW-6840/newline-bug-in-the-editor ### **Cause** Two keyboard handlers responded to **Ctrl+Enter** on Windows: * ProseMirror (`Mod-Enter`) inserted a hard break * ReplyBox (`$mod+Enter`) triggered send The existing guard only checked `metaKey` (Cmd), so it never worked on Windows. As a result, a line break was inserted just before sending. ### **Solution** Make the modifier check platform-aware so the editor correctly intercepts the send shortcut: * Added `detectOS`, `isMac`, and `OS` constants * Introduced `hasPressedMod` (uses `metaKey` on macOS, `ctrlKey` elsewhere) This ensures Ctrl+Enter sends the message without modifying content, while keeping existing behavior unchanged. **NB:** macOS behavior with Cmd+Enter remains unchanged ## Type of change - [x] Bug fix (non-breaking change which fixes an issue) ## How Has This Been Tested? **Case 1: line break** 1. Type `hello` 2. Press Shift+Enter, then Backspace 3. Press Ctrl+Enter → Message contains an unexpected blank new line **Case 2: Selection replaced** 1. Type two lines using Shift+Enter 2. Select text on the second line 3. Press Ctrl+Enter → Selected text is replaced and not sent ### Screencast **Before** https://github.com/user-attachments/assets/d6d285a9-260b-4711-8bbd-d0c8519e8d20 **After** https://github.com/user-attachments/assets/c0ace1f7-5d22-44a2-8e08-22190ee21e61 ## Checklist: - [x] My code follows the style guidelines of this project - [x] I have performed a self-review of my code - [x] I have commented on my code, particularly in hard-to-understand areas - [ ] I have made corresponding changes to the documentation - [x] My changes generate no new warnings - [x] I have added tests that prove my fix is effective or that my feature works - [x] New and existing unit tests pass locally with my changes - [ ] Any dependent changes have been merged and published in downstream modules
51 lines
1.4 KiB
JavaScript
51 lines
1.4 KiB
JavaScript
// Detects the current OS using the modern User-Agent Client Hints API,
|
|
// falling back to userAgent parsing on Safari/Firefox where it is unavailable.
|
|
// Treats iPad on iOS 13+ (which spoofs Macintosh) as iOS via maxTouchPoints.
|
|
|
|
export const OS = Object.freeze({
|
|
MAC: 'macos',
|
|
WINDOWS: 'windows',
|
|
LINUX: 'linux',
|
|
ANDROID: 'android',
|
|
IOS: 'ios',
|
|
UNKNOWN: 'unknown',
|
|
});
|
|
|
|
// navigator.userAgentData.platform → OS constant (lowercased keys)
|
|
const UAD_MAP = {
|
|
macos: OS.MAC,
|
|
windows: OS.WINDOWS,
|
|
linux: OS.LINUX,
|
|
android: OS.ANDROID,
|
|
ios: OS.IOS,
|
|
};
|
|
|
|
export function detectOS() {
|
|
if (typeof navigator === 'undefined') return OS.UNKNOWN;
|
|
|
|
// Trust userAgentData only when it maps to a known OS; otherwise fall
|
|
// through to UA parsing so unmapped values (e.g. "Chrome OS") don't leak.
|
|
const uad = navigator.userAgentData?.platform?.toLowerCase();
|
|
if (uad && UAD_MAP[uad]) return UAD_MAP[uad];
|
|
|
|
const ua = navigator.userAgent || '';
|
|
if (/android/i.test(ua)) return OS.ANDROID;
|
|
if (/iPhone|iPod/.test(ua)) return OS.IOS;
|
|
if (
|
|
/iPad/.test(ua) ||
|
|
(/Macintosh/.test(ua) && (navigator.maxTouchPoints || 0) > 1)
|
|
) {
|
|
return OS.IOS;
|
|
}
|
|
if (/Win/i.test(ua)) return OS.WINDOWS;
|
|
if (/Mac/i.test(ua)) return OS.MAC;
|
|
if (/Linux/i.test(ua)) return OS.LINUX;
|
|
|
|
return OS.UNKNOWN;
|
|
}
|
|
|
|
export const isApple = () => {
|
|
const os = detectOS();
|
|
return os === OS.MAC || os === OS.IOS;
|
|
};
|