Files
ichmagmaus111andClaude Opus 5.5 0622057a4f Ghostgram: open-source release
Ghostgram for iOS, rebuilt from scratch on Telegram iOS 13.0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-31 17:57:09 +01:00

92 lines
4.0 KiB
Swift

import Foundation
import TelegramCore
import TextFormat
import RichTextEditorCore
/// Build a normal message (text + entities) from entity-expressible blocks (body/quote paragraphs).
func buildEntityMessage(from blocks: [Block]) -> (text: String, entities: [MessageTextEntity]) {
let result = NSMutableAttributedString()
let marker = true as NSNumber
var isFirstParagraph = true
for block in blocks {
if case let .code(code) = block {
if !isFirstParagraph {
result.append(NSAttributedString(string: "\n"))
}
isFirstParagraph = false
let start = result.length
result.append(NSAttributedString(string: code.text)) // code runs are always plain text
let len = result.length - start
if len > 0 {
let attr = chatInputCodeBlockAttribute(language: code.language)
result.addAttribute(attr.key, value: attr.value, range: NSRange(location: start, length: len))
}
continue
}
// A `.buttonRow` (and any other non-paragraph, non-code block) is skipped here. A button always
// forces the rich path in `documentNeedsRichLayout`, so this is unreachable for one.
guard case let .paragraph(paragraph) = block else {
continue
}
if !isFirstParagraph {
result.append(NSAttributedString(string: "\n"))
}
isFirstParagraph = false
for run in paragraph.runs {
if let emoji = run.attributes.emoji {
// Mirror ComposerDocumentBridge: re-emit a customEmoji run; generateChatInputTextEntities
// converts it to a .CustomEmoji(fileId:) entity. Prefer altText; fall back to U+FFFC. A
// non-numeric id degrades to plain text rather than crashing.
let displayText = (emoji.altText?.isEmpty == false) ? emoji.altText! : "\u{FFFC}"
let piece = NSMutableAttributedString(string: displayText)
let range = NSRange(location: 0, length: piece.length)
if let fileId = Int64(emoji.id) {
piece.addAttribute(
ChatTextInputAttributes.customEmoji,
value: ChatTextInputTextCustomEmojiAttribute(interactivelySelectedFromPackId: nil, fileId: fileId, file: nil),
range: range
)
}
result.append(piece)
continue
}
let text = run.text
if text.isEmpty {
continue
}
let piece = NSMutableAttributedString(string: text)
let range = NSRange(location: 0, length: piece.length)
let attributes = run.attributes
if attributes.bold {
piece.addAttribute(ChatTextInputAttributes.bold, value: marker, range: range)
}
if attributes.italic {
piece.addAttribute(ChatTextInputAttributes.italic, value: marker, range: range)
}
if attributes.inlineCode {
piece.addAttribute(ChatTextInputAttributes.monospace, value: marker, range: range)
}
if attributes.strikethrough {
piece.addAttribute(ChatTextInputAttributes.strikethrough, value: marker, range: range)
}
if attributes.underline {
piece.addAttribute(ChatTextInputAttributes.underline, value: marker, range: range)
}
if attributes.spoiler {
piece.addAttribute(ChatTextInputAttributes.spoiler, value: marker, range: range)
}
if let link = attributes.link {
let attribute = chatInputLinkAttribute(forLink: link)
piece.addAttribute(attribute.key, value: attribute.value, range: range)
}
result.append(piece)
}
}
let entities = generateChatInputTextEntities(result)
return (result.string, entities)
}