Skip to content

Commit f252c42

Browse files
isaacclaude
andcommitted
feat(composer): localized rich-text editor placeholders + AI button/expand gating
Thread PresentationStrings into RichTextEditorChatInputNode so the composer editor's placeholders come from the new RichText.Placeholder{Quote,Code} strings. Gate the AI button on non-whitespace input content and the expand-input button on isAIEnabled, in the same input panel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent f932bb2 commit f252c42

3 files changed

Lines changed: 25 additions & 5 deletions

File tree

‎Telegram/Telegram-iOS/en.lproj/Localizable.strings‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16565,6 +16565,9 @@ Error: %8$@";
1656516565
"RichText.Menu.Table.Delete" = "Delete Table";
1656616566
"RichText.Menu.Table.HighlightOn" = "Highlight";
1656716567
"RichText.Menu.Table.HighlightOff" = "Remove Highlight";
16568+
"RichText.PlaceholderBody" = "Type something...";
16569+
"RichText.PlaceholderQuote" = "Type a quote here";
16570+
"RichText.PlaceholderCode" = "Type code here";
1656816571

1656916572
"TextProcessing.TitleAICompose" = "Create with AI";
1657016573
"TextProcessing.ActionAddToPage" = "Add to Page";

‎submodules/TelegramUI/Components/Chat/ChatRichTextEditorComposer/Sources/RichTextEditorChatInputNode.swift‎

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import RichTextEditorCore
1010
import RichTextEditorUIKit
1111
import ChatInputTextNode
1212
import CheckNode
13+
import TelegramPresentationData
1314

