Source code for the WriteFreely SwiftUI app for iOS, iPadOS, and macOS
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

127 lines
4.7 KiB

  1. import SwiftUI
  2. import CoreData
  3. class PostListModel: ObservableObject {
  4. func remove(_ post: WFAPost) {
  5. withAnimation {
  6. LocalStorageManager.standard.container.viewContext.delete(post)
  7. LocalStorageManager.standard.saveContext()
  8. }
  9. }
  10. func purgePublishedPosts() {
  11. let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "WFAPost")
  12. fetchRequest.predicate = NSPredicate(format: "status != %i", 0)
  13. let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)
  14. do {
  15. try LocalStorageManager.standard.container.viewContext.executeAndMergeChanges(using: deleteRequest)
  16. } catch {
  17. print("Error: Failed to purge cached posts.")
  18. }
  19. }
  20. func getBodyPreview(of post: WFAPost) -> String {
  21. var elidedPostBody: String = ""
  22. // Strip any markdown from the post body.
  23. let strippedPostBody = stripMarkdown(from: post.body)
  24. // Extract lede from post.
  25. elidedPostBody = extractLede(from: strippedPostBody)
  26. return elidedPostBody
  27. }
  28. }
  29. private extension PostListModel {
  30. func stripMarkdown(from string: String) -> String {
  31. var strippedString = string
  32. strippedString = stripHeadingOctothorpes(from: strippedString)
  33. strippedString = stripImages(from: strippedString, keepAltText: true)
  34. return strippedString
  35. }
  36. func stripHeadingOctothorpes(from string: String) -> String {
  37. let newLines = CharacterSet.newlines
  38. var processedComponents: [String] = []
  39. let components = string.components(separatedBy: newLines)
  40. for component in components {
  41. if component.isEmpty {
  42. continue
  43. }
  44. var newString = component
  45. while newString.first == "#" {
  46. newString.removeFirst()
  47. }
  48. if newString.hasPrefix(" ") {
  49. newString.removeFirst()
  50. }
  51. processedComponents.append(newString)
  52. }
  53. let headinglessString = processedComponents.joined(separator: "\n\n")
  54. return headinglessString
  55. }
  56. func stripImages(from string: String, keepAltText: Bool = false) -> String {
  57. let pattern = #"!\[[\"]?(.*?)[\"|]?\]\(.*?\)"#
  58. var processedComponents: [String] = []
  59. let components = string.components(separatedBy: .newlines)
  60. for component in components {
  61. if component.isEmpty { continue }
  62. var processedString: String = component
  63. if keepAltText {
  64. let regex = try? NSRegularExpression(pattern: pattern, options: [])
  65. if let matches = regex?.matches(
  66. in: component, options: [], range: NSRange(location: 0, length: component.utf16.count)
  67. ) {
  68. for match in matches {
  69. if let range = Range(match.range(at: 1), in: component) {
  70. processedString = "\(component[range])"
  71. }
  72. }
  73. }
  74. } else {
  75. let range = component.startIndex..<component.endIndex
  76. processedString = component.replacingOccurrences(
  77. of: pattern,
  78. with: "",
  79. options: .regularExpression,
  80. range: range
  81. )
  82. }
  83. if processedString.isEmpty { continue }
  84. processedComponents.append(processedString)
  85. }
  86. return processedComponents.joined(separator: "\n\n")
  87. }
  88. func extractLede(from string: String) -> String {
  89. let truncatedString = string.prefix(80)
  90. let terminatingPunctuation = ".。?"
  91. let terminatingCharacters = CharacterSet(charactersIn: terminatingPunctuation).union(.newlines)
  92. var lede: String = ""
  93. let sentences = truncatedString.components(separatedBy: terminatingCharacters)
  94. if let firstSentence = (sentences.filter { !$0.isEmpty }).first {
  95. if truncatedString.count > firstSentence.count {
  96. if terminatingPunctuation.contains(truncatedString[firstSentence.endIndex]) {
  97. lede = String(truncatedString[...firstSentence.endIndex])
  98. } else {
  99. lede = firstSentence
  100. }
  101. } else if truncatedString.count == firstSentence.count {
  102. if string.count > 80 {
  103. if let endOfStringIndex = truncatedString.lastIndex(of: " ") {
  104. lede = truncatedString[..<endOfStringIndex] + "…"
  105. }
  106. } else {
  107. lede = firstSentence
  108. }
  109. }
  110. }
  111. return lede
  112. }
  113. }