From 888b12405b65cdb1d797f2a43d8be30ade449ed7 Mon Sep 17 00:00:00 2001 From: Isaac <> Date: Fri, 14 Nov 2025 21:19:08 +0800 Subject: [PATCH] Filter consecutive newlines --- .../Sources/ChatTextInputPanelComponent.swift | 8 ++++ .../Sources/ChatTextInputPanelNode.swift | 48 +++++++++++++------ .../Sources/MessageInputPanelComponent.swift | 1 + 3 files changed, 42 insertions(+), 15 deletions(-) diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift index c90c972c29..88c90e22f5 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelComponent.swift @@ -182,6 +182,7 @@ public final class ChatTextInputPanelComponent: Component { let insets: UIEdgeInsets let maxHeight: CGFloat let maxLength: Int? + let allowConsecutiveNewlines: Bool let sendAction: (() -> Void)? let sendContextAction: ((UIView, ContextGesture) -> Void)? @@ -206,6 +207,7 @@ public final class ChatTextInputPanelComponent: Component { insets: UIEdgeInsets, maxHeight: CGFloat, maxLength: Int?, + allowConsecutiveNewlines: Bool, sendAction: (() -> Void)?, sendContextAction: ((UIView, ContextGesture) -> Void)? ) { @@ -229,6 +231,7 @@ public final class ChatTextInputPanelComponent: Component { self.insets = insets self.maxHeight = maxHeight self.maxLength = maxLength + self.allowConsecutiveNewlines = allowConsecutiveNewlines self.sendAction = sendAction self.sendContextAction = sendContextAction } @@ -294,6 +297,9 @@ public final class ChatTextInputPanelComponent: Component { if lhs.maxLength != rhs.maxLength { return false } + if lhs.allowConsecutiveNewlines != rhs.allowConsecutiveNewlines { + return false + } if (lhs.sendAction == nil) != (rhs.sendAction == nil) { return false } @@ -1013,6 +1019,8 @@ public final class ChatTextInputPanelComponent: Component { } } + panelNode.allowConsecutiveNewlines = component.allowConsecutiveNewlines + if let resetInputState = component.externalState.resetInputState { component.externalState.resetInputState = nil let _ = resetInputState diff --git a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift index 16ebe0de79..903663967c 100644 --- a/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift +++ b/submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift @@ -407,6 +407,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg public var customSendIsDisabled: Bool = false public var customInputTextMaxLength: Int? public var customSwitchToKeyboard: (() -> Void)? + public var allowConsecutiveNewlines = true private var starReactionButton: ComponentView? private var liveMicrophoneButton: ComponentView? @@ -4952,24 +4953,41 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg } } } - + + let string = NSMutableAttributedString(attributedString: editableTextNode.attributedText ?? NSAttributedString()) + var textColor: UIColor = .black + var accentTextColor: UIColor = .blue + var baseFontSize: CGFloat = 17.0 + if let presentationInterfaceState = self.presentationInterfaceState { + textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor + accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor + baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) + if "".isEmpty { + baseFontSize = 17.0 + } + } + let cleanReplacementString = textAttributedStringForStateText(context: context, stateText: NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in + return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) + }) + string.replaceCharacters(in: range, with: cleanReplacementString) + + var resetText = false if cleanText != text { - let string = NSMutableAttributedString(attributedString: editableTextNode.attributedText ?? NSAttributedString()) - var textColor: UIColor = .black - var accentTextColor: UIColor = .blue - var baseFontSize: CGFloat = 17.0 - if let presentationInterfaceState = self.presentationInterfaceState { - textColor = presentationInterfaceState.theme.chat.inputPanel.inputTextColor - accentTextColor = presentationInterfaceState.theme.chat.inputPanel.panelControlAccentColor - baseFontSize = max(minInputFontSize, presentationInterfaceState.fontSize.baseDisplaySize) - if "".isEmpty { - baseFontSize = 17.0 + resetText = true + } + + if !self.allowConsecutiveNewlines { + while string.string.range(of: "\n\n") != nil { + if let range = string.string.range(of: "\n\n") { + let rawRange = NSRange(range, in: string.string) + let firstNewline = string.attributedSubstring(from: NSRange(location: rawRange.location, length: 1)) + string.replaceCharacters(in: rawRange, with: firstNewline) + resetText = true } } - let cleanReplacementString = textAttributedStringForStateText(context: context, stateText: NSAttributedString(string: cleanText), fontSize: baseFontSize, textColor: textColor, accentTextColor: accentTextColor, writingDirection: nil, spoilersRevealed: self.spoilersRevealed, availableEmojis: (self.context?.animatedEmojiStickersValue.keys).flatMap(Set.init) ?? Set(), emojiViewProvider: self.emojiViewProvider, makeCollapsedQuoteAttachment: { text, attributes in - return ChatInputTextCollapsedQuoteAttachmentImpl(text: text, attributes: attributes) - }) - string.replaceCharacters(in: range, with: cleanReplacementString) + } + + if resetText { self.textInputNode?.attributedText = string self.textInputNode?.selectedRange = NSMakeRange(range.lowerBound + cleanReplacementString.length, 0) self.updateTextNodeText(animated: true) diff --git a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift index 86aa8224ef..870d8f3eec 100644 --- a/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift +++ b/submodules/TelegramUI/Components/MessageInputPanelComponent/Sources/MessageInputPanelComponent.swift @@ -1125,6 +1125,7 @@ public final class MessageInputPanelComponent: Component { insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: component.bottomInset, right: 0.0), maxHeight: availableSize.height, maxLength: component.maxLength, + allowConsecutiveNewlines: false, sendAction: { [weak self] in guard let self, let component = self.component else { return