Source code for the WriteFreely SwiftUI app for iOS, iPadOS, and macOS
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.
 
 
 

124 lignes
4.3 KiB

  1. import SwiftUI
  2. struct PostTextEditingView: View {
  3. @EnvironmentObject var model: WriteFreelyModel
  4. @ObservedObject var post: WFAPost
  5. @Binding var updatingFromServer: Bool
  6. @State private var appearance: PostAppearance = .serif
  7. @State private var combinedText = ""
  8. @State private var hasBeenEdited: Bool = false
  9. let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
  10. var body: some View {
  11. ZStack(alignment: .topLeading) {
  12. if combinedText.count == 0 {
  13. Text("Write…")
  14. .foregroundColor(Color(NSColor.placeholderTextColor))
  15. .padding(.horizontal, 5)
  16. .font(.custom(appearance.rawValue, size: 17, relativeTo: .body))
  17. }
  18. if post.appearance == "sans" {
  19. MacEditorTextView(
  20. text: $combinedText,
  21. isFirstResponder: combinedText.isEmpty,
  22. isEditable: true,
  23. font: NSFont(name: PostAppearance.sans.rawValue, size: 17),
  24. onEditingChanged: onEditingChanged,
  25. onCommit: onCommit,
  26. onTextChange: onTextChange
  27. )
  28. } else if post.appearance == "wrap" || post.appearance == "mono" || post.appearance == "code" {
  29. MacEditorTextView(
  30. text: $combinedText,
  31. isFirstResponder: combinedText.isEmpty,
  32. isEditable: true,
  33. font: NSFont(name: PostAppearance.mono.rawValue, size: 17),
  34. onEditingChanged: onEditingChanged,
  35. onCommit: onCommit,
  36. onTextChange: onTextChange
  37. )
  38. } else {
  39. MacEditorTextView(
  40. text: $combinedText,
  41. isFirstResponder: combinedText.isEmpty,
  42. isEditable: true,
  43. font: NSFont(name: PostAppearance.serif.rawValue, size: 17),
  44. onEditingChanged: onEditingChanged,
  45. onCommit: onCommit,
  46. onTextChange: onTextChange
  47. )
  48. }
  49. }
  50. .background(Color(NSColor.controlBackgroundColor))
  51. .onAppear(perform: {
  52. if post.title.isEmpty {
  53. self.combinedText = post.body
  54. } else {
  55. self.combinedText = "# \(post.title)\n\n\(post.body)"
  56. }
  57. })
  58. .onReceive(timer) { _ in
  59. if !post.body.isEmpty && hasBeenEdited {
  60. DispatchQueue.main.async {
  61. LocalStorageManager.standard.saveContext()
  62. hasBeenEdited = false
  63. }
  64. }
  65. }
  66. }
  67. private func onEditingChanged() {
  68. hasBeenEdited = true
  69. }
  70. private func onTextChange(_ text: String) {
  71. extractTitle(text)
  72. if !updatingFromServer {
  73. if post.status == PostStatus.published.rawValue {
  74. post.status = PostStatus.edited.rawValue
  75. }
  76. if post.status == PostStatus.edited.rawValue,
  77. post.title == model.editor.initialPostTitle,
  78. post.body == model.editor.initialPostBody {
  79. post.status = PostStatus.published.rawValue
  80. }
  81. }
  82. if updatingFromServer {
  83. self.updatingFromServer = false
  84. }
  85. hasBeenEdited = true
  86. }
  87. private func onCommit() {
  88. if !post.body.isEmpty && hasBeenEdited {
  89. DispatchQueue.main.async {
  90. LocalStorageManager.standard.saveContext()
  91. }
  92. }
  93. hasBeenEdited = false
  94. }
  95. private func extractTitle(_ text: String) {
  96. var detectedTitle: String
  97. if text.hasPrefix("# ") {
  98. let endOfTitleIndex = text.firstIndex(of: "\n") ?? text.endIndex
  99. detectedTitle = String(text[..<endOfTitleIndex])
  100. self.post.title = String(detectedTitle.dropFirst("# ".count))
  101. let remainingText = String(text.dropFirst(detectedTitle.count).dropFirst(1))
  102. if remainingText.hasPrefix("\n") {
  103. self.post.body = String(remainingText.dropFirst(1))
  104. } else {
  105. self.post.body = remainingText
  106. }
  107. } else {
  108. self.post.title = ""
  109. self.post.body = text
  110. }
  111. }
  112. }