mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 15:40:44 +00:00
fix(macOS): enable undo/redo in webchat composer text input (#34962)
* fix(macOS): enable undo/redo in webchat composer text input Set `allowsUndo = true` on ChatComposerNSTextView in makeNSView(). NSTextView defaults allowsUndo to false, which prevented Cmd+Z and the Edit menu Undo/Redo items from functioning. Fixes #34898 * fix(macos): enable webchat composer undo/redo (#34962) (thanks @tylerbittner) --------- Co-authored-by: Nimrod Gutman <nimrod.gutman@gmail.com>
This commit is contained in:
@@ -444,34 +444,18 @@ private struct ChatComposerTextView: NSViewRepresentable {
|
||||
func makeCoordinator() -> Coordinator { Coordinator(self) }
|
||||
|
||||
func makeNSView(context: Context) -> NSScrollView {
|
||||
let textView = ChatComposerNSTextView()
|
||||
textView.delegate = context.coordinator
|
||||
textView.drawsBackground = false
|
||||
textView.isRichText = false
|
||||
textView.isAutomaticQuoteSubstitutionEnabled = false
|
||||
textView.isAutomaticTextReplacementEnabled = false
|
||||
textView.isAutomaticDashSubstitutionEnabled = false
|
||||
textView.isAutomaticSpellingCorrectionEnabled = false
|
||||
textView.font = .systemFont(ofSize: 14, weight: .regular)
|
||||
textView.textContainer?.lineBreakMode = .byWordWrapping
|
||||
textView.textContainer?.lineFragmentPadding = 0
|
||||
textView.textContainerInset = NSSize(width: 2, height: 4)
|
||||
textView.focusRingType = .none
|
||||
let textView = ChatComposerTextViewFactory.makeConfiguredTextView()
|
||||
guard let composerTextView = textView as? ChatComposerNSTextView else {
|
||||
preconditionFailure("ChatComposerTextViewFactory must return ChatComposerNSTextView")
|
||||
}
|
||||
composerTextView.delegate = context.coordinator
|
||||
|
||||
textView.minSize = .zero
|
||||
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
|
||||
textView.isHorizontallyResizable = false
|
||||
textView.isVerticallyResizable = true
|
||||
textView.autoresizingMask = [.width]
|
||||
textView.textContainer?.containerSize = NSSize(width: 0, height: CGFloat.greatestFiniteMagnitude)
|
||||
textView.textContainer?.widthTracksTextView = true
|
||||
|
||||
textView.string = self.text
|
||||
textView.onSend = { [weak textView] in
|
||||
textView?.window?.makeFirstResponder(nil)
|
||||
composerTextView.string = self.text
|
||||
composerTextView.onSend = { [weak composerTextView] in
|
||||
composerTextView?.window?.makeFirstResponder(nil)
|
||||
self.onSend()
|
||||
}
|
||||
textView.onPasteImageAttachment = self.onPasteImageAttachment
|
||||
composerTextView.onPasteImageAttachment = self.onPasteImageAttachment
|
||||
|
||||
let scroll = NSScrollView()
|
||||
scroll.drawsBackground = false
|
||||
@@ -522,6 +506,34 @@ private struct ChatComposerTextView: NSViewRepresentable {
|
||||
}
|
||||
}
|
||||
|
||||
enum ChatComposerTextViewFactory {
|
||||
// Internal for @testable import coverage of composer text view defaults.
|
||||
@MainActor
|
||||
static func makeConfiguredTextView() -> NSTextView {
|
||||
let textView = ChatComposerNSTextView()
|
||||
textView.drawsBackground = false
|
||||
textView.isRichText = false
|
||||
textView.isAutomaticQuoteSubstitutionEnabled = false
|
||||
textView.isAutomaticTextReplacementEnabled = false
|
||||
textView.isAutomaticDashSubstitutionEnabled = false
|
||||
textView.isAutomaticSpellingCorrectionEnabled = false
|
||||
textView.font = .systemFont(ofSize: 14, weight: .regular)
|
||||
textView.textContainer?.lineBreakMode = .byWordWrapping
|
||||
textView.textContainer?.lineFragmentPadding = 0
|
||||
textView.textContainerInset = NSSize(width: 2, height: 4)
|
||||
textView.focusRingType = .none
|
||||
textView.allowsUndo = true
|
||||
textView.minSize = .zero
|
||||
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
|
||||
textView.isHorizontallyResizable = false
|
||||
textView.isVerticallyResizable = true
|
||||
textView.autoresizingMask = [.width]
|
||||
textView.textContainer?.containerSize = NSSize(width: 0, height: CGFloat.greatestFiniteMagnitude)
|
||||
textView.textContainer?.widthTracksTextView = true
|
||||
return textView
|
||||
}
|
||||
}
|
||||
|
||||
private final class ChatComposerNSTextView: NSTextView {
|
||||
var onSend: (() -> Void)?
|
||||
var onPasteImageAttachment: ((_ data: Data, _ fileName: String, _ mimeType: String) -> Void)?
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
#if os(macOS)
|
||||
import AppKit
|
||||
import Testing
|
||||
@testable import OpenClawChatUI
|
||||
|
||||
@Suite
|
||||
@MainActor
|
||||
struct ChatComposerTextViewTests {
|
||||
@Test func configuredComposerTextViewEnablesUndo() {
|
||||
let textView = ChatComposerTextViewFactory.makeConfiguredTextView()
|
||||
|
||||
#expect(textView.allowsUndo)
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user