From fa1ed4c149a9e78b9ced5c7410dfcf9aae87fc5e Mon Sep 17 00:00:00 2001 From: mobrava <82764703+mobrava@users.noreply.github.com> Date: Sat, 22 Aug 2026 17:37:59 +0900 Subject: [PATCH] Add paste as plain text (Shift modifier, context menu, setting) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - PasteService.paste(item:asPlainText:): nil이면 설정값과 Shift를 XOR로 판단 - 카드 컨텍스트 메뉴에 Paste with Formatting / Paste as Plain Text 분리 (RTF/HTML 클립만) - 설정 > General에 Always Paste as Plain Text 토글 추가 - 버전/appcast는 의도적으로 건드리지 않음 (1.3.0 App Store 심사 중) Refs #14 --- Clipbara/AppState.swift | 8 +++++ Clipbara/Services/PasteService.swift | 29 ++++++++++++++++++- Clipbara/Views/ClipboardCardView.swift | 7 ++++- .../Views/Settings/GeneralSettingsTab.swift | 18 ++++++++++++ 4 files changed, 60 insertions(+), 2 deletions(-) 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() }