PunctuationCheckerViewModel.swift 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  1. import AppKit
  2. import Combine
  3. import Foundation
  4. @MainActor
  5. final class PunctuationCheckerViewModel: ObservableObject {
  6. static let maxWords = 1_000
  7. @Published var text: String = ""
  8. @Published private(set) var correctedText: String = ""
  9. @Published private(set) var issues: [GrammarIssue] = []
  10. @Published private(set) var hasResults = false
  11. @Published private(set) var isChecking = false
  12. @Published private(set) var errorMessage: String?
  13. private let punctuationCheckService: any PunctuationCheckServicing
  14. private let textExtractionService: any TextExtracting
  15. init(
  16. punctuationCheckService: any PunctuationCheckServicing = PunctuationCheckService(),
  17. textExtractionService: any TextExtracting = TextExtractionService()
  18. ) {
  19. self.punctuationCheckService = punctuationCheckService
  20. self.textExtractionService = textExtractionService
  21. }
  22. var wordCountLimitLabel: String {
  23. "\(text.wordCount)/\(Self.maxWords)"
  24. }
  25. var canCheck: Bool {
  26. !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty && !isChecking
  27. }
  28. func pasteText() {
  29. guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
  30. text = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
  31. resetResults()
  32. }
  33. func uploadFile() {
  34. Task {
  35. do {
  36. guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
  37. text = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
  38. resetResults()
  39. } catch {
  40. errorMessage = error.localizedDescription
  41. }
  42. }
  43. }
  44. func notifyWordLimitReached() {
  45. errorMessage = WordLimit.message(maxWords: Self.maxWords)
  46. }
  47. func handleTextChange() {
  48. let (limited, exceeded) = WordLimit.apply(text, maxWords: Self.maxWords)
  49. if exceeded {
  50. text = limited
  51. errorMessage = WordLimit.message(maxWords: Self.maxWords)
  52. } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
  53. errorMessage = nil
  54. }
  55. if hasResults {
  56. resetResults()
  57. }
  58. }
  59. func clearText() {
  60. text = ""
  61. resetResults()
  62. }
  63. func resetResults() {
  64. correctedText = ""
  65. issues = []
  66. hasResults = false
  67. errorMessage = nil
  68. }
  69. func checkPunctuation() {
  70. let content = text.trimmingCharacters(in: .whitespacesAndNewlines)
  71. guard !content.isEmpty else {
  72. resetResults()
  73. return
  74. }
  75. isChecking = true
  76. errorMessage = nil
  77. Task {
  78. do {
  79. let result = try await punctuationCheckService.checkPunctuation(in: content)
  80. correctedText = result.correctedText
  81. issues = result.issues
  82. hasResults = true
  83. } catch {
  84. errorMessage = error.localizedDescription
  85. }
  86. isChecking = false
  87. }
  88. }
  89. func copyCorrectedText() {
  90. guard !correctedText.isEmpty else { return }
  91. NSPasteboard.general.clearContents()
  92. NSPasteboard.general.setString(correctedText, forType: .string)
  93. }
  94. }