1415
/// `RichTextChecklistMarkerView` host wrapper backing a checklist item's checkbox with a `CheckNode`
1516
/// (an `ASDisplayNode`, so we host its `.view` — this is a `UIView`, not a node). The editor frames this
@@ -37,6 +38,8 @@ private final class HostChecklistCheckboxView: UIView, RichTextChecklistMarkerVi
3738
/// `ChatRichTextInputNode` backend composing the TextKit-2 `RichTextEditorView`.
3839
@available(iOS 13.0, *)
3940
public final class RichTextEditorChatInputNode: ASDisplayNode, ChatRichTextInputNode {
41+
private let strings: PresentationStrings
42+
4043
private let editorView = RichTextEditorView()
4144
private let baseFontSize: CGFloat = 17.0
4245

@@ -103,11 +106,13 @@ public final class RichTextEditorChatInputNode: ASDisplayNode, ChatRichTextInput
103106

104107
public var asNode: ASDisplayNode { self }
105108

106-
public override init() {
109+
public init(strings: PresentationStrings) {
107110
#if DEBUG && false
108111
RichTextEditorView.debugShowLayoutOverlay = true
109112
#endif
110113

114+
self.strings = strings
115+
111116
super.init()
112117
}
113118

@@ -142,7 +147,8 @@ public final class RichTextEditorChatInputNode: ASDisplayNode, ChatRichTextInput
142147
RichTextEditorChatInputNode.applyComposerLayoutMetrics(to: self.editorView)
143148
// Suppress the editor's built-in placeholders ("Type something…" / list hints): the chat input panel
144149
// draws its own placeholder ("Message", etc.), so the editor's would double up.
145-
self.editorView.placeholders = RichTextEditorPlaceholders(body: "", listEnd: "", listOutdent: "", pullQuote: "Type a quote here", blockQuote: "Type a quote here", codeBlock: "Type code here")
150+
151+
self.editorView.placeholders = RichTextEditorPlaceholders(body: "", listEnd: "", listOutdent: "", pullQuote: self.strings.RichText_PlaceholderQuote, blockQuote: self.strings.RichText_PlaceholderQuote, codeBlock: self.strings.RichText_PlaceholderCode)
146152
// The composer sits over the input panel's own background — clear the editor's document "page"
147153
// background (`.systemBackground`, opaque white in light mode) so the panel shows through. `nil`
148154
// (no background) rather than `.clear`: same transparency, but signals "unset" and avoids an

‎submodules/TelegramUI/Components/Chat/ChatTextInputPanelNode/Sources/ChatTextInputPanelNode.swift‎

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
307307

308308
private var accessoryItemButtons: [(ChatTextInputAccessoryItem, AccessoryItemIconButton)] = []
309309

310+
private var isUpdating: Bool = false
310311
private var validLayout: (CGFloat, CGFloat, CGFloat, CGFloat, UIEdgeInsets, CGFloat, CGFloat, LayoutMetrics, Bool, Bool, DeviceMetrics)?
311312
private var leftMenuInset: CGFloat = 0.0
312313
private var rightSlowModeInset: CGFloat = 0.0
@@ -1189,7 +1190,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
11891190
private func loadTextInputNode(useNative: Bool = false) {
11901191
let richTextInputNode: ChatRichTextInputNode
11911192
if useNative {
1192-
richTextInputNode = RichTextEditorChatInputNode()
1193+
richTextInputNode = RichTextEditorChatInputNode(strings: self.presentationInterfaceState?.strings ?? defaultPresentationStrings)
11931194
} else {
11941195
richTextInputNode = makeChatRichTextInputNode()
11951196
}
@@ -1617,6 +1618,9 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
16171618
}
16181619

16191620
public func requestLayout(transition: ContainedViewLayoutTransition = .immediate) {
1621+
if self.isUpdating {
1622+
return
1623+
}
16201624
guard let presentationInterfaceState = self.presentationInterfaceState, let (width, leftInset, rightInset, bottomInset, additionalSideInsets, maxHeight, maxOverlayHeight, metrics, isSecondary, isMediaInputExpanded, deviceMetrics) = self.validLayout else {
16211625
return
16221626
}
@@ -1638,6 +1642,11 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
16381642
deviceMetrics: DeviceMetrics,
16391643
isMediaInputExpanded: Bool
16401644
) -> CGFloat {
1645+
self.isUpdating = true
1646+
defer {
1647+
self.isUpdating = false
1648+
}
1649+
16411650
let isFirstTime = self.validLayout == nil
16421651

16431652
let previousAdditionalSideInsets = self.validLayout?.4
@@ -3538,7 +3547,8 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
35383547
// grows UPWARD into a pill (bottom edge stays at textInputFrame.maxY): + at the bottom slot, AI at the top.
35393548
var isAIButtonVisible = false
35403549
var attachmentPillHeight: CGFloat = 40.0
3541-
if self.isAIEnabled, let node = self.richTextInputNode {
3550+
let inputHasNonWhitespaceText = !(self.richTextInputNode?.inputContentIsEmptyWhitespaceTrimmed ?? true)
3551+
if self.isAIEnabled, inputHasNonWhitespaceText, let node = self.richTextInputNode {
35423552
let threeLineHeight = self.threeLineFieldHeight(forWidth: baseWidth, node: node, metrics: metrics, bottomInset: bottomInset, textFieldInsets: textFieldInsets)
35433553
if textInputHeight >= threeLineHeight - 0.5 {
35443554
isAIButtonVisible = true
@@ -3823,7 +3833,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
38233833
)
38243834
}
38253835

3826-
let isExpandInputEnabled = self.enableRichTextInput
3836+
let isExpandInputEnabled = self.enableRichTextInput && self.isAIEnabled
38273837

38283838
if isExpandInputEnabled {
38293839
let expandButton: (button: HighlightTrackingButton, icon: UIImageView)
@@ -4779,6 +4789,7 @@ public class ChatTextInputPanelNode: ChatInputPanelNode, ASEditableTextNodeDeleg
47794789
if !self.bounds.size.height.isEqual(to: panelHeight) {
47804790
self.updateHeight(animated)
47814791
} else {
4792+
self.requestLayout(transition: .immediate)
47824793
if let richTextInputNode = self.richTextInputNode {
47834794
self.updateInputField(textInputFrame: richTextInputNode.textFieldFrame, transition: .immediate)
47844795
}

0 commit comments

Comments
 (0)