diff --git a/Clipbara/AppState.swift b/Clipbara/AppState.swift index e1f702b..1b0f5e1 100644 --- a/Clipbara/AppState.swift +++ b/Clipbara/AppState.swift @@ -65,6 +65,14 @@ final class AppState { } } + /// 패널/핀보드 카드에서 공통으로 쓰는 붙여넣기 경로. + /// - Parameter asPlainText: nil이면 설정 + Shift 조합으로 자동 판단 + func paste(_ item: ClipboardItem, asPlainText: Bool? = nil) { + clipboardMonitor.skipNextChange() + pasteService.paste(item: item, asPlainText: asPlainText) + hidePanel() + } + func hidePanel() { previewItem = nil panelToast = nil diff --git a/Clipbara/Services/PasteService.swift b/Clipbara/Services/PasteService.swift index 7f2c26e..9ebe102 100644 --- a/Clipbara/Services/PasteService.swift +++ b/Clipbara/Services/PasteService.swift @@ -5,7 +5,34 @@ struct PasteService { private static let tempDir = NSTemporaryDirectory() + "Clipbara/" - func paste(item: ClipboardItem) { + /// 설정 > General의 "항상 일반 텍스트로 붙여넣기" 값 + nonisolated static let alwaysPlainTextDefaultsKey = "alwaysPastePlainText" + + /// 서식 제거가 의미 있는 항목인지 (RTF/HTML만 해당) + static func supportsPlainText(_ item: ClipboardItem) -> Bool { + switch item.contentType { + case .richText, .html: + return !(item.textContent?.isEmpty ?? true) + default: + return false + } + } + + /// 기본값(설정)과 Shift 수정자를 XOR로 합친다. + /// 설정이 꺼져 있으면 Shift = 서식 제거, 켜져 있으면 Shift = 서식 유지. + static func resolvePlainText() -> Bool { + let always = UserDefaults.standard.bool(forKey: alwaysPlainTextDefaultsKey) + let shiftHeld = NSEvent.modifierFlags.contains(.shift) + return always != shiftHeld + } + + /// - Parameter asPlainText: nil이면 설정 + Shift 조합으로 자동 판단 + func paste(item: ClipboardItem, asPlainText: Bool? = nil) { + if asPlainText ?? Self.resolvePlainText(), Self.supportsPlainText(item) { + pastePlainText(item: item) + return + } + let pasteboard = NSPasteboard.general pasteboard.clearContents() diff --git a/Clipbara/Views/ClipboardCardView.swift b/Clipbara/Views/ClipboardCardView.swift index 43167a4..f960637 100644 --- a/Clipbara/Views/ClipboardCardView.swift +++ b/Clipbara/Views/ClipboardCardView.swift @@ -62,7 +62,12 @@ struct ClipboardCardView: View { @ViewBuilder private var menuItems: some View { - Button("Paste") { onPaste(item) } + if PasteService.supportsPlainText(item) { + Button("Paste with Formatting") { appState.paste(item, asPlainText: false) } + Button("Paste as Plain Text") { appState.paste(item, asPlainText: true) } + } else { + Button("Paste") { onPaste(item) } + } if showsManagementMenu { Divider() Button("Rename") { diff --git a/Clipbara/Views/Settings/GeneralSettingsTab.swift b/Clipbara/Views/Settings/GeneralSettingsTab.swift index 798320a..9a6dda0 100644 --- a/Clipbara/Views/Settings/GeneralSettingsTab.swift +++ b/Clipbara/Views/Settings/GeneralSettingsTab.swift @@ -7,6 +7,7 @@ struct GeneralSettingsTab: View { @Environment(\.modelContext) private var modelContext @Environment(AppState.self) private var appState @AppStorage("historyLimit") private var historyLimit: Int = 500 + @AppStorage(PasteService.alwaysPlainTextDefaultsKey) private var alwaysPastePlainText: Bool = false @State private var launchAtLogin: Bool = SMAppService.mainApp.status == .enabled @State private var transferMessage: String? @State private var showTransferAlert = false @@ -35,6 +36,23 @@ struct GeneralSettingsTab: View { } } + Section("Pasting") { + HStack { + VStack(alignment: .leading, spacing: 2) { + HStack(spacing: 5) { + Text("Always Paste as Plain Text") + InfoHoverButton(text: "Removes fonts, colors, and links from rich text and HTML clips, pasting only the text.") + } + Text("Hold \u{21e7} while pasting to switch for a single paste.") + .font(.system(size: 11)) + .foregroundStyle(.secondary) + } + Spacer() + Toggle("", isOn: $alwaysPastePlainText) + .labelsHidden() + } + } + Section("Backup") { LabeledContent("Export history, pinboards, and settings to a JSON file.") { Button("Export\u{2026}") { exportHistory() }