Skip to content

Commit

Permalink
General: Improve display of images (#100)
Browse files Browse the repository at this point in the history
* Add helper functions for replacing markdown images

* Fix upside down images and large height

* Replace markdown images with preview
  • Loading branch information
anian03 authored Nov 14, 2024
1 parent da16cb0 commit 8fcf177
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 23 deletions.
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ let package = Package(
name: "PushNotifications",
dependencies: [
"APIClient",
"ArtemisMarkdown",
"Common",
"CryptoSwift",
"DesignLibrary",
Expand Down
6 changes: 4 additions & 2 deletions Sources/ArtemisMarkdown/ArtemisImageProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Created by Anian Schleyer on 25.10.24.
//

import DesignLibrary
import MarkdownUI
import NetworkImage
import SwiftUI
Expand All @@ -13,14 +14,15 @@ struct ArtemisImageProvider: ImageProvider {
static let assetProvider = AssetImageProvider(name: {
$0.host(percentEncoded: false) ?? ""
}, bundle: .module)
static let networkProvider = DefaultImageProvider()

@ViewBuilder
func makeImage(url: URL?) -> some View {
if url?.absoluteString.contains("local://") == true {
Self.assetProvider.makeImage(url: url)
} else {
Self.networkProvider.makeImage(url: url)
ArtemisAsyncImage(imageURL: url) {}
.scaledToFit()
.frame(maxWidth: .infinity, maxHeight: 400, alignment: .leading)
}
}
}
Expand Down
86 changes: 86 additions & 0 deletions Sources/ArtemisMarkdown/String+MarkdownImage.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//
// String+MarkdownImage.swift
// ArtemisCore
//
// Created by Anian Schleyer on 13.11.24.
//

import Foundation

public extension String {
/**
Surrounds markdown images with newlines to convert inline images into block images.
*/
public func surroundingMarkdownImagesWithNewlines() -> String {
modifyingMarkdownImagesOutsideStyledBlocks()
}

/**
Replaces markdown images with a placeholder: [image 🏞️]
Used for notification previews.
*/
public func replacingMarkdownImages() -> String {
modifyingMarkdownImagesOutsideStyledBlocks(replaceWith: "[image 🏞️]")
}

/**
Either replaces images in Markdown text with a given placeholder or surrounds them with newlines if no placeholder is given
*/
private func modifyingMarkdownImagesOutsideStyledBlocks(replaceWith: String? = nil) -> String {
// Regex for code blocks, references and images outside styled blocks
let codeBlockPattern = #"(```[\s\S]*?```|(?: {4}|\t)[^\n]*\n?)"#
let referencePattern = #"(^>.*(?:\n|$))+"#
let imagePattern = #"(?<!\*\*|\*|<ins>|`)(\!\[[^\]]*\]\([^)]*\))(?!\*\*|\*|</ins>|`)"#

guard let codeBlockRegex = try? NSRegularExpression(pattern: codeBlockPattern, options: []),
let referenceRegex = try? NSRegularExpression(pattern: referencePattern, options: []),
let imageRegex = try? NSRegularExpression(pattern: imagePattern, options: []) else {
print("Failed to create regex for image replacements")
return self
}

// Extract and replace code blocks with a placeholder
let codePlaceholder = UUID().uuidString
let referencePlaceholder = UUID().uuidString
var codeBlocks: [String] = []
var references: [String] = []
var withoutCodeBlocks = self

var range = NSRange(withoutCodeBlocks.startIndex..<withoutCodeBlocks.endIndex, in: withoutCodeBlocks)
codeBlockRegex.enumerateMatches(in: withoutCodeBlocks, options: [], range: range) { match, _, _ in
if let matchRange = match?.range, let range = Range(matchRange, in: withoutCodeBlocks) {
let codeBlock = String(withoutCodeBlocks[range])
codeBlocks.append(codeBlock)
withoutCodeBlocks.replaceSubrange(range, with: codePlaceholder)
}
}
range = NSRange(withoutCodeBlocks.startIndex..<withoutCodeBlocks.endIndex, in: withoutCodeBlocks)
referenceRegex.enumerateMatches(in: withoutCodeBlocks, options: [], range: range) { match, _, _ in
if let matchRange = match?.range, let range = Range(matchRange, in: withoutCodeBlocks) {
let reference = String(withoutCodeBlocks[range])
references.append(reference)
withoutCodeBlocks.replaceSubrange(range, with: referencePlaceholder)
}
}

// Surround images with newlines or replace them
range = NSRange(withoutCodeBlocks.startIndex..<withoutCodeBlocks.endIndex, in: withoutCodeBlocks)
let replacementPattern = replaceWith ?? "\n\n$1\n\n"
withoutCodeBlocks = imageRegex.stringByReplacingMatches(in: withoutCodeBlocks, options: [], range: range, withTemplate: replacementPattern)

// Restore the code blocks in the processed text
var result = withoutCodeBlocks
for codeBlock in codeBlocks {
if let range = result.range(of: codePlaceholder) {
result.replaceSubrange(range, with: codeBlock)
}
}
for reference in references {
if let range = result.range(of: referencePlaceholder) {
result.replaceSubrange(range, with: reference)
}
}

return result
}
}
53 changes: 32 additions & 21 deletions Sources/PushNotifications/Models/PushNotification.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

// swiftlint:disable file_length

import ArtemisMarkdown
import Foundation

enum PushNotificationVersionError: Error {
Expand Down Expand Up @@ -278,35 +279,40 @@ public enum PushNotificationType: String, Codable {
//
case .newReplyForCoursePost:
guard notificationPlaceholders.count > 5 else { return nil }
return R.string.localizable.artemisAppGroupNotificationTextNewReplyForCoursePost(notificationPlaceholders[3],
notificationPlaceholders[4])
return R.string.localizable.artemisAppGroupNotificationTextNewReplyForCoursePost(
notificationPlaceholders[3],
notificationPlaceholders[4].replacingMarkdownImages())
case .newReplyForExamPost:
return nil
case .newReplyForExercisePost:
guard notificationPlaceholders.count > 7 else { return nil }
return R.string.localizable.artemisAppGroupNotificationTextNewReplyForExercisePost(notificationPlaceholders[3],
notificationPlaceholders[4],
notificationPlaceholders[7])
return R.string.localizable.artemisAppGroupNotificationTextNewReplyForExercisePost(
notificationPlaceholders[3],
notificationPlaceholders[4].replacingMarkdownImages(),
notificationPlaceholders[7])
case .newReplyForLecturePost:
guard notificationPlaceholders.count > 7 else { return nil }
return R.string.localizable.artemisAppGroupNotificationTextNewReplyForLecturePost(notificationPlaceholders[3],
notificationPlaceholders[4],
notificationPlaceholders[7])
return R.string.localizable.artemisAppGroupNotificationTextNewReplyForLecturePost(
notificationPlaceholders[3],
notificationPlaceholders[4].replacingMarkdownImages(),
notificationPlaceholders[7])
//
case .newAnnouncementPost, .newCoursePost:
guard notificationPlaceholders.count > 1 else { return nil }
// Post Content
return notificationPlaceholders[1]
return notificationPlaceholders[1].replacingMarkdownImages()
case .newExamPost:
return nil
case .newExercisePost:
guard notificationPlaceholders.count > 4 else { return nil }
return R.string.localizable.artemisAppGroupNotificationTextNewExercisePost(notificationPlaceholders[1],
notificationPlaceholders[3])
return R.string.localizable.artemisAppGroupNotificationTextNewExercisePost(
notificationPlaceholders[1].replacingMarkdownImages(),
notificationPlaceholders[3])
case .newLecturePost:
guard notificationPlaceholders.count > 4 else { return nil }
return R.string.localizable.artemisAppGroupNotificationTextNewLecturePost(notificationPlaceholders[1],
notificationPlaceholders[3])
return R.string.localizable.artemisAppGroupNotificationTextNewLecturePost(
notificationPlaceholders[1].replacingMarkdownImages(),
notificationPlaceholders[3])
//
case .courseArchiveStarted:
guard notificationPlaceholders.count > 0 else { return nil }
Expand Down Expand Up @@ -411,21 +417,26 @@ public enum PushNotificationType: String, Codable {
guard notificationPlaceholders.count > 5 else { return nil }
switch notificationPlaceholders[5] {
case "channel":
return R.string.localizable.artemisAppConversationNotificationTextNewMessageChannel(notificationPlaceholders[3],
notificationPlaceholders[4],
notificationPlaceholders[1])
return R.string.localizable.artemisAppConversationNotificationTextNewMessageChannel(
notificationPlaceholders[3],
notificationPlaceholders[4],
notificationPlaceholders[1].replacingMarkdownImages())
case "groupChat":
return R.string.localizable.artemisAppConversationNotificationTextNewMessageGroupChat(notificationPlaceholders[4],
notificationPlaceholders[1])
return R.string.localizable.artemisAppConversationNotificationTextNewMessageGroupChat(
notificationPlaceholders[4],
notificationPlaceholders[1].replacingMarkdownImages())
case "oneToOneChat":
return R.string.localizable.artemisAppConversationNotificationTextNewMessageDirect(notificationPlaceholders[4],
notificationPlaceholders[1])
return R.string.localizable.artemisAppConversationNotificationTextNewMessageDirect(
notificationPlaceholders[4],
notificationPlaceholders[1].replacingMarkdownImages())
default:
return nil
}
case .conversationNewReplyMessage:
guard notificationPlaceholders.count > 6 else { return nil }
return R.string.localizable.artemisAppSingleUserNotificationTextMessageReply(notificationPlaceholders[6], notificationPlaceholders[1])
return R.string.localizable.artemisAppSingleUserNotificationTextMessageReply(
notificationPlaceholders[6],
notificationPlaceholders[1].replacingMarkdownImages())
case .conversationCreateOneToOneChat:
return nil
case .conversationCreateGroupChat:
Expand Down

0 comments on commit 8fcf177

Please sign in to comment.