Source code for the WriteFreely SwiftUI app for iOS, iPadOS, and macOS
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 
 
 

125 wiersze
4.4 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, 16)
  16. .padding(.vertical, 16)
  17. .font(.custom(appearance.rawValue, size: 17, relativeTo: .body))
  18. }
  19. if post.appearance == "sans" {
  20. MacEditorTextView(
  21. text: $combinedText,
  22. isFirstResponder: combinedText.isEmpty,
  23. isEditable: true,
  24. font: NSFont(name: PostAppearance.sans.rawValue, size: 17),
  25. onEditingChanged: onEditingChanged,
  26. onCommit: onCommit,
  27. onTextChange: onTextChange
  28. )
  29. } else if post.appearance == "wrap" || post.appearance == "mono" || post.appearance == "code" {
  30. MacEditorTextView(
  31. text: $combinedText,
  32. isFirstResponder: combinedText.isEmpty,
  33. isEditable: true,
  34. font: NSFont(name: PostAppearance.mono.rawValue, size: 17),
  35. onEditingChanged: onEditingChanged,
  36. onCommit: onCommit,
  37. onTextChange: onTextChange
  38. )
  39. } else {
  40. MacEditorTextView(
  41. text: $combinedText,
  42. isFirstResponder: combinedText.isEmpty,
  43. isEditable: true,
  44. font: NSFont(name: PostAppearance.serif.rawValue, size: 17),
  45. onEditingChanged: onEditingChanged,
  46. onCommit: onCommit,
  47. onTextChange: onTextChange
  48. )
  49. }
  50. }
  51. .background(Color(NSColor.controlBackgroundColor))
  52. .onAppear(perform: {
  53. if post.title.isEmpty {
  54. self.combinedText = post.body
  55. } else {
  56. self.combinedText = "# \(post.title)\n\n\(post.body)"
  57. }
  58. })
  59. .onReceive(timer) { _ in
  60. if !post.body.isEmpty && hasBeenEdited {
  61. DispatchQueue.main.async {
  62. LocalStorageManager.standard.saveContext()
  63. hasBeenEdited = false
  64. }
  65. }
  66. }
  67. }
  68. private func onEditingChanged() {
  69. hasBeenEdited = true
  70. }
  71. private func onTextChange(_ text: String) {
  72. extractTitle(text)
  73. if !updatingFromServer {
  74. if post.status == PostStatus.published.rawValue {
  75. post.status = PostStatus.edited.rawValue
  76. }
  77. if post.status == PostStatus.edited.rawValue,
  78. post.title == model.editor.initialPostTitle,
  79. post.body == model.editor.initialPostBody {
  80. post.status = PostStatus.published.rawValue
  81. }
  82. }
  83. if updatingFromServer {
  84. self.updatingFromServer = false
  85. }
  86. hasBeenEdited = true
  87. }
  88. private func onCommit() {
  89. if !post.body.isEmpty && hasBeenEdited {
  90. DispatchQueue.main.async {
  91. LocalStorageManager.standard.saveContext()
  92. }
  93. }
  94. hasBeenEdited = false
  95. }
  96. private func extractTitle(_ text: String) {
  97. var detectedTitle: String
  98. if text.hasPrefix("# ") {
  99. let endOfTitleIndex = text.firstIndex(of: "\n") ?? text.endIndex
  100. detectedTitle = String(text[..<endOfTitleIndex])
  101. self.post.title = String(detectedTitle.dropFirst("# ".count))
  102. let remainingText = String(text.dropFirst(detectedTitle.count).dropFirst(1))
  103. if remainingText.hasPrefix("\n") {
  104. self.post.body = String(remainingText.dropFirst(1))
  105. } else {
  106. self.post.body = remainingText
  107. }
  108. } else {
  109. self.post.title = ""
  110. self.post.body = text
  111. }
  112. }
  113. }