From 069084cec3ca846bf183ae60788c5e50304c1e09 Mon Sep 17 00:00:00 2001 From: Augustinas Malinauskas Date: Sat, 25 May 2024 15:26:16 +0100 Subject: [PATCH] aesthetics --- Enchanted.xcodeproj/project.pbxproj | 18 +- .../Chat/Components/ChatMessageView.swift | 352 ------------------ .../ChatMessages/ChatMessageView.swift | 143 +++++++ .../ChatMessages/CodeBlockView.swift | 55 +++ .../ChatMessages/MarkdownColours.swift | 175 +++++++++ 5 files changed, 390 insertions(+), 353 deletions(-) delete mode 100644 Enchanted/UI/Shared/Chat/Components/ChatMessageView.swift create mode 100644 Enchanted/UI/Shared/Chat/Components/ChatMessages/ChatMessageView.swift create mode 100644 Enchanted/UI/Shared/Chat/Components/ChatMessages/CodeBlockView.swift create mode 100644 Enchanted/UI/Shared/Chat/Components/ChatMessages/MarkdownColours.swift diff --git a/Enchanted.xcodeproj/project.pbxproj b/Enchanted.xcodeproj/project.pbxproj index ff61253..74d5d68 100644 --- a/Enchanted.xcodeproj/project.pbxproj +++ b/Enchanted.xcodeproj/project.pbxproj @@ -78,6 +78,8 @@ FFBBF4882B34F9C8008D611C /* View+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBBF4872B34F9C8008D611C /* View+Extension.swift */; }; FFBBF48A2B350283008D611C /* SelectedImageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBBF4892B350283008D611C /* SelectedImageView.swift */; }; FFBBF48C2B35051D008D611C /* UIImage+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFBBF48B2B35051D008D611C /* UIImage+Extension.swift */; }; + FFD57E302BF29145003FEFF1 /* MarkdownColours.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD57E2F2BF29145003FEFF1 /* MarkdownColours.swift */; }; + FFD57E322BF291B2003FEFF1 /* CodeBlockView.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFD57E312BF291B2003FEFF1 /* CodeBlockView.swift */; }; FFD5FAD22B8130490055AB51 /* Vortex in Frameworks */ = {isa = PBXBuildFile; productRef = FF464B122B8026DA008E5130 /* Vortex */; }; FFD5FAD52B8130CE0055AB51 /* OllamaKit in Frameworks */ = {isa = PBXBuildFile; productRef = FFD5FAD42B8130CE0055AB51 /* OllamaKit */; }; FFE21C782B82353A00A69B9C /* SleepTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FFE21C772B82353A00A69B9C /* SleepTest.swift */; }; @@ -167,6 +169,8 @@ FFBBF4872B34F9C8008D611C /* View+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Extension.swift"; sourceTree = ""; }; FFBBF4892B350283008D611C /* SelectedImageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SelectedImageView.swift; sourceTree = ""; }; FFBBF48B2B35051D008D611C /* UIImage+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage+Extension.swift"; sourceTree = ""; }; + FFD57E2F2BF29145003FEFF1 /* MarkdownColours.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownColours.swift; sourceTree = ""; }; + FFD57E312BF291B2003FEFF1 /* CodeBlockView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CodeBlockView.swift; sourceTree = ""; }; FFE21C772B82353A00A69B9C /* SleepTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SleepTest.swift; sourceTree = ""; }; FFE2C8222B9A657A00BD82F3 /* Accessibility.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Accessibility.plist; sourceTree = ""; }; FFEB9CA72BA04304004B1F3D /* NotificationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMessage.swift; sourceTree = ""; }; @@ -206,7 +210,7 @@ FF10022C2B2481790011A4DC /* Components */ = { isa = PBXGroup; children = ( - FF10022F2B2482BA0011A4DC /* ChatMessageView.swift */, + FFD57E2E2BF2901A003FEFF1 /* ChatMessages */, FF10025F2B26499B0011A4DC /* ConversationStatusView.swift */, FF9300DB2B7823B0000859F4 /* EmptyConversaitonView.swift */, FF1002532B261D460011A4DC /* MessageListVIew.swift */, @@ -398,6 +402,16 @@ path = Recorder; sourceTree = ""; }; + FFD57E2E2BF2901A003FEFF1 /* ChatMessages */ = { + isa = PBXGroup; + children = ( + FF10022F2B2482BA0011A4DC /* ChatMessageView.swift */, + FFD57E2F2BF29145003FEFF1 /* MarkdownColours.swift */, + FFD57E312BF291B2003FEFF1 /* CodeBlockView.swift */, + ); + path = ChatMessages; + sourceTree = ""; + }; FFEC32842B24779A003E5C04 = { isa = PBXGroup; children = ( @@ -661,6 +675,7 @@ FF9300DE2B782A28000859F4 /* UnreachableAPIView.swift in Sources */, FF6D820D2B90C925001183A8 /* CompletionPanelView.swift in Sources */, FF6D821C2B914202001183A8 /* CompletionsEditorView.swift in Sources */, + FFD57E302BF29145003FEFF1 /* MarkdownColours.swift in Sources */, FF9300DC2B7823B0000859F4 /* EmptyConversaitonView.swift in Sources */, FFBBF4882B34F9C8008D611C /* View+Extension.swift in Sources */, FFEC9BE32B81358800AFBA63 /* DeallocPrinter.swift in Sources */, @@ -679,6 +694,7 @@ FF10025A2B2624C40011A4DC /* ConversationHistoryListView.swift in Sources */, FF10026D2B2751760011A4DC /* SettingsView.swift in Sources */, FF33066C2B83BB31007B33E5 /* SidebarButton.swift in Sources */, + FFD57E322BF291B2003FEFF1 /* CodeBlockView.swift in Sources */, FF1002542B261D460011A4DC /* MessageListVIew.swift in Sources */, FF10024E2B25C2A70011A4DC /* ConversationSD.swift in Sources */, FF9C920C2BF0C088004C8275 /* OptionsMenuView.swift in Sources */, diff --git a/Enchanted/UI/Shared/Chat/Components/ChatMessageView.swift b/Enchanted/UI/Shared/Chat/Components/ChatMessageView.swift deleted file mode 100644 index a5479c7..0000000 --- a/Enchanted/UI/Shared/Chat/Components/ChatMessageView.swift +++ /dev/null @@ -1,352 +0,0 @@ -// -// ChatMessageView.swift -// Enchanted -// -// Created by Augustinas Malinauskas on 09/12/2023. -// - -import SwiftUI -import MarkdownUI -import Splash -import ActivityIndicatorView - -struct ChatMessageView: View { - @Environment(\.colorScheme) var colorScheme - var message: MessageSD - var showLoader: Bool = false - var userInitials: String - @Binding var editMessage: MessageSD? - @State private var mouseHover = false - - var roleName: String { - let userInitialsNotEmpty = userInitials != "" ? userInitials : "AM" - return message.role == "user" ? userInitialsNotEmpty.uppercased() : "AI" - } - - var image: PlatformImage? { - message.image != nil ? PlatformImage(data: message.image!) : nil - } - - let enchantedTheme = Theme() - .text { - FontSize(16) - } - .code { - FontFamilyVariant(.monospaced) - FontSize(.em(0.85)) - BackgroundColor(.secondaryBackground) - } - .strong { - FontWeight(.semibold) - } - .link { - ForegroundColor(.link) - } - .heading1 { configuration in - VStack(alignment: .leading, spacing: 0) { - configuration.label - .relativePadding(.bottom, length: .em(0.3)) - .relativeLineSpacing(.em(0.125)) - .markdownMargin(top: 24, bottom: 16) - .markdownTextStyle { - FontWeight(.semibold) - FontSize(.em(2)) - } - Divider().overlay(Color.divider) - } - } - .heading2 { configuration in - VStack(alignment: .leading, spacing: 0) { - configuration.label - .relativePadding(.bottom, length: .em(0.3)) - .relativeLineSpacing(.em(0.125)) - .markdownMargin(top: 24, bottom: 16) - .markdownTextStyle { - FontWeight(.semibold) - FontSize(.em(1.5)) - } - Divider().overlay(Color.divider) - } - } - .heading3 { configuration in - configuration.label - .relativeLineSpacing(.em(0.125)) - .markdownMargin(top: 24, bottom: 16) - .markdownTextStyle { - FontWeight(.semibold) - FontSize(.em(1.25)) - } - } - .heading4 { configuration in - configuration.label - .relativeLineSpacing(.em(0.125)) - .markdownMargin(top: 24, bottom: 16) - .markdownTextStyle { - FontWeight(.semibold) - } - } - .heading5 { configuration in - configuration.label - .relativeLineSpacing(.em(0.125)) - .markdownMargin(top: 24, bottom: 16) - .markdownTextStyle { - FontWeight(.semibold) - FontSize(.em(0.875)) - } - } - .heading6 { configuration in - configuration.label - .relativeLineSpacing(.em(0.125)) - .markdownMargin(top: 24, bottom: 16) - .markdownTextStyle { - FontWeight(.semibold) - FontSize(.em(0.85)) - ForegroundColor(.tertiaryText) - } - } - .paragraph { configuration in - configuration.label - .fixedSize(horizontal: false, vertical: true) - .relativeLineSpacing(.em(0.25)) - .markdownMargin(top: 0, bottom: 16) - } - .blockquote { configuration in - HStack(spacing: 0) { - RoundedRectangle(cornerRadius: 6) - .fill(Color.border) - .relativeFrame(width: .em(0.2)) - configuration.label - .markdownTextStyle { ForegroundColor(.secondaryText) } - .relativePadding(.horizontal, length: .em(1)) - } - .fixedSize(horizontal: false, vertical: true) - } - .codeBlock { configuration in - codeBlock(configuration) - } - .listItem { configuration in - configuration.label - .padding(.bottom, 10) - } - .taskListMarker { configuration in - Image(systemName: configuration.isCompleted ? "checkmark.square.fill" : "square") - .symbolRenderingMode(.hierarchical) - .foregroundStyle(Color.checkbox, Color.checkboxBackground) - .imageScale(.small) - .relativeFrame(minWidth: .em(1.5), alignment: .trailing) - } - .table { configuration in - configuration.label - .fixedSize(horizontal: false, vertical: true) - .markdownTableBorderStyle(.init(color: .border)) - .markdownTableBackgroundStyle( - .alternatingRows(Color.background, Color.secondaryBackground) - ) - .markdownMargin(top: 0, bottom: 16) - } - .tableCell { configuration in - configuration.label - .markdownTextStyle { - if configuration.row == 0 { - FontWeight(.semibold) - } - BackgroundColor(nil) - } - .fixedSize(horizontal: false, vertical: true) - .padding(.vertical, 6) - .padding(.horizontal, 13) - .relativeLineSpacing(.em(0.25)) - } - .thematicBreak { - Divider() - .relativeFrame(height: .em(0.25)) - .overlay(Color.border) - .markdownMargin(top: 24, bottom: 24) - } - - private var codeHighlightColorScheme: Splash.Theme { - switch colorScheme { - case .dark: - return .wwdc17(withFont: .init(size: 16)) - default: - return .sunset(withFont: .init(size: 16)) - } - } - - var body: some View { - ZStack(alignment: .topTrailing) { - VStack { - HStack(alignment: .top, spacing: 12) { - if message.role == "user" { - ZStack { - Circle() - .foregroundColor(.green) - - Text(roleName) - .font(.system(size: 11)) - .foregroundStyle(.background) - - } - .frame(width: 24, height: 24) - - } else { - Image("logo-nobg") - .resizable() - .scaledToFit() - .frame(width: 24, height: 24) - } - - VStack(alignment: .leading) { - HStack { - Text(message.role.capitalized) - .font(.system(size: 16)) - .fontWeight(.medium) - .padding(.bottom, 2) - .frame(height: 27) - - ActivityIndicatorView(isVisible: .constant(true), type: .rotatingDots(count: 5)) - .frame(width: 20, height: 20) - .rotationEffect(.degrees(-90)) - .showIf(showLoader) - } - - Markdown(message.content) - .textSelection(.enabled) - .markdownCodeSyntaxHighlighter(.splash(theme: codeHighlightColorScheme)) - .markdownTheme(enchantedTheme) - - if let uiImage = image { -#if os(iOS) - Image(uiImage: uiImage) - .resizable() - .scaledToFit() - .frame(width: 100) - .clipShape(RoundedRectangle(cornerRadius: 5)) -#elseif os(macOS) - Image(nsImage: uiImage) - .resizable() - .scaledToFit() - .frame(width: 100) - .clipShape(RoundedRectangle(cornerRadius: 5)) -#endif - - } - } - - Spacer() - } - } - -#if os(macOS) - HStack { - Button(action: {Clipboard.shared.setString(message.content)}) { - Text("Copy") - } - .buttonStyle(GrowingButton()) - .padding(8) - .background(Color.gray5Custom) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .showIf(mouseHover) - - Button(action: {editMessage = message}) { - Text("Edit") - } - .buttonStyle(GrowingButton()) - .padding(8) - .background(Color.gray5Custom) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .showIf(mouseHover) - .showIf(message.role == "user") - } - -#endif - } -#if os(macOS) - .onHover { over in - withAnimation(.easeInOut(duration: 0.3)) { - mouseHover = over - } - } -#endif - } -} - -#Preview { - Group { - ChatMessageView(message: MessageSD.sample[0], userInitials: "AM", editMessage: .constant(nil)) - .previewLayout(.sizeThatFits) - } -} - -extension SwiftUI.Color { - fileprivate static let text = Color( - light: Color(rgba: 0x0606_06ff), dark: Color(rgba: 0xfbfb_fcff) - ) - fileprivate static let secondaryText = Color( - light: Color(rgba: 0x6b6e_7bff), dark: Color(rgba: 0x9294_a0ff) - ) - fileprivate static let tertiaryText = Color( - light: Color(rgba: 0x6b6e_7bff), dark: Color(rgba: 0x6d70_7dff) - ) - fileprivate static let background = Color( - light: .white, dark: Color(rgba: 0x1819_1dff) - ) - fileprivate static let secondaryBackground = Color( - light: Color(rgba: 0xf7f7_f9ff), dark: Color(rgba: 0x2526_2aff) - ) - fileprivate static let link = Color( - light: Color(rgba: 0x2c65_cfff), dark: Color(rgba: 0x4c8e_f8ff) - ) - fileprivate static let border = Color( - light: Color(rgba: 0xe4e4_e8ff), dark: Color(rgba: 0x4244_4eff) - ) - fileprivate static let divider = Color( - light: Color(rgba: 0xd0d0_d3ff), dark: Color(rgba: 0x3334_38ff) - ) - fileprivate static let checkbox = Color(rgba: 0xb9b9_bbff) - fileprivate static let checkboxBackground = Color(rgba: 0xeeee_efff) -} - -@ViewBuilder -private func codeBlock(_ configuration: CodeBlockConfiguration) -> some View { - var language: String { - let language = configuration.language ?? "code" - return language != "" ? language : "code" - } - - VStack(spacing: 0) { - HStack { - Text(language) - .font(.system(size: 13, design: .monospaced)) - .fontWeight(.semibold) - Spacer() - - Button(action: { - Clipboard.shared.setString(configuration.content) - }) { - Image(systemName: "clipboard") - .padding(7) - } - .buttonStyle(GrowingButton()) - } - .padding(.horizontal) - .padding(.vertical, 4) - .background(Color.secondaryBackground) - - Divider() - - ScrollView(.horizontal) { - configuration.label - .fixedSize(horizontal: false, vertical: true) - .relativeLineSpacing(.em(0.225)) - .markdownTextStyle { - FontFamilyVariant(.monospaced) - FontSize(.em(0.85)) - } - .padding(16) - } - } - .background(Color.secondaryBackground) - .clipShape(RoundedRectangle(cornerRadius: 8)) - .markdownMargin(top: .zero, bottom: .em(0.8)) -} diff --git a/Enchanted/UI/Shared/Chat/Components/ChatMessages/ChatMessageView.swift b/Enchanted/UI/Shared/Chat/Components/ChatMessages/ChatMessageView.swift new file mode 100644 index 0000000..4db5653 --- /dev/null +++ b/Enchanted/UI/Shared/Chat/Components/ChatMessages/ChatMessageView.swift @@ -0,0 +1,143 @@ +// +// ChatMessageView.swift +// Enchanted +// +// Created by Augustinas Malinauskas on 09/12/2023. +// + +import SwiftUI +import MarkdownUI +import Splash +import ActivityIndicatorView + +struct ChatMessageView: View { + @Environment(\.colorScheme) var colorScheme + var message: MessageSD + var showLoader: Bool = false + var userInitials: String + @Binding var editMessage: MessageSD? + @State private var mouseHover = false + + var roleName: String { + let userInitialsNotEmpty = userInitials != "" ? userInitials : "AM" + return message.role == "user" ? userInitialsNotEmpty.uppercased() : "AI" + } + + var image: PlatformImage? { + message.image != nil ? PlatformImage(data: message.image!) : nil + } + + private var codeHighlightColorScheme: Splash.Theme { + switch colorScheme { + case .dark: + return .wwdc17(withFont: .init(size: 16)) + default: + return .sunset(withFont: .init(size: 16)) + } + } + + var body: some View { + ZStack(alignment: .topTrailing) { + VStack { + HStack(alignment: .top, spacing: 12) { + if message.role == "user" { + ZStack { + Circle() + .foregroundColor(.green) + + Text(roleName) + .font(.system(size: 11)) + .foregroundStyle(.background) + + } + .frame(width: 24, height: 24) + + } else { + Image("logo-nobg") + .resizable() + .scaledToFit() + .frame(width: 24, height: 24) + } + + VStack(alignment: .leading) { + HStack { + Text(message.role.capitalized) + .font(.system(size: 14)) + .fontWeight(.medium) + .padding(.bottom, 2) + .frame(height: 27) + + ActivityIndicatorView(isVisible: .constant(true), type: .rotatingDots(count: 5)) + .frame(width: 20, height: 20) + .rotationEffect(.degrees(-90)) + .showIf(showLoader) + } + + Markdown(message.content) + .textSelection(.enabled) + .markdownCodeSyntaxHighlighter(.splash(theme: codeHighlightColorScheme)) + .markdownTheme(MarkdownColours.enchantedTheme) + + if let uiImage = image { +#if os(iOS) + Image(uiImage: uiImage) + .resizable() + .scaledToFit() + .frame(width: 100) + .clipShape(RoundedRectangle(cornerRadius: 5)) +#elseif os(macOS) + Image(nsImage: uiImage) + .resizable() + .scaledToFit() + .frame(width: 100) + .clipShape(RoundedRectangle(cornerRadius: 5)) +#endif + + } + } + + Spacer() + } + } + +#if os(macOS) + HStack(spacing: 0) { + Button(action: {Clipboard.shared.setString(message.content)}) { + Image(systemName: "doc.on.doc") + } + .buttonStyle(GrowingButton()) + .padding(8) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .showIf(mouseHover) + + Button(action: {editMessage = message}) { + Image(systemName: "pencil") + } + .buttonStyle(GrowingButton()) + .padding(8) + .clipShape(RoundedRectangle(cornerRadius: 10)) + .showIf(mouseHover) + .showIf(message.role == "user") + } + +#endif + } +#if os(macOS) + .onHover { over in + withAnimation(.easeInOut(duration: 0.3)) { + mouseHover = over + } + } +#endif + } +} + +#Preview { + VStack { + ChatMessageView(message: MessageSD.sample[0], userInitials: "AM", editMessage: .constant(nil)) + .previewLayout(.sizeThatFits) + + ChatMessageView(message: MessageSD(content: "```python \nprint(5+5)\n```", role: "ai"), userInitials: "AM", editMessage: .constant(nil)) + .previewLayout(.sizeThatFits) + } +} diff --git a/Enchanted/UI/Shared/Chat/Components/ChatMessages/CodeBlockView.swift b/Enchanted/UI/Shared/Chat/Components/ChatMessages/CodeBlockView.swift new file mode 100644 index 0000000..e655316 --- /dev/null +++ b/Enchanted/UI/Shared/Chat/Components/ChatMessages/CodeBlockView.swift @@ -0,0 +1,55 @@ +// +// CodeBlockView.swift +// Enchanted +// +// Created by Augustinas Malinauskas on 13/05/2024. +// + +import SwiftUI +import MarkdownUI + +struct CodeBlockView: View { + var configuration: CodeBlockConfiguration + var language: String { + let language = configuration.language ?? "code" + return language != "" ? language : "code" + } + + var body: some View { + VStack(spacing: 0) { + HStack { + Text(language) + .font(.system(size: 13, design: .monospaced)) + .fontWeight(.semibold) + Spacer() + + Button(action: { + Clipboard.shared.setString(configuration.content) + }) { + Image(systemName: "doc.on.doc") + .padding(7) + } + .buttonStyle(GrowingButton()) + } + .padding(.horizontal) + .padding(.vertical, 4) + .background(MarkdownColours.secondaryBackground) + + Divider() + + ScrollView(.horizontal) { + configuration.label + .fixedSize(horizontal: false, vertical: true) + .relativeLineSpacing(.em(0.225)) + .markdownTextStyle { + FontFamilyVariant(.monospaced) + FontSize(.em(0.85)) + } + .padding(16) + } + } + .background(MarkdownColours.secondaryBackground) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .markdownMargin(top: .zero, bottom: .em(0.8)) + } +} diff --git a/Enchanted/UI/Shared/Chat/Components/ChatMessages/MarkdownColours.swift b/Enchanted/UI/Shared/Chat/Components/ChatMessages/MarkdownColours.swift new file mode 100644 index 0000000..0c8cab5 --- /dev/null +++ b/Enchanted/UI/Shared/Chat/Components/ChatMessages/MarkdownColours.swift @@ -0,0 +1,175 @@ +// +// MarkdownColours.swift +// Enchanted +// +// Created by Augustinas Malinauskas on 13/05/2024. +// + +import SwiftUI +import MarkdownUI + +struct MarkdownColours { + static let text = Color( + light: Color(rgba: 0x0606_06ff), dark: Color(rgba: 0xfbfb_fcff) + ) + static let secondaryText = Color( + light: Color(rgba: 0x6b6e_7bff), dark: Color(rgba: 0x9294_a0ff) + ) + static let tertiaryText = Color( + light: Color(rgba: 0x6b6e_7bff), dark: Color(rgba: 0x6d70_7dff) + ) + static let background = Color( + light: .white, dark: Color(rgba: 0x1819_1dff) + ) + static let secondaryBackground = Color( + light: Color(rgba: 0xf7f7_f9ff), dark: Color(rgba: 0x2526_2aff) + ) + static let link = Color( + light: Color(rgba: 0x2c65_cfff), dark: Color(rgba: 0x4c8e_f8ff) + ) + static let border = Color( + light: Color(rgba: 0xe4e4_e8ff), dark: Color(rgba: 0x4244_4eff) + ) + static let divider = Color( + light: Color(rgba: 0xd0d0_d3ff), dark: Color(rgba: 0x3334_38ff) + ) + static let checkbox = Color(rgba: 0xb9b9_bbff) + static let checkboxBackground = Color(rgba: 0xeeee_efff) + + static let enchantedTheme = Theme() + .text { + FontSize(14) + } + .code { + FontFamilyVariant(.monospaced) + FontSize(.em(0.85)) + BackgroundColor(secondaryBackground) + } + .strong { + FontWeight(.semibold) + } + .link { + ForegroundColor(link) + } + .heading1 { configuration in + VStack(alignment: .leading, spacing: 0) { + configuration.label + .relativePadding(.bottom, length: .em(0.3)) + .relativeLineSpacing(.em(0.125)) + .markdownMargin(top: 24, bottom: 16) + .markdownTextStyle { + FontWeight(.semibold) + FontSize(.em(2)) + } + Divider().overlay(divider) + } + } + .heading2 { configuration in + VStack(alignment: .leading, spacing: 0) { + configuration.label + .relativePadding(.bottom, length: .em(0.3)) + .relativeLineSpacing(.em(0.125)) + .markdownMargin(top: 24, bottom: 16) + .markdownTextStyle { + FontWeight(.semibold) + FontSize(.em(1.5)) + } + Divider().overlay(divider) + } + } + .heading3 { configuration in + configuration.label + .relativeLineSpacing(.em(0.125)) + .markdownMargin(top: 24, bottom: 16) + .markdownTextStyle { + FontWeight(.semibold) + FontSize(.em(1.25)) + } + } + .heading4 { configuration in + configuration.label + .relativeLineSpacing(.em(0.125)) + .markdownMargin(top: 24, bottom: 16) + .markdownTextStyle { + FontWeight(.semibold) + } + } + .heading5 { configuration in + configuration.label + .relativeLineSpacing(.em(0.125)) + .markdownMargin(top: 24, bottom: 16) + .markdownTextStyle { + FontWeight(.semibold) + FontSize(.em(0.875)) + } + } + .heading6 { configuration in + configuration.label + .relativeLineSpacing(.em(0.125)) + .markdownMargin(top: 24, bottom: 16) + .markdownTextStyle { + FontWeight(.semibold) + FontSize(.em(0.85)) + ForegroundColor(tertiaryText) + } + } + .paragraph { configuration in + configuration.label + .fixedSize(horizontal: false, vertical: true) + .relativeLineSpacing(.em(0.25)) + .markdownMargin(top: 0, bottom: 16) + } + .blockquote { configuration in + HStack(spacing: 0) { + RoundedRectangle(cornerRadius: 6) + .fill(border) + .relativeFrame(width: .em(0.2)) + configuration.label + .markdownTextStyle { ForegroundColor(secondaryText) } + .relativePadding(.horizontal, length: .em(1)) + } + .fixedSize(horizontal: false, vertical: true) + } + .codeBlock { configuration in + CodeBlockView(configuration: configuration) + } + .listItem { configuration in + configuration.label + .padding(.bottom, 10) + } + .taskListMarker { configuration in + Image(systemName: configuration.isCompleted ? "checkmark.square.fill" : "square") + .symbolRenderingMode(.hierarchical) + .foregroundStyle(checkbox, checkboxBackground) + .imageScale(.small) + .relativeFrame(minWidth: .em(1.5), alignment: .trailing) + } + .table { configuration in + configuration.label + .fixedSize(horizontal: false, vertical: true) + .markdownTableBorderStyle(.init(color: border)) + .markdownTableBackgroundStyle( + .alternatingRows(background, secondaryBackground) + ) + .markdownMargin(top: 0, bottom: 16) + } + .tableCell { configuration in + configuration.label + .markdownTextStyle { + if configuration.row == 0 { + FontWeight(.semibold) + } + BackgroundColor(nil) + } + .fixedSize(horizontal: false, vertical: true) + .padding(.vertical, 6) + .padding(.horizontal, 13) + .relativeLineSpacing(.em(0.25)) + } + .thematicBreak { + Divider() + .relativeFrame(height: .em(0.25)) + .overlay(border) + .markdownMargin(top: 24, bottom: 24) + } +}