mirror of
https://github.com/stoatchat/for-ios.git
synced 2026-07-19 16:43:34 -04:00
fix: Working mention and emoji rendering
This commit is contained in:
@@ -94,6 +94,7 @@
|
||||
17D9C2BD2ADCA48D00426857 /* Kingfisher in Frameworks */ = {isa = PBXBuildFile; productRef = 17D9C2BC2ADCA48D00426857 /* Kingfisher */; };
|
||||
17D9D2B72B0843DE00D57812 /* PermissionsCalculator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D9D2B62B0843DE00D57812 /* PermissionsCalculator.swift */; };
|
||||
17DE7DB52BFAEC3F00C99188 /* Types.h in Headers */ = {isa = PBXBuildFile; fileRef = 17DE7DB42BFAEC3F00C99188 /* Types.h */; settings = {ATTRIBUTES = (Public, ); }; };
|
||||
17DF7CB02C4ACDA1003E8FDC /* UIImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF7CAF2C4ACD9D003E8FDC /* UIImage.swift */; };
|
||||
17DFB4642AE06A5D00E1D417 /* Settings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DFB4632AE06A5D00E1D417 /* Settings.swift */; };
|
||||
17E019C92AF1279100AB4663 /* AppearanceSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E019C82AF1279100AB4663 /* AppearanceSettings.swift */; };
|
||||
17E019CD2AF136B100AB4663 /* PresenceIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E019CC2AF136B100AB4663 /* PresenceIndicator.swift */; };
|
||||
@@ -253,6 +254,7 @@
|
||||
17DE7E0B2BFAFDDD00C99188 /* User.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = User.swift; sourceTree = "<group>"; };
|
||||
17DE7E0C2BFAFDDD00C99188 /* Api.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Api.swift; sourceTree = "<group>"; };
|
||||
17DE7E1B2BFAFE5600C99188 /* File.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = File.swift; sourceTree = "<group>"; };
|
||||
17DF7CAF2C4ACD9D003E8FDC /* UIImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIImage.swift; sourceTree = "<group>"; };
|
||||
17DFB4632AE06A5D00E1D417 /* Settings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Settings.swift; sourceTree = "<group>"; };
|
||||
17E019C82AF1279100AB4663 /* AppearanceSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppearanceSettings.swift; sourceTree = "<group>"; };
|
||||
17E019CC2AF136B100AB4663 /* PresenceIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PresenceIndicator.swift; sourceTree = "<group>"; };
|
||||
@@ -317,6 +319,7 @@
|
||||
170C23D82C224BA40057E399 /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
17DF7CAF2C4ACD9D003E8FDC /* UIImage.swift */,
|
||||
172F2D022C22ED5A00948C00 /* Collection.swift */,
|
||||
172F2D002C22ED4A00948C00 /* IteratorProtocol.swift */,
|
||||
172F2CFE2C22ED3600948C00 /* Bundle.swift */,
|
||||
@@ -830,6 +833,7 @@
|
||||
178BB11B2B02FCDD001143A4 /* Responses.swift in Sources */,
|
||||
17A589FA2C44781F00B9D85A /* AnyTransition.swift in Sources */,
|
||||
17BF54CD2B1785E200178866 /* HomeWelcome.swift in Sources */,
|
||||
17DF7CB02C4ACDA1003E8FDC /* UIImage.swift in Sources */,
|
||||
17F502542B9BFB1000A3022D /* AddFriend.swift in Sources */,
|
||||
17C9A3502B0580D10043A387 /* Welcome.swift in Sources */,
|
||||
17A7FE392C49EBD20083B22F /* Image.swift in Sources */,
|
||||
|
||||
@@ -8,9 +8,10 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
import Flow
|
||||
import SwiftParsec
|
||||
// import Parsing
|
||||
import Kingfisher
|
||||
import Types
|
||||
import SwiftParsec
|
||||
|
||||
enum ContentPart: Equatable {
|
||||
case text(AttributedString)
|
||||
@@ -26,31 +27,144 @@ enum Node {
|
||||
case custom_emoji(String)
|
||||
}
|
||||
|
||||
//
|
||||
//let userMention = Parse(input: Substring.self) { id in
|
||||
// Node.user_mention(String(id))
|
||||
//} with: {
|
||||
// "<@"
|
||||
// Prefix { $0 != ">" }
|
||||
// ">"
|
||||
//}
|
||||
//
|
||||
//let channelMention = Parse(input: Substring.self) { id in
|
||||
// Node.channel_mention(String(id))
|
||||
//} with: {
|
||||
// "<#"
|
||||
// Prefix { $0 != ">" }
|
||||
// ">"
|
||||
//}
|
||||
//let mention = Parse(input: Substring.self) {
|
||||
// OneOf {
|
||||
// userMention
|
||||
// channelMention
|
||||
// }
|
||||
//}
|
||||
//
|
||||
//let emoji = Parse(input: Substring.self) { id in
|
||||
// Node.custom_emoji(String(id))
|
||||
//} with: {
|
||||
// ":"
|
||||
// Prefix { $0 != ":" }
|
||||
// ":"
|
||||
//}
|
||||
//
|
||||
//let parser = Parse(input: Substring.self) {
|
||||
// Many {
|
||||
// Optionally {
|
||||
// Many {
|
||||
// OneOf {
|
||||
// mention
|
||||
// emoji
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Prefix { !["@", ":"].contains(String($0)) }.map { v in Node.text(String(v)!) }
|
||||
// }
|
||||
// Many {
|
||||
// Parse {
|
||||
// Optionally {
|
||||
// OneOf {
|
||||
// mention
|
||||
// emoji
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Optionally {
|
||||
// OneOf {
|
||||
// mention
|
||||
// emoji
|
||||
// }
|
||||
// }
|
||||
// }.map { nodes in
|
||||
// nodes.flatMap { (a, b, c) in
|
||||
// [a, b, c].compactMap { $0 }
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
//func parseMentions(text: String) -> [Node] {
|
||||
// // if it fails just default back to regular text
|
||||
// //[.text(text)]
|
||||
// print(text)
|
||||
// return try! parser.parse(text)
|
||||
//}
|
||||
|
||||
// if you need to properly debug this the haskell code for this is here:
|
||||
/*
|
||||
data Node = Text String | Emoji String | UserMention String | ChannelMention String
|
||||
deriving Show
|
||||
|
||||
emoji :: GenParser Char () Node
|
||||
emoji = Emoji <$> (char ':' *> many1 (noneOf ":") <* char ':')
|
||||
|
||||
mentionTemplate :: (String -> b) -> Char -> GenParser Char () b
|
||||
mentionTemplate f c = f <$> (char '<' *> char c *> many1 (noneOf ">") <* char '>')
|
||||
|
||||
userMention :: GenParser Char () Node
|
||||
userMention = mentionTemplate UserMention '@'
|
||||
|
||||
channelMention :: GenParser Char () Node
|
||||
channelMention = mentionTemplate UserMention '#'
|
||||
|
||||
mention :: GenParser Char () Node
|
||||
mention = try userMention <|> channelMention
|
||||
|
||||
customElement :: GenParser Char () Node
|
||||
customElement = try emoji <|> mention
|
||||
|
||||
text :: GenParser Char () Node
|
||||
text = Text <$> liftA2 (:) anyChar (manyTill anyChar (lookAhead $ eof <|> void (try customElement)))
|
||||
|
||||
node :: GenParser Char () Node
|
||||
node = try customElement <|> text
|
||||
|
||||
parser :: GenParser Char () [Node]
|
||||
parser = many1 node
|
||||
*/
|
||||
|
||||
let character = StringParser.character
|
||||
|
||||
func mentionTemplate(_ c: Character) -> GenericParser<String, (), String> {
|
||||
StringParser.noneOf("<@>")
|
||||
character("<")
|
||||
*> character(c)
|
||||
*> StringParser.noneOf(["<", c, ">"])
|
||||
.many1
|
||||
.stringValue
|
||||
.between(
|
||||
character("<") *> character(c),
|
||||
character(">")
|
||||
)
|
||||
<* character(">")
|
||||
}
|
||||
|
||||
let userMention = mentionTemplate("@").map(Node.user_mention)
|
||||
let channelMention = mentionTemplate("#").map(Node.channel_mention)
|
||||
let userMention = Node.user_mention <^> mentionTemplate("@")
|
||||
let channelMention = Node.channel_mention <^> mentionTemplate("#")
|
||||
|
||||
let mention = userMention.attempt <|> channelMention.attempt
|
||||
let mention = userMention.attempt <|> channelMention
|
||||
|
||||
let emojiRaw = StringParser.noneOf(":").many1.stringValue.between(character(":"), character(":"))
|
||||
let emoji = emojiRaw.map(Node.custom_emoji)
|
||||
let customElement = mention <|> emoji
|
||||
let emojiRaw = character(":")
|
||||
*> StringParser.noneOf(":")
|
||||
.many1
|
||||
.stringValue
|
||||
<* character(":")
|
||||
|
||||
let text = (
|
||||
StringParser.anyCharacter.manyTill(customElement.attempt.lookAhead).attempt <|>
|
||||
StringParser.anyCharacter.many1)
|
||||
.stringValue.map(Node.text)
|
||||
let emoji = Node.custom_emoji <^> emojiRaw
|
||||
|
||||
let customElement = mention.attempt <|> emoji
|
||||
|
||||
let text = StringParser.anyCharacter >>- { result in
|
||||
StringParser.anyCharacter.manyTill((StringParser.eof <|> customElement.attempt.discard).lookAhead) >>- { results in
|
||||
return GenericParser(
|
||||
result: Node.text(String(results.prepending(result)))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
let node = customElement.attempt <|> text
|
||||
|
||||
@@ -59,30 +173,30 @@ let emojiOnlyParser = emojiRaw.separatedBy1(character(" ").many).optional
|
||||
|
||||
func parseMentions(text: String) -> [Node] {
|
||||
// if it fails just default back to regular text
|
||||
[.text(text)]
|
||||
// (try? parser.run(sourceName: "<input>", input: text)) ?? [.text(text)]
|
||||
// [.text(text)]
|
||||
return (try? parser.run(sourceName: "<input>", input: text)) ?? [.text(text)]
|
||||
}
|
||||
|
||||
func parseEmojisOnly(text: String) -> [String]? {
|
||||
(try? emojiOnlyParser.run(sourceName: "<input>", input: text)) ?? nil
|
||||
}
|
||||
//
|
||||
|
||||
//struct Contents: View {
|
||||
// @EnvironmentObject var viewState: ViewState
|
||||
//
|
||||
// var text: String
|
||||
//
|
||||
//
|
||||
// @State var showMemberSheet: Bool = false
|
||||
//
|
||||
//
|
||||
// func parseText(content: String, currentServer: String? = nil) -> [ContentPart] {
|
||||
// var parts: [ContentPart] = []
|
||||
// let content = try! AttributedString(markdown: content, options: .init(allowsExtendedAttributes: true,
|
||||
//interpretedSyntax: .full, failurePolicy: .returnPartiallyParsedIfPossible))
|
||||
//
|
||||
//
|
||||
// for run in content.runs {
|
||||
// let innerContent = content.characters[run.range].map { String($0) }.joined(separator: "")
|
||||
// let innerParts = parseMentions(text: innerContent)
|
||||
//
|
||||
//
|
||||
// for part in innerParts {
|
||||
// switch part {
|
||||
// case .text(let c):
|
||||
@@ -96,13 +210,13 @@ func parseEmojisOnly(text: String) -> [String]? {
|
||||
// case .user_mention(let id):
|
||||
// if let user = viewState.users[id] {
|
||||
// let member: Member?
|
||||
//
|
||||
//
|
||||
// if let server = currentServer {
|
||||
// member = viewState.members[server]?[id]
|
||||
// } else {
|
||||
// member = nil
|
||||
// }
|
||||
//
|
||||
//
|
||||
// parts.append(.user_mention(user, member))
|
||||
// } else {
|
||||
// parts.append(.text(AttributedString("<@\(id)>", attributes: run.attributes)))
|
||||
@@ -112,13 +226,13 @@ func parseEmojisOnly(text: String) -> [String]? {
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// return parts
|
||||
// }
|
||||
//
|
||||
//
|
||||
// var body: some View {
|
||||
// let parts = parseText(content: text)
|
||||
//
|
||||
//
|
||||
// GeometryReader { proxy in
|
||||
// if let emojis = parseEmojisOnly(text: text) {
|
||||
// ForEach(emojis, id: \.self) { emoji in
|
||||
@@ -164,17 +278,17 @@ func parseEmojisOnly(text: String) -> [String]? {
|
||||
//
|
||||
//struct SubviewTextView: UIViewRepresentable {
|
||||
// @EnvironmentObject var viewState: ViewState
|
||||
//
|
||||
//
|
||||
// var fixedWidth: CGFloat
|
||||
// var parts: [ContentPart]
|
||||
//
|
||||
//
|
||||
// func memberColour(member: Member?) -> Color? {
|
||||
// return member.flatMap {
|
||||
// let server = viewState.servers[$0.id.server]!
|
||||
// return $0.displayColour(server: server)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
//
|
||||
// func makeUIView(context: Context) -> some UIView {
|
||||
// let view = SubviewAttachingTextView()
|
||||
// view.textContainer.lineFragmentPadding = 0
|
||||
@@ -184,21 +298,21 @@ func parseEmojisOnly(text: String) -> [String]? {
|
||||
// view.textColor = .white
|
||||
// view.backgroundColor = nil
|
||||
// view.isEditable = false
|
||||
//
|
||||
//
|
||||
// view.translatesAutoresizingMaskIntoConstraints = false
|
||||
// view.isScrollEnabled = false
|
||||
// view.adjustsFontForContentSizeCategory = true
|
||||
//
|
||||
//
|
||||
// view.setContentHuggingPriority(.required, for: .vertical)
|
||||
// view.setContentCompressionResistancePriority(.required, for: .vertical)
|
||||
// view.setContentHuggingPriority(.defaultLow, for: .horizontal)
|
||||
// view.setContentCompressionResistancePriority(.defaultLow, for: .horizontal)
|
||||
//
|
||||
//
|
||||
// let attrString = NSMutableAttributedString()
|
||||
//
|
||||
//
|
||||
// for part in parts {
|
||||
// var view: (any View)? = nil
|
||||
//
|
||||
//
|
||||
// switch part {
|
||||
// case .text(var str):
|
||||
// str.foregroundColor = .white
|
||||
@@ -215,7 +329,7 @@ func parseEmojisOnly(text: String) -> [String]? {
|
||||
//
|
||||
// case .custom_emoji(let emoji):
|
||||
// ()
|
||||
//
|
||||
//
|
||||
// case .user_mention(let user, let member):
|
||||
// view = HStack(spacing: 2) {
|
||||
// Avatar(user: user, member: member, width: 16, height: 16)
|
||||
@@ -226,7 +340,7 @@ func parseEmojisOnly(text: String) -> [String]? {
|
||||
// }
|
||||
// .contentShape(Capsule())
|
||||
// }
|
||||
//
|
||||
//
|
||||
// if let subView = view {
|
||||
// let controller = UIHostingController(rootView: AnyView(subView))
|
||||
// let nsAttrString = NSMutableAttributedString(attachment: SubviewTextAttachment(view: controller.view!))
|
||||
@@ -235,19 +349,19 @@ func parseEmojisOnly(text: String) -> [String]? {
|
||||
// }
|
||||
// }
|
||||
// view.attributedText = attrString
|
||||
//
|
||||
//
|
||||
// return view
|
||||
// }
|
||||
//
|
||||
//
|
||||
// func updateUIView(_ view: UIViewType, context: Context) {
|
||||
// }
|
||||
//}
|
||||
|
||||
//struct Contents: View {
|
||||
// @EnvironmentObject var viewState: ViewState
|
||||
//
|
||||
//
|
||||
// var text: String
|
||||
//
|
||||
//
|
||||
// var body: some View {
|
||||
// Text(verbatim: text)
|
||||
// }
|
||||
@@ -256,92 +370,128 @@ func parseEmojisOnly(text: String) -> [String]? {
|
||||
//struct InnerContents: UIViewRepresentable {
|
||||
// var viewState: ViewState
|
||||
// var text: String
|
||||
//
|
||||
//
|
||||
// func makeUIView(context: Context) -> some UIView {
|
||||
// let textView = UITextView()
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
// textView.attributedText = NSAttributedString(str)
|
||||
// textView.backgroundColor = .clear
|
||||
// return textView
|
||||
// }
|
||||
//
|
||||
//
|
||||
// func updateUIView(_ uiView: UIViewType, context: Context) {
|
||||
// }
|
||||
//}
|
||||
|
||||
//struct _Contents: View {
|
||||
// @State var images: [String: UIImage] = [:]
|
||||
//
|
||||
// func loadImage(url: String) -> UIImage {
|
||||
// if let image = images[url] {
|
||||
// return image
|
||||
// } else {
|
||||
// Task {
|
||||
//
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// var body: some View {
|
||||
// let font = UIFont.preferredFont(forTextStyle: .title1)
|
||||
// let nsattr = NSAttributedString(string: "Zomatree", attributes: [.foregroundColor: UIColor.red, .font: font, .link: "https://revolt.chat"])
|
||||
// let attr = AttributedString(nsattr)
|
||||
// let large = UIImage(named: "large")!
|
||||
// let image = Image(uiImage: large.imageWith(newSize: CGSize(width: font.pointSize, height: font.pointSize)))
|
||||
//
|
||||
// (
|
||||
// Text("Hello ") +
|
||||
// Text(image) +
|
||||
// Text(attr) +
|
||||
// Text(".")
|
||||
// ).font(.title)
|
||||
// }
|
||||
//}
|
||||
|
||||
|
||||
struct Contents: View {
|
||||
@Environment(\.font) var font: Font?
|
||||
@EnvironmentObject var viewState: ViewState
|
||||
@Binding var text: String
|
||||
|
||||
func buildContent() -> AttributedString {
|
||||
let font = self.font ?? Font.body
|
||||
let boldFont = font.bold()
|
||||
@State var images: [String: UIImage] = [:]
|
||||
var fontSize: CGFloat
|
||||
|
||||
func buildContent() -> Text? {
|
||||
let font = UIFont.systemFont(ofSize: fontSize)
|
||||
let boldFont = UIFont.boldSystemFont(ofSize: fontSize)
|
||||
|
||||
let parts = parseMentions(text: text)
|
||||
var str = AttributedString()
|
||||
|
||||
var textParts: [Text] = []
|
||||
|
||||
for part in parts {
|
||||
switch part {
|
||||
case .user_mention(let string):
|
||||
var mention: AttributedString = AttributedString()
|
||||
|
||||
var mention: NSAttributedString
|
||||
|
||||
if let user = viewState.users[string] {
|
||||
let member = viewState.currentServer.id.flatMap { viewState.members[$0] }.flatMap { $0[string] }
|
||||
|
||||
|
||||
let name = member?.nickname ?? user.display_name ?? user.username
|
||||
|
||||
mention.append(AttributedString("@\(name)", attributes: AttributeContainer([.foregroundColor: viewState.theme.accent.color, .font: boldFont, .link: "revoltchat://users?user=\(string)"])
|
||||
))
|
||||
|
||||
mention = NSAttributedString(string: "@\(name)", attributes: [.foregroundColor: viewState.theme.accent.color, .font: boldFont, .link: "revoltchat://users?user=\(string)"])
|
||||
} else {
|
||||
let container = AttributeContainer([.foregroundColor: viewState.theme.accent.color, .font: boldFont])
|
||||
|
||||
mention.append(AttributedString("@Unknown", attributes: container))
|
||||
mention = NSAttributedString(string: "@Unknown", attributes: [.foregroundColor: viewState.theme.accent.color, .font: boldFont])
|
||||
}
|
||||
|
||||
str.append(mention)
|
||||
|
||||
|
||||
textParts.append(Text(AttributedString(mention)))
|
||||
|
||||
case .channel_mention(let string):
|
||||
let mention: AttributedString
|
||||
|
||||
let mention: NSAttributedString
|
||||
|
||||
if let channel = viewState.channels[string] {
|
||||
let name = channel.getName(viewState)
|
||||
|
||||
let container = AttributeContainer([.foregroundColor: viewState.theme.accent.color, .font: boldFont, .link: "revoltchat://channels?channel=\(string)"])
|
||||
mention = AttributedString("#\(name)", attributes: container)
|
||||
|
||||
mention = NSAttributedString(string: "#\(name)", attributes: [.foregroundColor: viewState.theme.accent.color, .font: boldFont, .link: "revoltchat://channels?channel=\(string)"])
|
||||
} else {
|
||||
let container = AttributeContainer([.foregroundColor: viewState.theme.accent.color, .font: boldFont])
|
||||
|
||||
mention = AttributedString("#Unknown", attributes: container)
|
||||
mention = NSAttributedString(string: "#Unknown", attributes: [.foregroundColor: viewState.theme.accent.color, .font: boldFont])
|
||||
}
|
||||
|
||||
str.append(mention)
|
||||
|
||||
|
||||
textParts.append(Text(AttributedString(mention)))
|
||||
|
||||
case .text(let string):
|
||||
var substring = try! AttributedString(markdown: string, options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace))
|
||||
substring.setAttributes(AttributeContainer([.font: font, .foregroundColor: viewState.theme.foreground.color]))
|
||||
|
||||
str.append(substring)
|
||||
|
||||
if string.count > 0 {
|
||||
var substring = try! NSMutableAttributedString(markdown: string, options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace))
|
||||
substring.setAttributes([.font: font, .foregroundColor: viewState.theme.foreground.color], range: NSRange(location: 0, length: substring.length))
|
||||
|
||||
textParts.append(Text(AttributedString(substring)))
|
||||
}
|
||||
|
||||
case .custom_emoji(let string):
|
||||
str.append(AttributedString("<emoji>", attributes: AttributeContainer([.font: font])))
|
||||
let image = UIImage(named: "amog")!.imageWith(newSize: CGSize(width: fontSize, height: fontSize))
|
||||
|
||||
textParts.append(Text(Image(uiImage: image)))
|
||||
}
|
||||
}
|
||||
|
||||
return str
|
||||
|
||||
if textParts.count > 0 {
|
||||
let first = textParts.removeFirst()
|
||||
return textParts.reduce(first, { a, b in a + b })
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var body: some View {
|
||||
Text(buildContent())
|
||||
if let text = buildContent() {
|
||||
text
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ParserPreview: PreviewProvider {
|
||||
static let viewState = ViewState.preview().applySystemScheme(theme: .light)
|
||||
|
||||
|
||||
static var previews: some View {
|
||||
Contents(text: .constant("hello <@0>, checkout <#0>!"))
|
||||
Contents(text: .constant("hello <@0>, checkout <#0>!"), fontSize: 16)
|
||||
.applyPreviewModifiers(withState: viewState)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ class MessageContentsViewModel: ObservableObject, Equatable {
|
||||
@Binding var channel: Channel
|
||||
@Binding var channelReplies: [Reply]
|
||||
@Binding var editing: Message?
|
||||
|
||||
|
||||
var channelScrollPosition: ChannelScrollController
|
||||
|
||||
init(viewState: ViewState, message: Binding<Message>, author: Binding<User>, member: Binding<Member?>, server: Binding<Server?>, channel: Binding<Channel>, replies: Binding<[Reply]>, channelScrollPosition: ChannelScrollController, editing: Binding<Message?>) {
|
||||
@@ -58,14 +58,14 @@ struct MessageContentsView: View {
|
||||
@State var showReactSheet: Bool = false
|
||||
@State var isStatic: Bool
|
||||
|
||||
private var isModeratorInChannel: Bool {
|
||||
private var canManageMessages: Bool {
|
||||
let member = viewModel.server.flatMap {
|
||||
viewState.members[$0.id]?[viewState.currentUser!.id]
|
||||
}
|
||||
|
||||
|
||||
let permissions = resolveChannelPermissions(from: viewState.currentUser!, targettingUser: viewState.currentUser!, targettingMember: member, channel: viewModel.channel, server: viewModel.server)
|
||||
|
||||
return permissions.contains(.manageChannel)
|
||||
|
||||
return permissions.contains(.manageMessages)
|
||||
}
|
||||
|
||||
private var isMessageAuthor: Bool {
|
||||
@@ -73,16 +73,16 @@ struct MessageContentsView: View {
|
||||
}
|
||||
|
||||
private var canDeleteMessage: Bool {
|
||||
return isMessageAuthor || isModeratorInChannel
|
||||
return isMessageAuthor || canManageMessages
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading) {
|
||||
if let content = Binding(viewModel.$message.content) {
|
||||
Contents(text: content)
|
||||
.font(.body)
|
||||
Contents(text: content, fontSize: 17)
|
||||
//.font(.body)
|
||||
}
|
||||
|
||||
|
||||
if let embeds = Binding(viewModel.$message.embeds) {
|
||||
ForEach(embeds, id: \.wrappedValue) { embed in
|
||||
MessageEmbed(embed: embed)
|
||||
|
||||
@@ -101,8 +101,8 @@ struct MessageEmbed: View {
|
||||
}
|
||||
|
||||
if let description = embed.description {
|
||||
Contents(text: .constant(description))
|
||||
.font(.footnote)
|
||||
Contents(text: .constant(description), fontSize: 13)
|
||||
//.font(.footnote)
|
||||
}
|
||||
|
||||
if let special = embed.special, special != .none {
|
||||
|
||||
@@ -168,7 +168,7 @@ struct UserSheet: View {
|
||||
Text("Bio")
|
||||
.font(.caption)
|
||||
|
||||
Contents(text: .constant(bio))
|
||||
Contents(text: .constant(bio), fontSize: 17)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// UIImage.swift
|
||||
// Revolt
|
||||
//
|
||||
// Created by Angelo on 19/07/2024.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
extension UIImage {
|
||||
func imageWith(newSize: CGSize) -> UIImage {
|
||||
let image = UIGraphicsImageRenderer(size: newSize).image { _ in
|
||||
draw(in: CGRect(origin: .zero, size: newSize))
|
||||
}
|
||||
|
||||
return image
|
||||
}
|
||||
}
|
||||
@@ -133,7 +133,7 @@ public class ViewState: ObservableObject {
|
||||
static var application: NSApplication? = nil
|
||||
#endif
|
||||
|
||||
var http: HTTPClient = HTTPClient(token: nil, baseURL: "http://local.angelomanca.com:8000")
|
||||
var http: HTTPClient = HTTPClient(token: nil, baseURL: "https://revolt.chat/api")
|
||||
var ws: WebSocketStream? = nil
|
||||
var apiInfo: ApiInfo? = nil
|
||||
|
||||
|
||||
Reference in New Issue
Block a user