WordsCountViewModel.swift 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. import AppKit
  2. import Combine
  3. import Foundation
  4. @MainActor
  5. final class WordsCountViewModel: ObservableObject {
  6. static let maxWords = 1_000
  7. @Published var text: String = ""
  8. @Published private(set) var errorMessage: String?
  9. private let textExtractionService: any TextExtracting
  10. init(textExtractionService: any TextExtracting = TextExtractionService()) {
  11. self.textExtractionService = textExtractionService
  12. }
  13. var characterCount: Int { text.count }
  14. var wordCountLimitLabel: String {
  15. "\(wordCount)/\(Self.maxWords)"
  16. }
  17. var wordCount: Int {
  18. text.wordCount
  19. }
  20. var wordCountLabel: String {
  21. "\(wordCount) Words Count"
  22. }
  23. var sentenceCount: Int {
  24. let trimmed = text.trimmingCharacters(in: .whitespacesAndNewlines)
  25. guard !trimmed.isEmpty else { return 0 }
  26. let sentences = trimmed.components(separatedBy: CharacterSet(charactersIn: ".!?"))
  27. .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
  28. .filter { !$0.isEmpty }
  29. return max(sentences.count, 1)
  30. }
  31. var paragraphCount: Int {
  32. let paragraphs = text
  33. .components(separatedBy: .newlines)
  34. .map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
  35. .filter { !$0.isEmpty }
  36. return paragraphs.count
  37. }
  38. func pasteText() {
  39. guard let clipboard = NSPasteboard.general.string(forType: .string) else { return }
  40. text = WordLimit.applyImported(clipboard, maxWords: Self.maxWords, setError: { errorMessage = $0 })
  41. }
  42. func uploadFile() {
  43. Task {
  44. do {
  45. guard let extracted = try await textExtractionService.pickAndExtractText() else { return }
  46. text = WordLimit.applyImported(extracted, maxWords: Self.maxWords, setError: { errorMessage = $0 })
  47. } catch {
  48. errorMessage = UserFacingError.message(for: error)
  49. }
  50. }
  51. }
  52. func notifyWordLimitReached() {
  53. errorMessage = WordLimit.message(maxWords: Self.maxWords)
  54. }
  55. func handleTextChange() {
  56. let (limited, exceeded) = WordLimit.apply(text, maxWords: Self.maxWords)
  57. if exceeded {
  58. text = limited
  59. errorMessage = WordLimit.message(maxWords: Self.maxWords)
  60. } else if errorMessage == WordLimit.message(maxWords: Self.maxWords) {
  61. errorMessage = nil
  62. }
  63. }
  64. func clearText() {
  65. text = ""
  66. errorMessage = nil
  67. }
  68. }