Horizontally scrollable code blocks (#5001)

* Remove attributed string backed codeblock background color

* Add code block support to attributed string componentization

* Render code blocks within their own custom horizontal scroll view within the timeline

* Update preview test snapshots

* Introduce a attributed string component type instead of a 2 different booleans.
This commit is contained in:
Stefan Ceriu
2026-01-26 12:39:55 +02:00
committed by GitHub
parent bb801d0be1
commit cf79cb9b01
9 changed files with 75 additions and 36 deletions
@@ -81,34 +81,53 @@ struct FormattedBodyText: View {
// Ignore if the string contains only the layout correction
if String(component.attributedString.characters) == layoutDirection.isolateLayoutUnicodeString {
EmptyView()
} else if component.isBlockquote {
// The rendered blockquote with a greedy width. The custom layout prevents the
// infinite width from increasing the overall width of the view.
MessageText(attributedString: component.attributedString.mergingAttributes(blockquoteAttributes))
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 12.0)
.overlay(alignment: .leading) {
// User an overlay here so that the rectangle's infinite height doesn't take priority
Capsule()
.frame(width: 2.0)
.padding(.leading, 5.0)
.foregroundColor(.compound.textSecondary)
.padding(.vertical, 2)
}
.layoutPriority(TimelineBubbleLayout.Priority.visibleQuote)
} else {
MessageText(attributedString: component.attributedString)
.padding(.horizontal, 4)
switch component.type {
case .blockquote:
// The rendered blockquote with a greedy width. The custom layout prevents the
// infinite width from increasing the overall width of the view.
MessageText(attributedString: component.attributedString.mergingAttributes(blockquoteAttributes))
.fixedSize(horizontal: false, vertical: true)
.frame(maxWidth: .infinity, alignment: .leading)
.padding(.leading, 12.0)
.overlay(alignment: .leading) {
// User an overlay here so that the rectangle's infinite height doesn't take priority
Capsule()
.frame(width: 2.0)
.padding(.leading, 5.0)
.foregroundColor(.compound.textSecondary)
.padding(.vertical, 2)
}
.layoutPriority(TimelineBubbleLayout.Priority.visibleQuote)
case .codeBlock:
ScrollView(.horizontal) {
MessageText(attributedString: component.attributedString)
.padding([.horizontal, .top], 4)
.padding(.bottom, 8)
}
.background(.compound._bgCodeBlock)
.scrollIndicatorsFlash(onAppear: true)
.fixedSize(horizontal: false, vertical: true)
.padding(.horizontal, 4)
.layoutPriority(TimelineBubbleLayout.Priority.regularText)
.contextMenu {
Button(L10n.actionCopy) {
UIPasteboard.general.string = component.attributedString.string
}
}
case .plainText:
MessageText(attributedString: component.attributedString)
.padding(.horizontal, 4)
.fixedSize(horizontal: false, vertical: true)
.layoutPriority(TimelineBubbleLayout.Priority.regularText)
}
}
}
// Make a second iteration through the components adding fixed width blockquotes
// which are used for layout calculations but won't be rendered.
ForEach(attributedComponents) { component in
if component.isBlockquote {
if case .blockquote = component.type {
MessageText(attributedString: component.attributedString.mergingAttributes(blockquoteAttributes))
.fixedSize(horizontal: false, vertical: true)
.padding(.leading, 12.0)