@@ -9,89 +9,93 @@ import LaTeXSwiftUI
99import MarkdownUI
1010import Splash
1111import SwiftUI
12- import Shimmer
1312
1413struct MessageContentView : View {
15-
16- @Environment ( \. colorScheme) private var colorScheme
1714
18- @State private var cachedMarkdown : AnyView ? // Cache rendered markdown view
19-
20- var text : String
21-
22- var textLatexProcessed : String {
23- return self . text. convertLaTeX ( )
24- }
25-
26- let imageScaleFactor : CGFloat = 1.0
27-
28- private var theme : Splash . Theme {
29- switch self . colorScheme {
30- case . dark: return . wwdc17( withFont: . init( size: 16 ) )
31- default : return . sunset( withFont: . init( size: 16 ) )
32- }
33- }
34-
35- var body : some View {
36- VStack ( alignment: . leading) {
37- self . content
38- }
39- . onChange ( of: self . textLatexProcessed) { _, newText in
40- self . updateCachedMarkdown ( newText) // Only redraw when text changes
41- }
42- . onAppear {
43- if self . cachedMarkdown == nil {
44- self . updateCachedMarkdown (
45- self . textLatexProcessed
46- ) // Cache on appear
47- }
48- }
49- }
50-
51- @ViewBuilder
52- var content : some View {
53- if let cached = cachedMarkdown {
54- cached // Used cached view if available
55- } else {
56- self . markdownView (
57- self . textLatexProcessed
58- )
59- }
60- }
61-
62- @ViewBuilder
63- private func markdownView( _ text: String ) -> some View {
64- Markdown ( MarkdownContent ( text) )
65- . markdownTheme ( . gitHub)
66- . markdownCodeSyntaxHighlighter ( . splash( theme: self . theme) )
67- . markdownImageProvider (
68- MarkdownImageProvider ( scaleFactor: imageScaleFactor)
69- )
70- . markdownInlineImageProvider (
71- MarkdownInlineImageProvider (
72- scaleFactor: imageScaleFactor
73- )
74- )
75- . textSelection ( . enabled)
76- }
77-
78- private func updateCachedMarkdown( _ newText: String ) {
79- self . cachedMarkdown = AnyView (
80- Markdown ( MarkdownContent ( newText) )
81- . markdownTheme ( . gitHub)
82- . markdownCodeSyntaxHighlighter ( . splash( theme: self . theme) )
83- . markdownImageProvider (
84- MarkdownImageProvider (
85- scaleFactor: imageScaleFactor
86- )
87- )
88- . markdownInlineImageProvider (
89- MarkdownInlineImageProvider (
90- scaleFactor: imageScaleFactor
91- )
92- )
93- . textSelection ( . enabled)
94- )
95- }
96-
15+ @Environment ( \. colorScheme) private var colorScheme
16+
17+ // Asynchronously rendered markdown cached view.
18+ @State private var cachedMarkdown : AnyView ? = nil
19+ // Stores the processed text after LaTeX conversion.
20+ @State private var processedText : String = " "
21+ // Used to debounce rapid changes.
22+ @State private var debounceWorkItem : DispatchWorkItem ?
23+
24+ var text : String
25+ private let imageScaleFactor : CGFloat = 1.0
26+
27+ // Computes the code highlighting theme from the color scheme.
28+ private var theme : Splash . Theme {
29+ switch colorScheme {
30+ case . dark:
31+ return . wwdc17( withFont: . init( size: 16 ) )
32+ default :
33+ return . sunset( withFont: . init( size: 16 ) )
34+ }
35+ }
36+
37+ var body : some View {
38+ VStack ( alignment: . leading) {
39+ // Always render the inline markdown immediately using processedText
40+ // This provides quick visual feedback while the cached version is updated
41+ if let cached = cachedMarkdown {
42+ cached
43+ } else {
44+ markdownView ( processedText. isEmpty ? text. convertLaTeX ( ) : processedText)
45+ }
46+ }
47+ . onAppear {
48+ // Immediately update processed text and asynchronously update the cached markdown
49+ updateProcessedTextAndMarkdown ( text)
50+ }
51+ . onChange ( of: self . text) { _, newText in
52+ updateProcessedTextAndMarkdown ( newText)
53+ }
54+ }
55+
56+ /// Function to render a markdown view for the provided text
57+ @ViewBuilder
58+ private func markdownView( _ text: String ) -> some View {
59+ Markdown ( MarkdownContent ( text) )
60+ . markdownTheme ( . gitHub)
61+ . markdownCodeSyntaxHighlighter ( . splash( theme: theme) )
62+ . markdownImageProvider ( MarkdownImageProvider ( scaleFactor: imageScaleFactor) )
63+ . markdownInlineImageProvider ( MarkdownInlineImageProvider ( scaleFactor: imageScaleFactor) )
64+ . textSelection ( . enabled)
65+ }
66+
67+ /// Function to update the processed text and debounce the cached markdown update
68+ private func updateProcessedTextAndMarkdown( _ newText: String ) {
69+ let converted = newText. convertLaTeX ( )
70+ // Update inline view immediately.
71+ processedText = converted
72+
73+ // Cancel any pending update.
74+ debounceWorkItem? . cancel ( )
75+
76+ // Shorten debounce time to reduce perceived delay (e.g., 0.1 second).
77+ let workItem = DispatchWorkItem { [ converted] in
78+ updateCachedMarkdown ( with: converted)
79+ }
80+ debounceWorkItem = workItem
81+ DispatchQueue . main. asyncAfter ( deadline: . now( ) + 0.1 , execute: workItem)
82+ }
83+
84+ /// Function to asynchronously render and update the cached markdown view.
85+ private func updateCachedMarkdown( with text: String ) {
86+ DispatchQueue . global ( qos: . userInitiated) . async {
87+ let rendered = AnyView (
88+ Markdown ( MarkdownContent ( text) )
89+ . markdownTheme ( . gitHub)
90+ . markdownCodeSyntaxHighlighter ( . splash( theme: self . theme) )
91+ . markdownImageProvider ( MarkdownImageProvider ( scaleFactor: self . imageScaleFactor) )
92+ . markdownInlineImageProvider ( MarkdownInlineImageProvider ( scaleFactor: self . imageScaleFactor) )
93+ . textSelection ( . enabled)
94+ )
95+ DispatchQueue . main. async {
96+ self . cachedMarkdown = rendered
97+ }
98+ }
99+ }
100+
97101}
0 commit comments