Working Text Clipboard

This commit is contained in:
Hariharan Mudaliar 2025-07-17 20:21:49 +05:30
commit 4890e6fc28
8 changed files with 631 additions and 4 deletions

View file

@ -0,0 +1,217 @@
//
// ClipboardManager.swift
// DynamicIsland
//
// Created by GitHub Copilot on 17/07/25.
//
import AppKit
import SwiftUI
import Combine
import Foundation
// Clipboard item data structure
struct ClipboardItem: Identifiable, Codable {
let id = UUID()
let content: String
let type: ClipboardItemType
let timestamp: Date
let preview: String
init(content: String, type: ClipboardItemType) {
self.content = content
self.type = type
self.timestamp = Date()
self.preview = ClipboardItem.generatePreview(content: content, type: type)
}
static func generatePreview(content: String, type: ClipboardItemType) -> String {
switch type {
case .text:
return String(content.prefix(50))
case .url:
if let url = URL(string: content) {
return url.lastPathComponent.isEmpty ? url.host ?? content : url.lastPathComponent
}
return String(content.prefix(50))
case .file:
if let url = URL(string: content) {
return url.lastPathComponent
}
return "File"
case .image:
return "Image"
case .rtf:
// Remove RTF formatting for preview
let stripped = content.replacingOccurrences(of: "\\[^\\s]*\\s?", with: "", options: .regularExpression)
return String(stripped.prefix(50))
case .unknown:
return String(content.prefix(50))
}
}
}
enum ClipboardItemType: String, CaseIterable, Codable {
case text = "text"
case url = "url"
case file = "file"
case image = "image"
case rtf = "rtf"
case unknown = "unknown"
var icon: String {
switch self {
case .text: return "doc.text"
case .url: return "link"
case .file: return "doc"
case .image: return "photo"
case .rtf: return "doc.richtext"
case .unknown: return "questionmark.circle"
}
}
var displayName: String {
switch self {
case .text: return "Text"
case .url: return "URL"
case .file: return "File"
case .image: return "Image"
case .rtf: return "Rich Text"
case .unknown: return "Unknown"
}
}
}
class ClipboardManager: ObservableObject {
static let shared = ClipboardManager()
@Published var clipboardHistory: [ClipboardItem] = []
@Published var isMonitoring: Bool = false
private var timer: Timer?
private var lastChangeCount: Int = 0
private let maxHistoryItems = 3
private init() {
lastChangeCount = NSPasteboard.general.changeCount
loadHistoryFromDefaults()
}
// MARK: - Public Methods
func startMonitoring() {
guard !isMonitoring else { return }
isMonitoring = true
timer = Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { [weak self] _ in
self?.checkClipboard()
}
}
func stopMonitoring() {
isMonitoring = false
timer?.invalidate()
timer = nil
}
func copyToClipboard(_ item: ClipboardItem) {
NSPasteboard.general.clearContents()
NSPasteboard.general.setString(item.content, forType: .string)
}
func deleteItem(_ item: ClipboardItem) {
clipboardHistory.removeAll { $0.id == item.id }
saveHistoryToDefaults()
}
func clearHistory() {
clipboardHistory.removeAll()
saveHistoryToDefaults()
}
// MARK: - Private Methods
private func checkClipboard() {
let currentChangeCount = NSPasteboard.general.changeCount
guard currentChangeCount != lastChangeCount else { return }
lastChangeCount = currentChangeCount
guard let clipboardItem = getCurrentClipboardItem() else { return }
// Don't add duplicate items
if !clipboardHistory.contains(where: { $0.content == clipboardItem.content && $0.type == clipboardItem.type }) {
addToHistory(clipboardItem)
}
}
private func getCurrentClipboardItem() -> ClipboardItem? {
let pasteboard = NSPasteboard.general
// Check for URL first
if let url = pasteboard.string(forType: .URL) ?? pasteboard.string(forType: .fileURL) {
return ClipboardItem(content: url, type: .url)
}
// Check for file URLs
if let fileURLs = pasteboard.propertyList(forType: .fileURL) as? [String], !fileURLs.isEmpty {
return ClipboardItem(content: fileURLs.first!, type: .file)
}
// Check for images
if pasteboard.canReadItem(withDataConformingToTypes: [NSPasteboard.PasteboardType.tiff.rawValue, NSPasteboard.PasteboardType.png.rawValue]) {
return ClipboardItem(content: "Image data", type: .image)
}
// Check for RTF
if let rtfData = pasteboard.data(forType: .rtf),
let rtfString = NSAttributedString(rtf: rtfData, documentAttributes: nil)?.string {
return ClipboardItem(content: rtfString, type: .rtf)
}
// Check for plain text
if let string = pasteboard.string(forType: .string) {
// Determine if it's a URL
if string.hasPrefix("http://") || string.hasPrefix("https://") || string.hasPrefix("file://") {
return ClipboardItem(content: string, type: .url)
}
return ClipboardItem(content: string, type: .text)
}
return nil
}
private func addToHistory(_ item: ClipboardItem) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
// Remove any existing items with the same content
self.clipboardHistory.removeAll { $0.content == item.content }
// Add to beginning of array
self.clipboardHistory.insert(item, at: 0)
// Keep only the most recent items
if self.clipboardHistory.count > self.maxHistoryItems {
self.clipboardHistory = Array(self.clipboardHistory.prefix(self.maxHistoryItems))
}
self.saveHistoryToDefaults()
}
}
// MARK: - Persistence
private func saveHistoryToDefaults() {
if let encoded = try? JSONEncoder().encode(clipboardHistory) {
UserDefaults.standard.set(encoded, forKey: "ClipboardHistory")
}
}
private func loadHistoryFromDefaults() {
if let data = UserDefaults.standard.data(forKey: "ClipboardHistory"),
let history = try? JSONDecoder().decode([ClipboardItem].self, from: data) {
clipboardHistory = history
}
}
}