diff --git a/Build.xcconfig b/Build.xcconfig new file mode 100644 index 0000000..0637502 --- /dev/null +++ b/Build.xcconfig @@ -0,0 +1,12 @@ +// +// Build.xcconfig +// uPic +// +// Created by Licardo on 2025/11/10. +// + +// Configuration settings file format documentation can be found at: +// https://developer.apple.com/documentation/xcode/adding-a-build-configuration-file-to-your-project + +MARKETING_VERSION = 2.0.0 +CURRENT_PROJECT_VERSION = 1 diff --git a/macOS/AppDelegate.swift b/macOS/AppDelegate.swift index 464bd95..065276f 100644 --- a/macOS/AppDelegate.swift +++ b/macOS/AppDelegate.swift @@ -8,18 +8,22 @@ import AppKit import KeyboardShortcuts import SimpleLogger +import SwiftUI class AppDelegate: NSResponder, NSApplicationDelegate { var statusItem: NSStatusItem? + @Environment(\.openWindow) var openWindow func applicationWillFinishLaunching(_: Notification) { Noti.shared.requestNotificationAuthorization() // Add URL scheme listening - NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(self.handleGetURLEvent(event:withReplyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL)) + NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(handleGetURLEvent(event:withReplyEvent:)), forEventClass: AEEventClass(kInternetEventClass), andEventID: AEEventID(kAEGetURL)) } func applicationShouldHandleReopen(_: NSApplication, hasVisibleWindows _: Bool) -> Bool { + NSApp.activate(ignoringOtherApps: true) + openWindow(id: "settings") return true } diff --git a/macOS/Localizable.xcstrings b/macOS/Localizable.xcstrings index f9ba903..d4f0daa 100644 --- a/macOS/Localizable.xcstrings +++ b/macOS/Localizable.xcstrings @@ -1401,39 +1401,39 @@ } } }, - "Preview" : { - "comment" : "The label of a button that previews an image in a new window.", + "Print this help message" : { + "comment" : "A help message for the \"help\" option.", + "extractionState" : "stale", "isCommentAutoGenerated" : true, "localizations" : { "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "预览" + "value" : "打印此帮助信息" } } } }, - "Print this help message" : { - "comment" : "A help message for the \"help\" option.", - "extractionState" : "stale", + "Quality" : { + "comment" : "A label describing the quality setting for Weibo Quick.", "isCommentAutoGenerated" : true, "localizations" : { "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "打印此帮助信息" + "value" : "质量" } } } }, - "Quality" : { - "comment" : "A label describing the quality setting for Weibo Quick.", + "Quick Look" : { + "comment" : "The label of a button that previews an image in a new window.", "isCommentAutoGenerated" : true, "localizations" : { "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "质量" + "value" : "快速查看" } } } diff --git a/macOS/Manager/URLSchemeManager.swift b/macOS/Manager/URLSchemeManager.swift index 6f13d77..9857062 100644 --- a/macOS/Manager/URLSchemeManager.swift +++ b/macOS/Manager/URLSchemeManager.swift @@ -36,7 +36,7 @@ class URLSchemeManager { case "files": if keyValue.count == 2 { let pathStr = String(keyValue.last ?? "") - AppLogger.urlScheme.debug("URLScheme pload type file: \(pathStr)") + AppLogger.urlScheme.debug("URLScheme upload type file: \(pathStr)") await uploader.upload(fileURLs: [URL(filePath: pathStr)]) } case "url": @@ -48,7 +48,7 @@ class URLSchemeManager { } } case .some(let str) where str.contains("x-callback-url"): - AppLogger.urlScheme.debug("URLScheme pload type: x-callback-url") + AppLogger.urlScheme.debug("URLScheme upload type: x-callback-url") if str.contains("acceptSnip") { AppLogger.urlScheme.info("Processing x-callback-url request: \(keyValue)") diff --git a/macOS/Manager/UploadManager.swift b/macOS/Manager/UploadManager.swift index 4227415..1e0b1f5 100644 --- a/macOS/Manager/UploadManager.swift +++ b/macOS/Manager/UploadManager.swift @@ -39,7 +39,6 @@ public class UploadManager: ObservableObject { // MARK: - Dependencies private var modelContext: ModelContext? - private let notificationCenter = NotificationCenter.default // MARK: - Initialization @@ -82,7 +81,7 @@ public class UploadManager: ObservableObject { // 使用 DiskPermissionManager 管理磁盘访问权限 let diskPermissionManager = BookmarkManager.shared - let _ = diskPermissionManager.startDirectoryAccessing() + _ = diskPermissionManager.startDirectoryAccessing() var items: [UploadItem] = [] @@ -260,7 +259,11 @@ public class UploadManager: ObservableObject { let url = try await performSingleUpload(hostModel: hostModel, item: item) AppLogger.uploader.info("Upload successful: \(url)") - Noti.shared.postUploadSuccessful(url) + if Defaults[.autoCopyUrlToClipboard] { + Tools.shared.copyUrlsToClipboard([url], afterUpload: true) + } else { + Noti.shared.postUploadSuccessful("\(Tools.shared.formatOutputUrls([url]))") + } await saveToHistory(item: item, url: url, hostModel: hostModel) successCount += 1 diff --git a/macOS/Utils/Noti.swift b/macOS/Utils/Noti.swift index 0f1cee8..7fc0a56 100644 --- a/macOS/Utils/Noti.swift +++ b/macOS/Utils/Noti.swift @@ -20,10 +20,14 @@ extension Noti { } func postUploadSuccessful(_ body: String? = "") { + self.post(title: String(localized: "Uploaded successfully"), body: body) + } + + func postUploadAndCopyToClipboardSuccessful(_ body: String? = "") { self.post(title: String(localized: "Uploaded successfully"), subtitle: String(localized: "URL has been copied to the clipboard"), body: body) } - func postCopySuccessful(_ body: String? = "") { + func postCopyToClipboardSuccessful(_ body: String? = "") { self.post(title: String(localized: "URL has been copied to the clipboard"), body: body) } diff --git a/macOS/Utils/Tools.swift b/macOS/Utils/Tools.swift index 8847949..8b09a3c 100644 --- a/macOS/Utils/Tools.swift +++ b/macOS/Utils/Tools.swift @@ -12,9 +12,7 @@ import UPicCore class Tools { static let shared = Tools() - func copyUrls(_ urls: [String]) { - AppLogger.tools.debug("Ready to copy upload results to clipboard: \(urls.joined(separator: ","))") - + func copyUrlsToClipboard(_ urls: [String], afterUpload: Bool = false) { let outputUrls = formatOutputUrls(urls) let outputStr = outputUrls.joined(separator: "\n") NSPasteboard.general.clearContents() @@ -23,10 +21,14 @@ class Tools { AppLogger.tools.info("Copy upload result to clipboard: \(outputStr)") - Noti.shared.postCopySuccessful(outputStr) + if afterUpload { + Noti.shared.postUploadAndCopyToClipboardSuccessful(outputStr) + } else { + Noti.shared.postCopyToClipboardSuccessful(outputStr) + } } - private func formatOutputUrls(_ urls: [String], _ outputType: OutputFormatModel? = nil) -> [String] { + func formatOutputUrls(_ urls: [String], _ outputType: OutputFormatModel? = nil) -> [String] { let outputUrls = urls.map { url in OutputFormatModel.formatUrl(url, outputFormat: outputType) } diff --git a/macOS/View/Database/HistoryTable.swift b/macOS/View/Database/HistoryTableView.swift similarity index 81% rename from macOS/View/Database/HistoryTable.swift rename to macOS/View/Database/HistoryTableView.swift index f28a46b..df50b2c 100644 --- a/macOS/View/Database/HistoryTable.swift +++ b/macOS/View/Database/HistoryTableView.swift @@ -6,10 +6,10 @@ // import QuickLook +import SimpleLogger import SwiftData import SwiftUI import UPicCore -import SimpleLogger struct HistoryTableView: View { let uploadHistory: [UploadHistoryModel] @@ -57,29 +57,47 @@ struct HistoryTableView: View { } } .contextMenu(forSelectionType: UploadHistoryModel.ID.self) { selectedIds in - if let selectedId = selectedIds.first, let history = uploadHistory.first(where: { $0.id == selectedId }) { - Button("Preview", systemImage: "eye") { - if let url = URL(string: history.url) { - quickLookURL = url - } - } + let selectedHistories = selectedIds.compactMap { selectedId in + uploadHistory.first(where: { $0.id == selectedId }) + } - Button("Copy URL", systemImage: "clipboard") { - Tools.shared.copyUrls([history.url]) - } + Button("Copy URL", systemImage: "clipboard") { + Tools.shared.copyUrlsToClipboard(selectedHistories.compactMap { $0.url }) + } - Button("Open in Browser", systemImage: "network") { + Button("Open in Browser", systemImage: "network") { + for history in selectedHistories { if let url = URL(string: history.url) { openURL(url) } } + } + if selectedHistories.count == 1 { + // Quick Look 只对第一个选中的项目 Divider() - Button("Delete", systemImage: "trash", role: .destructive) { + Button("Quick Look", systemImage: "eye") { + if let firstHistory = selectedHistories.first, let url = URL(string: firstHistory.url) { + quickLookURL = url + } + } + } + + Divider() + + Button("Delete", systemImage: "trash", role: .destructive) { + for history in selectedHistories { deleteHistory(history) } } + } primaryAction: { selectedIds in + let selectedUrls = selectedIds.compactMap { selectedId in + uploadHistory.first(where: { $0.id == selectedId })?.url + } + if !selectedUrls.isEmpty { + Tools.shared.copyUrlsToClipboard(selectedUrls) + } } .alert("Clear History Record", isPresented: $showClearHistoryAlert) { Button("Cancel", role: .cancel) {} @@ -98,7 +116,7 @@ struct HistoryTableView: View { HStack(spacing: 2) { Text("\(uploadHistory.count) Items") - + Spacer() HStack(spacing: 2) { diff --git a/macOS/View/StatusMenu/HistoryMenuItem.swift b/macOS/View/StatusMenu/HistoryMenuItem.swift index 747604c..f5e2569 100644 --- a/macOS/View/StatusMenu/HistoryMenuItem.swift +++ b/macOS/View/StatusMenu/HistoryMenuItem.swift @@ -23,7 +23,7 @@ struct HistoryMenuItem: View { var body: some View { Menu { Button("Copy URL", systemImage: "clipboard") { - Tools.shared.copyUrls([history.url]) + Tools.shared.copyUrlsToClipboard([history.url]) } Button("Open in Browser", systemImage: "globe") { @@ -34,7 +34,7 @@ struct HistoryMenuItem: View { Divider() - Button("Preview", systemImage: "eye") { + Button("Quick Look", systemImage: "eye") { if let url = URL(string: history.url) { quickLookURL = url } @@ -65,7 +65,7 @@ struct HistoryMenuItem: View { .truncationMode(.middle) } } primaryAction: { - Tools.shared.copyUrls([history.url]) + Tools.shared.copyUrlsToClipboard([history.url]) } } diff --git a/uPic.xcodeproj/project.pbxproj b/uPic.xcodeproj/project.pbxproj index 3aec1b4..4b5146c 100644 --- a/uPic.xcodeproj/project.pbxproj +++ b/uPic.xcodeproj/project.pbxproj @@ -87,6 +87,7 @@ 9602EC752EBB214C004AE3E6 /* uPicAppIntentsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = uPicAppIntentsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 96149DDA2EB3DA9E005ADB6A /* CloudKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CloudKit.framework; path = System/Library/Frameworks/CloudKit.framework; sourceTree = SDKROOT; }; 961848742EB09470005BD3AC /* uPic.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = uPic.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Build.xcconfig; sourceTree = ""; }; 964569682EB8F3B40083BD40 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; }; 968762442EBAE826001BDC0B /* Cocoa.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Cocoa.framework; path = System/Library/Frameworks/Cocoa.framework; sourceTree = SDKROOT; }; 968762762EBAF1AA001BDC0B /* uPicActionExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = uPicActionExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -261,6 +262,7 @@ 9618486B2EB09470005BD3AC = { isa = PBXGroup; children = ( + 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */, 961848762EB09470005BD3AC /* macOS */, 96A829B72EB8D89C001FE480 /* uPicShareExtension */, 968762782EBAF1AA001BDC0B /* uPicActionExtension */, @@ -537,10 +539,11 @@ /* Begin XCBuildConfiguration section */ 9602EC802EBB214C004AE3E6 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = uPicAppIntentsExtension/uPicAppIntentsExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(inherited)"; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -554,7 +557,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.macos.uPicAppIntentsExtension; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -569,10 +572,11 @@ }; 9602EC812EBB214C004AE3E6 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = uPicAppIntentsExtension/uPicAppIntentsExtension.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(inherited)"; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -586,7 +590,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.macos.uPicAppIntentsExtension; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -601,6 +605,7 @@ }; 9618487F2EB09471005BD3AC /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -670,6 +675,7 @@ }; 961848802EB09471005BD3AC /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; @@ -731,6 +737,7 @@ }; 961848822EB09471005BD3AC /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = uPic; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -738,7 +745,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(inherited)"; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_APP_SANDBOX = YES; @@ -770,7 +777,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.macos; PRODUCT_NAME = uPic; REGISTER_APP_GROUPS = YES; @@ -785,6 +792,7 @@ }; 961848832EB09471005BD3AC /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = uPic; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; @@ -792,7 +800,7 @@ "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(inherited)"; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_APP_SANDBOX = YES; @@ -824,7 +832,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.macos; PRODUCT_NAME = uPic; REGISTER_APP_GROUPS = YES; @@ -839,11 +847,12 @@ }; 968762842EBAF1AA001BDC0B /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = uPicActionExtension/uPicActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(inherited)"; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -868,7 +877,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.macos.uPicActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -883,11 +892,12 @@ }; 968762852EBAF1AA001BDC0B /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = uPicActionExtension/uPicActionExtension.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(inherited)"; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = YES; @@ -912,7 +922,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.macos.uPicActionExtension; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -927,11 +937,12 @@ }; 96A829C42EB8D89C001FE480 /* Debug */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = uPicShareExtension/uPicShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(inherited)"; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_APP_SANDBOX = YES; @@ -957,7 +968,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.macos.uPicShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -972,11 +983,12 @@ }; 96A829C52EB8D89C001FE480 /* Release */ = { isa = XCBuildConfiguration; + baseConfigurationReference = 963FCACC2EC1AE35009ABA6D /* Build.xcconfig */; buildSettings = { CODE_SIGN_ENTITLEMENTS = uPicShareExtension/uPicShareExtension.entitlements; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1; + CURRENT_PROJECT_VERSION = "$(inherited)"; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = W863J6W8DZ; ENABLE_APP_SANDBOX = YES; @@ -1002,7 +1014,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 15; - MARKETING_VERSION = 2.0.0; + MARKETING_VERSION = "$(inherited)"; PRODUCT_BUNDLE_IDENTIFIER = com.svend.uPic.macos.uPicShareExtension; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES;