Release TagLauncher 7.5.1
36 files modified
1 files added
| | |
| | | |
| | | enum AppDefaults { |
| | | static let schemaVersionKey = "initialDefaultsSchemaVersion" |
| | | static let currentSchemaVersion = 1 |
| | | static let currentSchemaVersion = 2 |
| | | |
| | | static let tagFontSize: Double = 22 |
| | | static let iconSize: Double = 80 |
| | |
| | | "launchAtLogin": launchAtLogin, |
| | | "showUncommonAppBubbles": showUncommonAppBubbles |
| | | ]) |
| | | migrateShortcutDefaultsIfNeeded() |
| | | } |
| | | |
| | | static func hasStoredValue(for key: String) -> Bool { |
| | | let domain = Bundle.main.bundleIdentifier ?? "com.apptag.launcher" |
| | | return UserDefaults.standard.persistentDomain(forName: domain)?[key] != nil |
| | | } |
| | | |
| | | private static func migrateShortcutDefaultsIfNeeded() { |
| | | let defaults = UserDefaults.standard |
| | | guard defaults.integer(forKey: schemaVersionKey) < currentSchemaVersion else { return } |
| | | |
| | | defaults.set(LauncherHotkey.defaultMain.serialized, forKey: LauncherHotkeyKind.main.storageKey) |
| | | defaults.removeObject(forKey: LauncherHotkeyKind.main.pendingStorageKey) |
| | | defaults.removeObject(forKey: LauncherHotkeyKind.main.statusKey) |
| | | defaults.removeObject(forKey: LauncherHotkeyKind.main.conflictMessageKey) |
| | | |
| | | let quickSearchKey = LauncherHotkeyKind.quickSearch.storageKey |
| | | let storedQuickSearch = defaults.string(forKey: quickSearchKey) ?? "" |
| | | if storedQuickSearch.isEmpty { |
| | | defaults.set(LauncherHotkey.defaultQuickSearch.serialized, forKey: quickSearchKey) |
| | | defaults.removeObject(forKey: LauncherHotkeyKind.quickSearch.pendingStorageKey) |
| | | defaults.removeObject(forKey: LauncherHotkeyKind.quickSearch.statusKey) |
| | | defaults.removeObject(forKey: LauncherHotkeyKind.quickSearch.conflictMessageKey) |
| | | } |
| | | |
| | | defaults.set(currentSchemaVersion, forKey: schemaVersionKey) |
| | | } |
| | | } |
| | |
| | | private static let statusItemButtonIdentifier = NSUserInterfaceItemIdentifier("TagLauncherStatusItemButton") |
| | | private static let statusItemAccessibilityLabel = "TagLauncher" |
| | | private static let showAppListMenuItemIdentifier = NSUserInterfaceItemIdentifier("TagLauncherShowAppListMenuItem") |
| | | private static let showAppListShortcutGlyphs = "⌥⇧␣" |
| | | private static let overlayDefaultLevel = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow))) |
| | | private static let overlayTextInputLevel = NSWindow.Level.modalPanel |
| | | |
| | |
| | | private var overlayWindow: NSWindow? |
| | | private var overlayKeyMonitor: Any? |
| | | private var settingsWindow: NSWindow? // Track Settings window to keep it above overlay |
| | | private var hotkeyRef: EventHotKeyRef? |
| | | private var mainHotkeyRef: EventHotKeyRef? |
| | | private var quickSearchHotkeyRef: EventHotKeyRef? |
| | | private var hotkeyEventHandlerInstalled = false |
| | | private var isQuickSearchOpen = false |
| | | private var isModalInteractionActive = false |
| | | private var areHotkeysPausedForCapture = false |
| | | private var isInEditMode = false // Suppress auto-dismiss during editing |
| | | private var isEditingAppNote = false |
| | | private var isConfiguringApplicationMenu = false |
| | |
| | | migrateDefaultGroupName() |
| | | TagDatabase.seedDefaultTags() |
| | | syncChromeSettings(force: true) |
| | | registerHotkey() |
| | | registerConfiguredHotkeys() |
| | | observeOtherWindows() |
| | | observeSettingsClose() |
| | | observeEditMode() |
| | | observeAppNoteEditing() |
| | | observeQuickSearch() |
| | | observePreferencesRequests() |
| | | observeApplicationMenuChanges() |
| | | observeChromeSettings() |
| | |
| | | } |
| | | |
| | | private var showAppListMenuTitle: String { |
| | | "\(tr("menu.showAppList")) \(Self.showAppListShortcutGlyphs)" |
| | | if let hotkey = LauncherHotkeyStore.hotkey(for: .main) { |
| | | return "\(tr("menu.showAppList")) \(hotkey.displayString)" |
| | | } |
| | | return tr("menu.showAppList") |
| | | } |
| | | |
| | | private func observeApplicationMenuChanges() { |
| | |
| | | } |
| | | } |
| | | |
| | | private func showOverlay() { |
| | | private func showOverlay(initialQuickSearchSource: String? = nil) { |
| | | // Use the screen under the mouse cursor — works in fullscreen spaces |
| | | let mousePoint = NSEvent.mouseLocation |
| | | guard let screen = NSScreen.screens.first(where: { |
| | |
| | | if let existingWindow = overlayWindow { |
| | | window = existingWindow |
| | | } else { |
| | | window = makeOverlayWindow(on: screen) |
| | | window = makeOverlayWindow(on: screen, initialQuickSearchSource: initialQuickSearchSource) |
| | | overlayWindow = window |
| | | } |
| | | |
| | |
| | | window.level = isEditingAppNote ? Self.overlayTextInputLevel : Self.overlayDefaultLevel |
| | | |
| | | installOverlayKeyMonitor() |
| | | |
| | | if let initialQuickSearchSource { |
| | | NotificationCenter.default.post( |
| | | name: .tagLauncherQuickSearchRequested, |
| | | object: nil, |
| | | userInfo: ["source": initialQuickSearchSource] |
| | | ) |
| | | } |
| | | |
| | | window.makeKeyAndOrderFront(nil) |
| | | window.orderFrontRegardless() |
| | |
| | | private func installOverlayKeyMonitor() { |
| | | guard overlayKeyMonitor == nil else { return } |
| | | overlayKeyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in |
| | | guard let self else { return event } |
| | | if event.keyCode == 53 { // Escape |
| | | self?.hideOverlay() |
| | | if self.isQuickSearchOpen { |
| | | NotificationCenter.default.post(name: .tagLauncherQuickSearchDismissRequested, object: nil) |
| | | return nil |
| | | } |
| | | self.hideOverlay() |
| | | return nil |
| | | } |
| | | if self.shouldOpenQuickSearch(for: event) { |
| | | NotificationCenter.default.post( |
| | | name: .tagLauncherQuickSearchRequested, |
| | | object: nil, |
| | | userInfo: ["source": QuickSearchOpenSource.mainOverlay] |
| | | ) |
| | | return nil |
| | | } |
| | | return event |
| | | } |
| | | } |
| | | |
| | | private func shouldOpenQuickSearch(for event: NSEvent) -> Bool { |
| | | guard event.keyCode == UInt16(kVK_Space), |
| | | !event.isARepeat, |
| | | event.modifierFlags.intersection(.deviceIndependentFlagsMask).isEmpty, |
| | | overlayWindow?.isVisible == true, |
| | | !isQuickSearchOpen, |
| | | !isInEditMode, |
| | | !isEditingAppNote, |
| | | !isModalInteractionActive |
| | | else { return false } |
| | | |
| | | guard let firstResponder = overlayWindow?.firstResponder else { return true } |
| | | if firstResponder is NSText || firstResponder is NSTextField { |
| | | return false |
| | | } |
| | | if let responderView = firstResponder as? NSView, |
| | | viewOrAncestorHandlesSpace(responderView) { |
| | | return false |
| | | } |
| | | return true |
| | | } |
| | | |
| | | private func viewOrAncestorHandlesSpace(_ view: NSView) -> Bool { |
| | | var current: NSView? = view |
| | | while let candidate = current { |
| | | if candidate is NSButton || candidate is NSSegmentedControl || candidate is NSSlider { |
| | | return true |
| | | } |
| | | current = candidate.superview |
| | | } |
| | | return false |
| | | } |
| | | |
| | | private func removeOverlayKeyMonitor() { |
| | |
| | | } |
| | | } |
| | | |
| | | private func makeOverlayWindow(on screen: NSScreen) -> NSWindow { |
| | | private func makeOverlayWindow(on screen: NSScreen, initialQuickSearchSource: String? = nil) -> NSWindow { |
| | | let panel = OverlayPanel( |
| | | contentRect: screen.frame, |
| | | styleMask: [.borderless, .fullSizeContentView, .nonactivatingPanel], |
| | |
| | | panel.titleVisibility = .hidden |
| | | panel.isReleasedWhenClosed = false |
| | | panel.contentView = DismissibleHostingView( |
| | | rootView: ContentView(hideOverlay: { [weak self] in |
| | | self?.hideOverlay(force: true) |
| | | }), |
| | | rootView: ContentView( |
| | | hideOverlay: { [weak self] in |
| | | self?.hideOverlay(force: true) |
| | | }, |
| | | initialQuickSearchSource: initialQuickSearchSource |
| | | ), |
| | | onBackdropTap: { [weak self] in |
| | | self?.hideOverlay() |
| | | } |
| | |
| | | } |
| | | } |
| | | |
| | | // MARK: - Global Hotkey (Shift+Option+Space) |
| | | // MARK: - Global Hotkeys |
| | | |
| | | /// Carbon RegisterEventHotKey. If it fails (sandbox, etc.), falls back to menu bar only. |
| | | private func registerHotkey() { |
| | | private func registerConfiguredHotkeys() { |
| | | installHotkeyEventHandlerIfNeeded() |
| | | registerStoredHotkey(.main) |
| | | registerStoredHotkey(.quickSearch) |
| | | } |
| | | |
| | | @discardableResult |
| | | func applyHotkey(_ hotkey: LauncherHotkey?, for kind: LauncherHotkeyKind) -> Bool { |
| | | installHotkeyEventHandlerIfNeeded() |
| | | |
| | | if hotkey == nil { |
| | | unregisterHotkey(for: kind) |
| | | LauncherHotkeyStore.save(nil, for: kind) |
| | | LauncherHotkeyStore.setStatus(.disabled, message: nil, for: kind) |
| | | return true |
| | | } |
| | | |
| | | guard let hotkey else { return false } |
| | | let previousRef = hotkeyRef(for: kind) |
| | | if previousRef != nil, LauncherHotkeyStore.hotkey(for: kind) == hotkey { |
| | | LauncherHotkeyStore.setStatus(.active, message: nil, for: kind) |
| | | return true |
| | | } |
| | | setHotkeyRef(nil, for: kind) |
| | | |
| | | var hotkeyID = EventHotKeyID() |
| | | hotkeyID.signature = OSType(0x41505447) // 'APTG' |
| | | hotkeyID.id = 1 |
| | | hotkeyID.id = kind.eventID |
| | | |
| | | let modifiers = UInt32(shiftKey | optionKey) |
| | | |
| | | var ref: EventHotKeyRef? |
| | | var newRef: EventHotKeyRef? |
| | | let status = RegisterEventHotKey( |
| | | UInt32(kVK_Space), |
| | | modifiers, |
| | | hotkey.keyCode, |
| | | hotkey.modifiers, |
| | | hotkeyID, |
| | | GetApplicationEventTarget(), |
| | | 0, |
| | | &ref |
| | | &newRef |
| | | ) |
| | | hotkeyRef = ref |
| | | |
| | | if status != noErr { |
| | | print("[TagLauncher] Hotkey registration failed: \(status). Falling back to menu bar only.") |
| | | return |
| | | if status == noErr, let newRef { |
| | | if let previousRef { UnregisterEventHotKey(previousRef) } |
| | | setHotkeyRef(newRef, for: kind) |
| | | LauncherHotkeyStore.save(hotkey, for: kind) |
| | | LauncherHotkeyStore.setStatus(.active, message: nil, for: kind) |
| | | return true |
| | | } |
| | | |
| | | setHotkeyRef(previousRef, for: kind) |
| | | let message = LauncherHotkeyStore.knownConflictMessage(for: hotkey, status: status) |
| | | ?? tr("quickSearch.hotkeyConflict.generic") |
| | | LauncherHotkeyStore.savePending(hotkey, for: kind) |
| | | LauncherHotkeyStore.setStatus(.conflict, message: message, for: kind) |
| | | print("[TagLauncher] Hotkey registration failed for \(kind.rawValue): \(status)") |
| | | return false |
| | | } |
| | | |
| | | func retryHotkeyRegistration(for kind: LauncherHotkeyKind) { |
| | | _ = applyHotkey( |
| | | LauncherHotkeyStore.pendingHotkey(for: kind) ?? LauncherHotkeyStore.hotkey(for: kind), |
| | | for: kind |
| | | ) |
| | | } |
| | | |
| | | private func registerStoredHotkey(_ kind: LauncherHotkeyKind) { |
| | | _ = applyHotkey(LauncherHotkeyStore.hotkey(for: kind), for: kind) |
| | | } |
| | | |
| | | private func installHotkeyEventHandlerIfNeeded() { |
| | | guard !hotkeyEventHandlerInstalled else { return } |
| | | hotkeyEventHandlerInstalled = true |
| | | |
| | | var eventSpec = EventTypeSpec( |
| | | eventClass: OSType(kEventClassKeyboard), |
| | | eventKind: UInt32(kEventHotKeyPressed) |
| | | ) |
| | | |
| | | let selfPtr = Unmanaged.passUnretained(self).toOpaque() |
| | | |
| | | InstallEventHandler( |
| | | GetApplicationEventTarget(), |
| | | { (_, _, userData) -> OSStatus in |
| | | guard let userData else { return noErr } |
| | | { (_, event, userData) -> OSStatus in |
| | | guard let event, let userData else { return noErr } |
| | | var hotkeyID = EventHotKeyID() |
| | | let status = GetEventParameter( |
| | | event, |
| | | EventParamName(kEventParamDirectObject), |
| | | EventParamType(typeEventHotKeyID), |
| | | nil, |
| | | MemoryLayout<EventHotKeyID>.size, |
| | | nil, |
| | | &hotkeyID |
| | | ) |
| | | guard status == noErr else { return noErr } |
| | | |
| | | let delegate = Unmanaged<AppDelegate> |
| | | .fromOpaque(userData) |
| | | .takeUnretainedValue() |
| | | DispatchQueue.main.async { |
| | | delegate.toggleOverlay() |
| | | delegate.handleHotkeyEvent(id: hotkeyID.id) |
| | | } |
| | | return noErr |
| | | }, |
| | |
| | | selfPtr, |
| | | nil |
| | | ) |
| | | } |
| | | |
| | | private func handleHotkeyEvent(id: UInt32) { |
| | | if id == LauncherHotkeyKind.quickSearch.eventID { |
| | | showQuickSearchFromGlobalHotkey() |
| | | } else { |
| | | toggleOverlay() |
| | | } |
| | | } |
| | | |
| | | private func showQuickSearchFromGlobalHotkey() { |
| | | guard overlayWindow?.isVisible != true else { return } |
| | | showOverlay(initialQuickSearchSource: QuickSearchOpenSource.globalHidden) |
| | | } |
| | | |
| | | private func hotkeyRef(for kind: LauncherHotkeyKind) -> EventHotKeyRef? { |
| | | switch kind { |
| | | case .main: return mainHotkeyRef |
| | | case .quickSearch: return quickSearchHotkeyRef |
| | | } |
| | | } |
| | | |
| | | private func setHotkeyRef(_ ref: EventHotKeyRef?, for kind: LauncherHotkeyKind) { |
| | | switch kind { |
| | | case .main: mainHotkeyRef = ref |
| | | case .quickSearch: quickSearchHotkeyRef = ref |
| | | } |
| | | } |
| | | |
| | | private func unregisterHotkey(for kind: LauncherHotkeyKind) { |
| | | if let ref = hotkeyRef(for: kind) { |
| | | UnregisterEventHotKey(ref) |
| | | setHotkeyRef(nil, for: kind) |
| | | } |
| | | } |
| | | |
| | | // MARK: - Preferences |
| | |
| | | } |
| | | } |
| | | |
| | | private func observeQuickSearch() { |
| | | NotificationCenter.default.addObserver( |
| | | forName: .tagLauncherQuickSearchVisibilityChanged, |
| | | object: nil, |
| | | queue: .main |
| | | ) { [weak self] notification in |
| | | self?.isQuickSearchOpen = (notification.userInfo?["active"] as? Bool) ?? false |
| | | } |
| | | |
| | | NotificationCenter.default.addObserver( |
| | | forName: .tagLauncherModalInteractionChanged, |
| | | object: nil, |
| | | queue: .main |
| | | ) { [weak self] notification in |
| | | guard let self else { return } |
| | | let active = (notification.userInfo?["active"] as? Bool) ?? false |
| | | self.isModalInteractionActive = active |
| | | if (notification.userInfo?["source"] as? String) == "hotkeyRecording" { |
| | | self.setHotkeysPausedForCapture(active) |
| | | } |
| | | } |
| | | |
| | | NotificationCenter.default.addObserver( |
| | | forName: .tagLauncherHotkeysChanged, |
| | | object: nil, |
| | | queue: .main |
| | | ) { [weak self] _ in |
| | | self?.setupMenuBar() |
| | | self?.configureApplicationMenuWhenAvailable(retries: 2) |
| | | } |
| | | } |
| | | |
| | | private func updateOverlayLevelForTextInput() { |
| | | guard let overlayWindow else { return } |
| | | overlayWindow.level = isEditingAppNote ? Self.overlayTextInputLevel : Self.overlayDefaultLevel |
| | |
| | | } |
| | | } |
| | | |
| | | private func setHotkeysPausedForCapture(_ paused: Bool) { |
| | | guard areHotkeysPausedForCapture != paused else { return } |
| | | areHotkeysPausedForCapture = paused |
| | | if paused { |
| | | unregisterHotkey(for: .main) |
| | | unregisterHotkey(for: .quickSearch) |
| | | } else { |
| | | registerConfiguredHotkeys() |
| | | } |
| | | } |
| | | |
| | | /// The overlay buttons live inside SwiftUI; use an app-level notification like edit mode does. |
| | | private func observePreferencesRequests() { |
| | | NotificationCenter.default.addObserver( |
| | |
| | | |
| | | struct ContentView: View { |
| | | let hideOverlay: () -> Void |
| | | private let initialQuickSearchSource: String? |
| | | |
| | | @State private var allApps: [AppInfo] = [] |
| | | @State private var tagColors: [String: Int] = [:] |
| | |
| | | @State private var bubbleDraftNote = "" |
| | | @FocusState private var bubbleNoteFocused: Bool |
| | | |
| | | // Quick Search |
| | | @State private var quickSearchVisible = false |
| | | @State private var quickSearchQuery = "" |
| | | @State private var quickSearchDocuments: [QuickSearchDocument] = [] |
| | | @State private var quickSearchResults: [QuickSearchResult] = [] |
| | | @State private var quickSearchSelectedID: URL? = nil |
| | | @State private var quickSearchManualSelection = false |
| | | @State private var quickSearchFocusToken = 0 |
| | | @State private var quickSearchCloseHidesOverlay = false |
| | | @State private var quickSearchErrorMessage: String? = nil |
| | | @State private var initialQuickSearchConsumed = false |
| | | |
| | | init(hideOverlay: @escaping () -> Void, initialQuickSearchSource: String? = nil) { |
| | | self.hideOverlay = hideOverlay |
| | | self.initialQuickSearchSource = initialQuickSearchSource |
| | | let startsInQuickSearch = initialQuickSearchSource != nil |
| | | _quickSearchVisible = State(initialValue: startsInQuickSearch) |
| | | _quickSearchCloseHidesOverlay = State(initialValue: initialQuickSearchSource == QuickSearchOpenSource.globalHidden) |
| | | _quickSearchFocusToken = State(initialValue: startsInQuickSearch ? 1 : 0) |
| | | } |
| | | |
| | | // Configurable defaults |
| | | @AppStorage("defaultGroupName") private var defaultGroupName = "Other" |
| | | @AppStorage("tagFontSize") private var tagFontSize: Double = AppDefaults.tagFontSize |
| | |
| | | displayMode == "coloredContainer" || displayMode == "coloredGridContainer" |
| | | } |
| | | |
| | | private var quickSearchOnlyMode: Bool { |
| | | quickSearchVisible && quickSearchCloseHidesOverlay |
| | | } |
| | | |
| | | var body: some View { |
| | | ZStack { |
| | | VisualEffectView(material: .hudWindow, blendingMode: .behindWindow) |
| | | .ignoresSafeArea() |
| | | .allowsHitTesting(false) |
| | | if !quickSearchOnlyMode { |
| | | VisualEffectView(material: .hudWindow, blendingMode: .behindWindow) |
| | | .ignoresSafeArea() |
| | | .allowsHitTesting(false) |
| | | } |
| | | |
| | | if notchHeight > 0 { |
| | | VStack { |
| | | Rectangle().fill(.black) |
| | | .frame(height: notchHeight) |
| | | .ignoresSafeArea(edges: .top) |
| | | Spacer() |
| | | if !quickSearchOnlyMode { |
| | | if notchHeight > 0 { |
| | | VStack { |
| | | Rectangle().fill(.black) |
| | | .frame(height: notchHeight) |
| | | .ignoresSafeArea(edges: .top) |
| | | Spacer() |
| | | } |
| | | .allowsHitTesting(false) |
| | | } |
| | | .allowsHitTesting(false) |
| | | |
| | | switch editPhase { |
| | | case .none: |
| | | normalContent |
| | | case .editingTags: |
| | | editTagsView |
| | | case .editingApps: |
| | | editAppsView |
| | | } |
| | | |
| | | uncommonAppBubbleOverlay |
| | | smartStartNoticeOverlay |
| | | editActionFeedbackOverlay |
| | | uncategorizedDropConfirmOverlay |
| | | } |
| | | |
| | | switch editPhase { |
| | | case .none: |
| | | normalContent |
| | | case .editingTags: |
| | | editTagsView |
| | | case .editingApps: |
| | | editAppsView |
| | | } |
| | | quickSearchOverlay |
| | | |
| | | uncommonAppBubbleOverlay |
| | | smartStartNoticeOverlay |
| | | editActionFeedbackOverlay |
| | | uncategorizedDropConfirmOverlay |
| | | |
| | | if let message = dropWarningToast { |
| | | if !quickSearchOnlyMode, let message = dropWarningToast { |
| | | Text(message) |
| | | .font(.system(size: 16, weight: .semibold)) |
| | | .foregroundStyle(.primary) |
| | |
| | | .allowsHitTesting(false) |
| | | } |
| | | |
| | | if dropRefreshVisible { |
| | | if !quickSearchOnlyMode && dropRefreshVisible { |
| | | Color.black.opacity(0.08) |
| | | .ignoresSafeArea() |
| | | .transition(.opacity) |
| | |
| | | .onAppear { |
| | | refreshNotchHeight() |
| | | refreshApps() |
| | | if let initialQuickSearchSource, !initialQuickSearchConsumed { |
| | | initialQuickSearchConsumed = true |
| | | quickSearchCloseHidesOverlay = initialQuickSearchSource == QuickSearchOpenSource.globalHidden |
| | | quickSearchVisible = true |
| | | quickSearchFocusToken &+= 1 |
| | | NotificationCenter.default.post( |
| | | name: .tagLauncherQuickSearchVisibilityChanged, |
| | | object: nil, |
| | | userInfo: ["active": true] |
| | | ) |
| | | } |
| | | } |
| | | .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidShow)) { _ in |
| | | resetTransientDragState() |
| | |
| | | } |
| | | .onReceive(NotificationCenter.default.publisher(for: .tagLauncherOverlayDidHide)) { _ in |
| | | resetTransientDragState() |
| | | closeQuickSearch(notify: true, hideOverlayIfNeeded: false) |
| | | } |
| | | .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchRequested)) { notification in |
| | | let source = notification.userInfo?["source"] as? String ?? QuickSearchOpenSource.mainOverlay |
| | | openQuickSearch(source: source) |
| | | } |
| | | .onReceive(NotificationCenter.default.publisher(for: .tagLauncherQuickSearchDismissRequested)) { _ in |
| | | closeQuickSearch() |
| | | } |
| | | .onReceive(NotificationCenter.default.publisher(for: NSApplication.didBecomeActiveNotification)) { _ in |
| | | guard allApps.isEmpty, !refreshInProgress else { return } |
| | |
| | | if editingBubble != nil && !focused { |
| | | commitBubbleNote() |
| | | } |
| | | } |
| | | .onChange(of: quickSearchQuery) { _, _ in |
| | | quickSearchErrorMessage = nil |
| | | refreshQuickSearchResults() |
| | | } |
| | | .onChange(of: pendingUncategorizedDrop != nil) { _, active in |
| | | NotificationCenter.default.post( |
| | |
| | | NotificationCenter.default.post(name: .tagLauncherEditModeChanged, object: nil, userInfo: ["active": false]) |
| | | } |
| | | } |
| | | } |
| | | |
| | | // MARK: - Quick Search |
| | | |
| | | private var quickSearchOverlay: some View { |
| | | GeometryReader { proxy in |
| | | if quickSearchVisible { |
| | | QuickSearchBackdropClickView { |
| | | closeQuickSearch() |
| | | } |
| | | .frame(width: proxy.size.width, height: proxy.size.height) |
| | | .ignoresSafeArea() |
| | | .zIndex(899) |
| | | |
| | | QuickSearchOverlayView( |
| | | query: $quickSearchQuery, |
| | | results: quickSearchResults, |
| | | selectedID: quickSearchSelectedID, |
| | | focusToken: quickSearchFocusToken, |
| | | isLoading: quickSearchDocuments.isEmpty && refreshInProgress, |
| | | maxVisibleRows: quickSearchMaxVisibleRows(in: proxy.size), |
| | | errorMessage: quickSearchErrorMessage, |
| | | onCommand: handleQuickSearchCommand, |
| | | onHover: selectQuickSearchResult, |
| | | onLaunch: launchQuickSearchResult |
| | | ) |
| | | .position( |
| | | x: proxy.size.width / 2, |
| | | y: quickSearchPanelCenterY(in: proxy.size) |
| | | ) |
| | | .transition(.scale(scale: 0.98).combined(with: .opacity)) |
| | | .zIndex(900) |
| | | } |
| | | } |
| | | .ignoresSafeArea() |
| | | .animation(.easeOut(duration: 0.12), value: quickSearchVisible) |
| | | } |
| | | |
| | | private func quickSearchPanelCenterY(in size: CGSize) -> CGFloat { |
| | | let visibleRows = max(1, min(quickSearchResults.isEmpty ? 1 : quickSearchResults.count, quickSearchMaxVisibleRows(in: size))) |
| | | let estimatedPanelHeight = CGFloat(visibleRows) * 76 + 122 |
| | | return quickSearchPanelTopY(in: size) + estimatedPanelHeight / 2 |
| | | } |
| | | |
| | | private func quickSearchPanelTopY(in size: CGSize) -> CGFloat { |
| | | max(notchHeight + 54, min(112, size.height * 0.12)) |
| | | } |
| | | |
| | | private func quickSearchMaxVisibleRows(in size: CGSize) -> Int { |
| | | let bottomClearance: CGFloat = 84 |
| | | let chromeHeight: CGFloat = 122 |
| | | let rowHeightWithSpacing: CGFloat = 76 |
| | | let availableHeight = max(0, size.height - quickSearchPanelTopY(in: size) - bottomClearance - chromeHeight) |
| | | return max(1, min(8, Int(floor(availableHeight / rowHeightWithSpacing)))) |
| | | } |
| | | |
| | | private func openQuickSearch(source: String) { |
| | | guard canOpenQuickSearch else { return } |
| | | dismissAppBubble() |
| | | quickSearchCloseHidesOverlay = source == QuickSearchOpenSource.globalHidden |
| | | quickSearchVisible = true |
| | | quickSearchQuery = "" |
| | | quickSearchManualSelection = false |
| | | quickSearchErrorMessage = nil |
| | | quickSearchFocusToken &+= 1 |
| | | refreshQuickSearchResults() |
| | | NotificationCenter.default.post( |
| | | name: .tagLauncherQuickSearchVisibilityChanged, |
| | | object: nil, |
| | | userInfo: ["active": true] |
| | | ) |
| | | } |
| | | |
| | | private var canOpenQuickSearch: Bool { |
| | | editPhase == .none |
| | | && pendingUncategorizedDrop == nil |
| | | && smartStartNotice == nil |
| | | && !dropRefreshVisible |
| | | && !quickSearchVisible |
| | | } |
| | | |
| | | private func closeQuickSearch(notify: Bool = true, hideOverlayIfNeeded: Bool = true) { |
| | | let shouldHideOverlay = quickSearchVisible && quickSearchCloseHidesOverlay && hideOverlayIfNeeded |
| | | guard quickSearchVisible else { return } |
| | | quickSearchVisible = false |
| | | quickSearchQuery = "" |
| | | quickSearchResults = [] |
| | | quickSearchSelectedID = nil |
| | | quickSearchManualSelection = false |
| | | quickSearchErrorMessage = nil |
| | | quickSearchCloseHidesOverlay = false |
| | | if notify { |
| | | NotificationCenter.default.post( |
| | | name: .tagLauncherQuickSearchVisibilityChanged, |
| | | object: nil, |
| | | userInfo: ["active": false] |
| | | ) |
| | | } |
| | | if shouldHideOverlay { |
| | | hideOverlay() |
| | | } |
| | | } |
| | | |
| | | private func refreshQuickSearchResults() { |
| | | let previousSelection = quickSearchSelectedID |
| | | quickSearchResults = QuickSearchEngine.search(quickSearchQuery, documents: quickSearchDocuments) |
| | | |
| | | if quickSearchResults.isEmpty { |
| | | quickSearchSelectedID = nil |
| | | quickSearchManualSelection = false |
| | | return |
| | | } |
| | | |
| | | if quickSearchManualSelection, |
| | | let previousSelection, |
| | | quickSearchResults.contains(where: { $0.id == previousSelection }) { |
| | | quickSearchSelectedID = previousSelection |
| | | } else { |
| | | quickSearchSelectedID = quickSearchResults.first?.id |
| | | quickSearchManualSelection = false |
| | | } |
| | | } |
| | | |
| | | private func handleQuickSearchCommand(_ command: QuickSearchCommand) { |
| | | switch command { |
| | | case .moveUp: |
| | | moveQuickSearchSelection(by: -1) |
| | | case .moveDown: |
| | | moveQuickSearchSelection(by: 1) |
| | | case .submit: |
| | | guard let selected = selectedQuickSearchResult else { return } |
| | | launchQuickSearchResult(selected) |
| | | case .dismiss: |
| | | closeQuickSearch() |
| | | } |
| | | } |
| | | |
| | | private var selectedQuickSearchResult: QuickSearchResult? { |
| | | guard let quickSearchSelectedID else { return nil } |
| | | return quickSearchResults.first { $0.id == quickSearchSelectedID } |
| | | } |
| | | |
| | | private func moveQuickSearchSelection(by delta: Int) { |
| | | guard !quickSearchResults.isEmpty else { return } |
| | | let currentIndex = quickSearchSelectedID.flatMap { id in |
| | | quickSearchResults.firstIndex { $0.id == id } |
| | | } ?? 0 |
| | | let nextIndex = min(max(currentIndex + delta, 0), quickSearchResults.count - 1) |
| | | quickSearchSelectedID = quickSearchResults[nextIndex].id |
| | | quickSearchManualSelection = true |
| | | } |
| | | |
| | | private func selectQuickSearchResult(_ result: QuickSearchResult) { |
| | | quickSearchSelectedID = result.id |
| | | quickSearchManualSelection = true |
| | | } |
| | | |
| | | private func launchQuickSearchResult(_ result: QuickSearchResult) { |
| | | quickSearchErrorMessage = nil |
| | | launchApp( |
| | | result.app, |
| | | closeQuickSearchOnSuccess: true, |
| | | closeOverlayOnSuccess: true, |
| | | onFailure: { |
| | | quickSearchErrorMessage = tr("quickSearch.launchFailed") |
| | | quickSearchFocusToken &+= 1 |
| | | NSAccessibility.post( |
| | | element: NSApp.mainWindow as Any, |
| | | notification: .announcementRequested, |
| | | userInfo: [.announcement: tr("quickSearch.launchFailed")] |
| | | ) |
| | | } |
| | | ) |
| | | } |
| | | |
| | | // MARK: - Normal Content |
| | |
| | | ) |
| | | let store = smartStartResult.store |
| | | let apps = TagEditor.annotate(apps: scannedApps, store: store) |
| | | let quickSearchDocs = QuickSearchEngine.makeDocuments(apps: apps, store: store) |
| | | let colors = store.tags.mapValues { $0.color } |
| | | let order = TagEditor.orderedTagNames() |
| | | DispatchQueue.main.async { |
| | | allApps = apps |
| | | quickSearchDocuments = quickSearchDocs |
| | | tagColors = colors |
| | | draggedTagNames = order |
| | | if quickSearchVisible { |
| | | refreshQuickSearchResults() |
| | | } |
| | | handleSmartStartRunResult(smartStartResult) |
| | | if forceLayoutRefresh { |
| | | finishDropRefreshAfterMinimumDuration() |
| | |
| | | } |
| | | } |
| | | |
| | | func openApp(_ app: AppInfo) { |
| | | private func launchApp( |
| | | _ app: AppInfo, |
| | | closeQuickSearchOnSuccess: Bool = false, |
| | | closeOverlayOnSuccess: Bool = true, |
| | | onFailure: (() -> Void)? = nil |
| | | ) { |
| | | appDragModeActive = false |
| | | endTagNavReorder() |
| | | hideOverlay() |
| | | DispatchQueue.global(qos: .utility).async { |
| | | TagEditor.recordLauncherOpen(for: app.path.path) |
| | | } |
| | | let configuration = NSWorkspace.OpenConfiguration() |
| | | NSWorkspace.shared.openApplication(at: app.path, configuration: configuration) |
| | | NSWorkspace.shared.openApplication(at: app.path, configuration: configuration) { _, error in |
| | | DispatchQueue.main.async { |
| | | guard error == nil else { |
| | | onFailure?() |
| | | return |
| | | } |
| | | |
| | | DispatchQueue.global(qos: .utility).async { |
| | | TagEditor.recordLauncherOpen(for: app.path.path) |
| | | } |
| | | if closeQuickSearchOnSuccess { |
| | | closeQuickSearch(hideOverlayIfNeeded: false) |
| | | } |
| | | if closeOverlayOnSuccess { |
| | | hideOverlay() |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | func openApp(_ app: AppInfo) { |
| | | launchApp(app) |
| | | } |
| | | } |
| | | |
| | |
| | | var uncommonAppPaths: [String] = [] // special marker; does not affect normal groups |
| | | var uncommonSources: [String: UncommonSource] = [:] // current uncommon source: auto/manual |
| | | var appOpenCounts: [String: Int] = [:] // launches opened from TagLauncher |
| | | var appLastOpenedAt: [String: Date] = [:] // successful launches opened from TagLauncher |
| | | var knownAppPaths: [String] = [] // baseline set to detect newly installed apps |
| | | var appNotes: [String: String] = [:] // path → user note; retained even if marker is removed |
| | | var disabledSystemCategoryIDs: [SmartCategoryID] = [] // system categories the user deleted |
| | |
| | | case uncommonAppPaths |
| | | case uncommonSources |
| | | case appOpenCounts |
| | | case appLastOpenedAt |
| | | case knownAppPaths |
| | | case appNotes |
| | | case disabledSystemCategoryIDs |
| | |
| | | uncommonAppPaths = try container.decodeIfPresent([String].self, forKey: .uncommonAppPaths) ?? [] |
| | | uncommonSources = try container.decodeIfPresent([String: UncommonSource].self, forKey: .uncommonSources) ?? [:] |
| | | appOpenCounts = try container.decodeIfPresent([String: Int].self, forKey: .appOpenCounts) ?? [:] |
| | | appLastOpenedAt = try container.decodeIfPresent([String: Date].self, forKey: .appLastOpenedAt) ?? [:] |
| | | knownAppPaths = try container.decodeIfPresent([String].self, forKey: .knownAppPaths) ?? [] |
| | | appNotes = try container.decodeIfPresent([String: String].self, forKey: .appNotes) ?? [:] |
| | | disabledSystemCategoryIDs = try container.decodeIfPresent( |
| | |
| | | } |
| | | |
| | | let newPaths = scannedPaths.subtracting(knownPaths) |
| | | guard !newPaths.isEmpty else { return store } |
| | | let removedPaths = knownPaths.subtracting(scannedPaths) |
| | | if !removedPaths.isEmpty { |
| | | for path in removedPaths { |
| | | store.appOpenCounts.removeValue(forKey: path) |
| | | store.appLastOpenedAt.removeValue(forKey: path) |
| | | } |
| | | store.knownAppPaths = knownPaths.subtracting(removedPaths).sorted() |
| | | } |
| | | |
| | | guard !newPaths.isEmpty else { |
| | | if !removedPaths.isEmpty { |
| | | TagDatabase.save(store) |
| | | } |
| | | return store |
| | | } |
| | | |
| | | var uncommonPaths = Set(store.uncommonAppPaths) |
| | | let appsByPath = Dictionary(uniqueKeysWithValues: apps.map { ($0.path.path, $0) }) |
| | |
| | | let newApps = apps.filter { newPaths.contains($0.path.path) } |
| | | _ = seedDefaultAppleAppNotes(for: newApps, in: &store) |
| | | store.uncommonAppPaths = uncommonPaths.sorted() |
| | | store.knownAppPaths = knownPaths.union(newPaths).sorted() |
| | | store.knownAppPaths = Set(store.knownAppPaths).union(newPaths).sorted() |
| | | TagDatabase.save(store) |
| | | return store |
| | | } |
| | |
| | | static func recordLauncherOpen(for path: String) { |
| | | var store = TagDatabase.load() |
| | | store.appOpenCounts[path] = (store.appOpenCounts[path] ?? 0) + 1 |
| | | store.appLastOpenedAt[path] = Date() |
| | | |
| | | if store.uncommonSources[path] == .auto, |
| | | store.appOpenCounts[path, default: 0] >= TagDatabase.autoUncommonOpenThreshold { |
| | |
| | | <key>CFBundlePackageType</key> |
| | | <string>APPL</string> |
| | | <key>CFBundleShortVersionString</key> |
| | | <string>7.3.5</string> |
| | | <string>7.5.1</string> |
| | | <key>CFBundleVersion</key> |
| | | <string>744</string> |
| | | <string>751</string> |
| | | <key>LSMinimumSystemVersion</key> |
| | | <string>15.0</string> |
| | | <key>NSHighResolutionCapable</key> |
| | |
| | | "scheme.systemSmartStart": "مخطط البداية الذكي للنظام", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "مخطط البداية الذكي للنظام", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Chytré počáteční schéma systému", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Systemets smarte startskema", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Intelligentes System-Startschema", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "System smart initial scheme", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Esquema inicial inteligente del sistema", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Schéma initial intelligent du système", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Skema awal pintar sistem", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Schema iniziale intelligente di sistema", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "システム初期スマート分類", |
| | | "settings.bubbleDisplayScope": "吹き出しの表示対象:", |
| | | "settings.bubbleAllApps": "すべてのアプリ", |
| | | "settings.bubbleUncommonOnly": "あまり使わないアプリのみ" |
| | | "settings.bubbleUncommonOnly": "あまり使わないアプリのみ", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "시스템 스마트 초기 분류", |
| | | "settings.bubbleDisplayScope": "말풍선 표시 대상:", |
| | | "settings.bubbleAllApps": "모든 앱", |
| | | "settings.bubbleUncommonOnly": "자주 쓰지 않는 앱만" |
| | | "settings.bubbleUncommonOnly": "자주 쓰지 않는 앱만", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Skema awal pintar sistem", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Systemets smarte startskjema", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Slim beginschema van systeem", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Systemets smarte startskjema", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Systemets smarte startskjema", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Inteligentny schemat początkowy systemu", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Esquema inicial inteligente do sistema", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Schema inteligentă inițială a sistemului", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Интеллектуальная начальная схема системы", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Паметна почетна шема система", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Systemets smarta startschema", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "ชุดเริ่มต้นอัจฉริยะของระบบ", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Sistem akıllı başlangıç şeması", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Розумна початкова схема системи", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "Sơ đồ khởi tạo thông minh của hệ thống", |
| | | "settings.bubbleDisplayScope": "Bubble tips show for:", |
| | | "settings.bubbleAllApps": "All apps", |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only" |
| | | "settings.bubbleUncommonOnly": "Uncommon apps only", |
| | | "quickSearch.title": "Quick Search", |
| | | "quickSearch.placeholder": "Search apps, tags, and notes", |
| | | "quickSearch.inputAccessibility": "Quick Search input", |
| | | "quickSearch.emptyPrompt": "Start typing to search apps, tags, and notes.", |
| | | "quickSearch.noResults": "No apps found.", |
| | | "quickSearch.launchFailed": "Unable to open this app. Please try again or choose another app.", |
| | | "quickSearch.tagPrefix": "Tag", |
| | | "quickSearch.hotkeys": "Keyboard shortcuts", |
| | | "quickSearch.mainHotkey": "App list", |
| | | "quickSearch.mainHotkeyDesc": "Opens the TagLauncher app list.", |
| | | "quickSearch.directHotkey": "Quick Search (global)", |
| | | "quickSearch.directHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.record": "Change", |
| | | "quickSearch.recording": "Recording", |
| | | "quickSearch.clear": "Clear", |
| | | "quickSearch.restoreDefault": "Restore default", |
| | | "quickSearch.retry": "Retry registration", |
| | | "quickSearch.recordingHint": "Press the new global shortcut. Press Esc to cancel.", |
| | | "quickSearch.disabled": "Disabled", |
| | | "quickSearch.blocked": "Blocked", |
| | | "quickSearch.previousStillActive": "Previous shortcut remains active:", |
| | | "quickSearch.status.Active": "Active", |
| | | "quickSearch.status.Conflict": "Conflict", |
| | | "quickSearch.status.Disabled": "Disabled", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space is used by Spotlight. Change Spotlight in macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space is used by the Finder search window. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space is used to switch to the previous input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space is used to switch to the next input source. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space is used for Emoji & Symbols. Change macOS Keyboard Shortcuts, or choose another shortcut.", |
| | | "quickSearch.hotkeyConflict.generic": "This shortcut is already used by macOS or another app. Choose another shortcut, or change the shortcut in the other app and retry.", |
| | | "quickSearch.loading": "Preparing search index…", |
| | | "quickSearch.openKeyboardSettings": "Open macOS Keyboard Shortcuts settings", |
| | | "quickSearch.internalHotkey": "Quick Search (inside TagLauncher)", |
| | | "quickSearch.internalHotkeyDesc": "Works only while the TagLauncher app list is open.", |
| | | "quickSearch.internalHotkeyStatus": "App list only", |
| | | "quickSearch.globalHotkey": "Quick Search (global)", |
| | | "quickSearch.globalHotkeyDesc": "Open Quick Search globally when the TagLauncher app list is not visible.", |
| | | "quickSearch.spaceDisplay": "Space", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space may already be used by macOS input source or Globe/Fn shortcuts. Open macOS Keyboard Shortcuts to change that system shortcut, or choose another shortcut here." |
| | | } |
| | |
| | | "scheme.systemSmartStart": "系统智能化初始分类", |
| | | "settings.bubbleDisplayScope": "气泡提示框显示位置:", |
| | | "settings.bubbleAllApps": "全部应用", |
| | | "settings.bubbleUncommonOnly": "仅不常用应用" |
| | | "settings.bubbleUncommonOnly": "仅不常用应用", |
| | | "quickSearch.title": "快捷搜索", |
| | | "quickSearch.placeholder": "搜索 App、标签和备注", |
| | | "quickSearch.inputAccessibility": "快捷搜索输入框", |
| | | "quickSearch.emptyPrompt": "开始输入以搜索 App、标签和备注。", |
| | | "quickSearch.noResults": "未找到 App。", |
| | | "quickSearch.launchFailed": "无法打开这个 App。请重试或选择其他 App。", |
| | | "quickSearch.tagPrefix": "标签", |
| | | "quickSearch.hotkeys": "快捷键", |
| | | "quickSearch.mainHotkey": "主界面", |
| | | "quickSearch.mainHotkeyDesc": "打开 TagLauncher 应用列表。", |
| | | "quickSearch.directHotkey": "快捷搜索(全局)", |
| | | "quickSearch.directHotkeyDesc": "不在 TagLauncher 主界面时,直接打开快捷搜索。全局有效。", |
| | | "quickSearch.record": "修改", |
| | | "quickSearch.recording": "录制中", |
| | | "quickSearch.clear": "清除", |
| | | "quickSearch.restoreDefault": "恢复默认", |
| | | "quickSearch.retry": "重试注册", |
| | | "quickSearch.recordingHint": "按下新的全局快捷键。按 Esc 取消。", |
| | | "quickSearch.disabled": "已关闭", |
| | | "quickSearch.blocked": "被阻塞", |
| | | "quickSearch.previousStillActive": "之前可用的快捷键仍保持可用:", |
| | | "quickSearch.status.Active": "已启用", |
| | | "quickSearch.status.Conflict": "冲突", |
| | | "quickSearch.status.Disabled": "已关闭", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space 已被 Spotlight 使用。请在 macOS 键盘快捷键中修改 Spotlight,或选择其他快捷键。", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space 已被 Finder 搜索窗口使用。请在 macOS 键盘快捷键中修改,或选择其他快捷键。", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space 已被“上一个输入法”使用。请在 macOS 键盘快捷键中修改,或选择其他快捷键。", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space 已被“下一个输入法”使用。请在 macOS 键盘快捷键中修改,或选择其他快捷键。", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space 已被“表情与符号”使用。请在 macOS 键盘快捷键中修改,或选择其他快捷键。", |
| | | "quickSearch.hotkeyConflict.generic": "这个快捷键已被 macOS 或其他 App 使用。请选择其他快捷键,或先修改其他 App 中的快捷键后再重试。", |
| | | "quickSearch.loading": "正在准备搜索索引…", |
| | | "quickSearch.openKeyboardSettings": "打开 macOS 键盘快捷键设置", |
| | | "quickSearch.internalHotkey": "快捷搜索(TagLauncher 内部)", |
| | | "quickSearch.internalHotkeyDesc": "仅在 TagLauncher 主界面有效。", |
| | | "quickSearch.internalHotkeyStatus": "仅主界面有效", |
| | | "quickSearch.globalHotkey": "快捷搜索(全局)", |
| | | "quickSearch.globalHotkeyDesc": "不在 TagLauncher 主界面时,直接打开快捷搜索。全局有效。", |
| | | "quickSearch.spaceDisplay": "Space(空格键)", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space 可能已被 macOS 的输入法或地球键快捷键占用。请打开 macOS 键盘快捷键设置关闭或改掉对应系统快捷键,或在这里选择其他组合。" |
| | | } |
| | |
| | | "scheme.systemSmartStart": "系統智慧化初始分類", |
| | | "settings.bubbleDisplayScope": "氣泡提示框顯示位置:", |
| | | "settings.bubbleAllApps": "全部 App", |
| | | "settings.bubbleUncommonOnly": "僅不常用 App" |
| | | "settings.bubbleUncommonOnly": "僅不常用 App", |
| | | "quickSearch.title": "快捷搜尋", |
| | | "quickSearch.placeholder": "搜尋 App、標籤和備註", |
| | | "quickSearch.inputAccessibility": "快捷搜尋輸入框", |
| | | "quickSearch.emptyPrompt": "開始輸入以搜尋 App、標籤和備註。", |
| | | "quickSearch.noResults": "找不到 App。", |
| | | "quickSearch.launchFailed": "無法打開這個 App。請重試或選擇其他 App。", |
| | | "quickSearch.tagPrefix": "標籤", |
| | | "quickSearch.hotkeys": "快捷鍵", |
| | | "quickSearch.mainHotkey": "主介面", |
| | | "quickSearch.mainHotkeyDesc": "打開 TagLauncher 應用列表。", |
| | | "quickSearch.directHotkey": "快捷搜尋(全域)", |
| | | "quickSearch.directHotkeyDesc": "不在 TagLauncher 主介面時,直接打開快捷搜尋。全域有效。", |
| | | "quickSearch.record": "修改", |
| | | "quickSearch.recording": "錄製中", |
| | | "quickSearch.clear": "清除", |
| | | "quickSearch.restoreDefault": "恢復預設", |
| | | "quickSearch.retry": "重試註冊", |
| | | "quickSearch.recordingHint": "按下新的全域快捷鍵。按 Esc 取消。", |
| | | "quickSearch.disabled": "已關閉", |
| | | "quickSearch.blocked": "被阻塞", |
| | | "quickSearch.previousStillActive": "之前可用的快捷鍵仍保持可用:", |
| | | "quickSearch.status.Active": "已啟用", |
| | | "quickSearch.status.Conflict": "衝突", |
| | | "quickSearch.status.Disabled": "已關閉", |
| | | "quickSearch.hotkeyConflict.spotlight": "Command + Space 已被 Spotlight 使用。請在 macOS 鍵盤快捷鍵中修改 Spotlight,或選擇其他快捷鍵。", |
| | | "quickSearch.hotkeyConflict.finderSearch": "Option + Command + Space 已被 Finder 搜尋視窗使用。請在 macOS 鍵盤快捷鍵中修改,或選擇其他快捷鍵。", |
| | | "quickSearch.hotkeyConflict.previousInput": "Control + Space 已被「上一個輸入法」使用。請在 macOS 鍵盤快捷鍵中修改,或選擇其他快捷鍵。", |
| | | "quickSearch.hotkeyConflict.nextInput": "Control + Option + Space 已被「下一個輸入法」使用。請在 macOS 鍵盤快捷鍵中修改,或選擇其他快捷鍵。", |
| | | "quickSearch.hotkeyConflict.emoji": "Control + Command + Space 已被「表情與符號」使用。請在 macOS 鍵盤快捷鍵中修改,或選擇其他快捷鍵。", |
| | | "quickSearch.hotkeyConflict.generic": "這個快捷鍵已被 macOS 或其他 App 使用。請選擇其他快捷鍵,或先修改其他 App 中的快捷鍵後再重試。", |
| | | "quickSearch.loading": "正在準備搜尋索引…", |
| | | "quickSearch.openKeyboardSettings": "打開 macOS 鍵盤快捷鍵設定", |
| | | "quickSearch.internalHotkey": "快捷搜尋(TagLauncher 內部)", |
| | | "quickSearch.internalHotkeyDesc": "僅在 TagLauncher 主介面有效。", |
| | | "quickSearch.internalHotkeyStatus": "僅主介面有效", |
| | | "quickSearch.globalHotkey": "快捷搜尋(全域)", |
| | | "quickSearch.globalHotkeyDesc": "不在 TagLauncher 主介面時,直接打開快捷搜尋。全域有效。", |
| | | "quickSearch.spaceDisplay": "Space(空格鍵)", |
| | | "quickSearch.hotkeyConflict.fnSpace": "Fn + Space 可能已被 macOS 的輸入法或地球鍵快捷鍵佔用。請打開 macOS 鍵盤快捷鍵設定關閉或改掉對應系統快捷鍵,或在這裡選擇其他組合。" |
| | | } |
| | |
| | | @State private var categoryScheme = TagDatabase.CategorySchemeState() |
| | | @State private var isApplyingSystemScheme = false |
| | | @State private var showApplySystemSchemeConfirmation = false |
| | | @State private var recordingHotkeyKind: LauncherHotkeyKind? = nil |
| | | @State private var hotkeyCaptureMonitor: Any? = nil |
| | | @State private var hotkeyRefreshToken = 0 |
| | | |
| | | private func scanApps() { |
| | | DispatchQueue.global(qos: .userInitiated).async { |
| | |
| | | .contentShape(RoundedRectangle(cornerRadius: 8, style: .continuous)) |
| | | } |
| | | |
| | | private func applyHotkey(_ hotkey: LauncherHotkey?, for kind: LauncherHotkeyKind) { |
| | | (NSApp.delegate as? AppDelegate)?.applyHotkey(hotkey, for: kind) |
| | | hotkeyRefreshToken &+= 1 |
| | | } |
| | | |
| | | private func retryHotkey(_ kind: LauncherHotkeyKind) { |
| | | (NSApp.delegate as? AppDelegate)?.retryHotkeyRegistration(for: kind) |
| | | hotkeyRefreshToken &+= 1 |
| | | } |
| | | |
| | | private func openKeyboardShortcutsSettings() { |
| | | let urls = [ |
| | | URL(string: "x-apple.systempreferences:com.apple.Keyboard-Settings.extension?Shortcuts"), |
| | | URL(string: "x-apple.systempreferences:com.apple.preference.keyboard?shortcuts") |
| | | ].compactMap { $0 } |
| | | guard let url = urls.first else { return } |
| | | NSWorkspace.shared.open(url) |
| | | } |
| | | |
| | | private func hotkeySettingsPanel() -> some View { |
| | | VStack(alignment: .leading, spacing: 12) { |
| | | HStack { |
| | | Text(tr("quickSearch.hotkeys")) |
| | | .font(.system(size: 13, weight: .semibold)) |
| | | .foregroundStyle(.secondary) |
| | | Spacer() |
| | | Button(action: openKeyboardShortcutsSettings) { |
| | | Label(tr("quickSearch.openKeyboardSettings"), systemImage: "keyboard") |
| | | .font(.system(size: 13, weight: .semibold)) |
| | | .lineLimit(1) |
| | | } |
| | | .buttonStyle(.bordered) |
| | | .help(tr("quickSearch.openKeyboardSettings")) |
| | | .accessibilityLabel(tr("quickSearch.openKeyboardSettings")) |
| | | } |
| | | |
| | | HotkeySettingRow( |
| | | title: tr("quickSearch.mainHotkey"), |
| | | description: tr("quickSearch.mainHotkeyDesc"), |
| | | kind: .main, |
| | | isRecording: recordingHotkeyKind == .main, |
| | | allowsEditing: false, |
| | | refreshToken: hotkeyRefreshToken, |
| | | onBeginRecording: { }, |
| | | onRestoreDefault: nil, |
| | | onRetry: { }, |
| | | onCancelRecording: cancelHotkeyRecording |
| | | ) |
| | | |
| | | StaticHotkeyInfoRow( |
| | | title: tr("quickSearch.internalHotkey"), |
| | | description: tr("quickSearch.internalHotkeyDesc"), |
| | | displayText: tr("quickSearch.spaceDisplay"), |
| | | statusText: tr("quickSearch.internalHotkeyStatus") |
| | | ) |
| | | |
| | | HotkeySettingRow( |
| | | title: tr("quickSearch.globalHotkey"), |
| | | description: tr("quickSearch.globalHotkeyDesc"), |
| | | kind: .quickSearch, |
| | | isRecording: recordingHotkeyKind == .quickSearch, |
| | | allowsEditing: true, |
| | | refreshToken: hotkeyRefreshToken, |
| | | onBeginRecording: { beginHotkeyRecording(.quickSearch) }, |
| | | onRestoreDefault: { applyHotkey(.defaultQuickSearch, for: .quickSearch) }, |
| | | onRetry: { retryHotkey(.quickSearch) }, |
| | | onCancelRecording: cancelHotkeyRecording |
| | | ) |
| | | } |
| | | .id(hotkeyRefreshToken) |
| | | } |
| | | |
| | | private func beginHotkeyRecording(_ kind: LauncherHotkeyKind) { |
| | | removeHotkeyCaptureMonitor() |
| | | recordingHotkeyKind = kind |
| | | postHotkeyRecordingState(active: true) |
| | | hotkeyCaptureMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in |
| | | handleCapturedHotkeyEvent(event, for: kind) |
| | | } |
| | | } |
| | | |
| | | private func handleCapturedHotkeyEvent(_ event: NSEvent, for kind: LauncherHotkeyKind) -> NSEvent? { |
| | | if event.keyCode == UInt16(kVK_Escape) { |
| | | cancelHotkeyRecording() |
| | | return nil |
| | | } |
| | | guard let hotkey = LauncherHotkey.from(event: event) else { |
| | | NSSound.beep() |
| | | return nil |
| | | } |
| | | finishHotkeyRecording() |
| | | applyHotkey(hotkey, for: kind) |
| | | return nil |
| | | } |
| | | |
| | | private func cancelHotkeyRecording() { |
| | | finishHotkeyRecording() |
| | | } |
| | | |
| | | private func finishHotkeyRecording() { |
| | | guard recordingHotkeyKind != nil || hotkeyCaptureMonitor != nil else { return } |
| | | recordingHotkeyKind = nil |
| | | removeHotkeyCaptureMonitor() |
| | | postHotkeyRecordingState(active: false) |
| | | } |
| | | |
| | | private func removeHotkeyCaptureMonitor() { |
| | | if let hotkeyCaptureMonitor { |
| | | NSEvent.removeMonitor(hotkeyCaptureMonitor) |
| | | self.hotkeyCaptureMonitor = nil |
| | | } |
| | | } |
| | | |
| | | private func postHotkeyRecordingState(active: Bool) { |
| | | NotificationCenter.default.post( |
| | | name: .tagLauncherModalInteractionChanged, |
| | | object: nil, |
| | | userInfo: [ |
| | | "active": active, |
| | | "source": "hotkeyRecording" |
| | | ] |
| | | ) |
| | | } |
| | | |
| | | var body: some View { |
| | | ZStack { |
| | | TabView { |
| | |
| | | .frame(maxHeight: 250) |
| | | |
| | | Spacer(minLength: 0) |
| | | ShortcutHintView() |
| | | .frame(maxWidth: .infinity, alignment: .center) |
| | | .padding(.bottom, 2) |
| | | } |
| | | .frame(maxWidth: settingsContentWidth, alignment: .leading) |
| | | .tabItem { Label(tr("settings.language"), systemImage: "globe") } |
| | | .padding() |
| | | |
| | | // Tab 2: General |
| | | VStack(alignment: .leading, spacing: 0) { |
| | | // Toggle row — centered as a rectangular block |
| | | HStack(spacing: 20) { |
| | | if AppDelegate.supportsLaunchAtLogin { |
| | | Toggle(tr("settings.launchAtLogin"), isOn: $launchAtLogin) |
| | | .onChange(of: launchAtLogin) { _, enabled in |
| | | if enabled { |
| | | AppDelegate.enableLaunchAtLogin() |
| | | } else { |
| | | AppDelegate.disableLaunchAtLogin() |
| | | ScrollView(.vertical, showsIndicators: true) { |
| | | VStack(alignment: .leading, spacing: 0) { |
| | | // Toggle row — centered as a rectangular block |
| | | HStack(spacing: 20) { |
| | | if AppDelegate.supportsLaunchAtLogin { |
| | | Toggle(tr("settings.launchAtLogin"), isOn: $launchAtLogin) |
| | | .onChange(of: launchAtLogin) { _, enabled in |
| | | if enabled { |
| | | AppDelegate.enableLaunchAtLogin() |
| | | } else { |
| | | AppDelegate.disableLaunchAtLogin() |
| | | } |
| | | } |
| | | } |
| | | Toggle(tr("settings.showInDock"), isOn: $showDockIcon) |
| | | .onChange(of: showDockIcon) { _, _ in |
| | | AppDelegate.refreshChromeSettings() |
| | | } |
| | | } |
| | | } |
| | | Toggle(tr("settings.showInDock"), isOn: $showDockIcon) |
| | | .onChange(of: showDockIcon) { _, _ in |
| | | AppDelegate.refreshChromeSettings() |
| | | Toggle(tr("settings.hideAppNames"), isOn: $hideAppNames) |
| | | } |
| | | Toggle(tr("settings.hideAppNames"), isOn: $hideAppNames) |
| | | } |
| | | .frame(maxWidth: .infinity, alignment: .center) |
| | | .padding(.bottom, 16) |
| | | .frame(maxWidth: .infinity, alignment: .center) |
| | | .padding(.bottom, 16) |
| | | |
| | | Divider() |
| | | .padding(.bottom, 16) |
| | | Divider() |
| | | .padding(.bottom, 16) |
| | | |
| | | VStack(spacing: 18) { |
| | | generalSettingRow(tr("settings.appListStyle"), description: tr("settings.flatDesc")) { |
| | | VStack(spacing: 8) { |
| | | DisplayModeOptionButton( |
| | | mode: "flat", |
| | | title: tr("settings.flat"), |
| | | isSelected: displayMode == "flat" |
| | | ) { |
| | | displayMode = "flat" |
| | | } |
| | | |
| | | LazyVGrid(columns: displayModeColumns, alignment: .leading, spacing: 8) { |
| | | ForEach(containerDisplayModeOptions, id: \.id) { option in |
| | | VStack(spacing: 18) { |
| | | generalSettingRow(tr("settings.appListStyle"), description: tr("settings.flatDesc")) { |
| | | VStack(spacing: 8) { |
| | | DisplayModeOptionButton( |
| | | mode: option.id, |
| | | title: option.title, |
| | | isSelected: displayMode == option.id |
| | | mode: "flat", |
| | | title: tr("settings.flat"), |
| | | isSelected: displayMode == "flat" |
| | | ) { |
| | | displayMode = option.id |
| | | displayMode = "flat" |
| | | } |
| | | |
| | | LazyVGrid(columns: displayModeColumns, alignment: .leading, spacing: 8) { |
| | | ForEach(containerDisplayModeOptions, id: \.id) { option in |
| | | DisplayModeOptionButton( |
| | | mode: option.id, |
| | | title: option.title, |
| | | isSelected: displayMode == option.id |
| | | ) { |
| | | displayMode = option.id |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .frame(width: generalControlWidth, alignment: .leading) |
| | | } |
| | | |
| | | generalSettingRow(tr("settings.tagPosition"), description: tr("settings.tagPosDesc")) { |
| | | Picker("", selection: $tagPosition) { |
| | | Text(tr("settings.left")).tag("left") |
| | | Text(tr("settings.right")).tag("right") |
| | | Text(tr("settings.top")).tag("top") |
| | | } |
| | | .pickerStyle(.segmented) |
| | | .frame(width: compactPickerWidth, alignment: .leading) |
| | | } |
| | | |
| | | generalSettingRow(tr("settings.tagFontSize"), description: tr("settings.tagFontDesc")) { |
| | | Picker("", selection: $tagFontSize) { |
| | | ForEach([16.0, 18.0, 20.0, 22.0, 24.0, 26.0], id: \.self) { size in |
| | | Text("\(Int(size))").tag(size) |
| | | } |
| | | } |
| | | .pickerStyle(.segmented) |
| | | .frame(width: compactPickerWidth, alignment: .leading) |
| | | } |
| | | |
| | | generalSettingRow(tr("settings.iconSize"), description: tr("settings.iconSizeDesc")) { |
| | | Picker("", selection: $iconSize) { |
| | | ForEach([40.0, 48.0, 56.0, 64.0, 72.0, 80.0], id: \.self) { size in |
| | | Text("\(Int(size))").tag(size) |
| | | } |
| | | } |
| | | .pickerStyle(.segmented) |
| | | .frame(width: compactPickerWidth, alignment: .leading) |
| | | } |
| | | } |
| | | .frame(width: generalControlWidth, alignment: .leading) |
| | | } |
| | | |
| | | generalSettingRow(tr("settings.tagPosition"), description: tr("settings.tagPosDesc")) { |
| | | Picker("", selection: $tagPosition) { |
| | | Text(tr("settings.left")).tag("left") |
| | | Text(tr("settings.right")).tag("right") |
| | | Text(tr("settings.top")).tag("top") |
| | | } |
| | | .pickerStyle(.segmented) |
| | | .frame(width: compactPickerWidth, alignment: .leading) |
| | | } |
| | | |
| | | generalSettingRow(tr("settings.tagFontSize"), description: tr("settings.tagFontDesc")) { |
| | | Picker("", selection: $tagFontSize) { |
| | | ForEach([16.0, 18.0, 20.0, 22.0, 24.0, 26.0], id: \.self) { size in |
| | | Text("\(Int(size))").tag(size) |
| | | } |
| | | } |
| | | .pickerStyle(.segmented) |
| | | .frame(width: compactPickerWidth, alignment: .leading) |
| | | } |
| | | |
| | | generalSettingRow(tr("settings.iconSize"), description: tr("settings.iconSizeDesc")) { |
| | | Picker("", selection: $iconSize) { |
| | | ForEach([40.0, 48.0, 56.0, 64.0, 72.0, 80.0], id: \.self) { size in |
| | | Text("\(Int(size))").tag(size) |
| | | } |
| | | } |
| | | .pickerStyle(.segmented) |
| | | .frame(width: compactPickerWidth, alignment: .leading) |
| | | } |
| | | .frame(maxWidth: generalContentWidth, alignment: .center) |
| | | .padding() |
| | | } |
| | | } |
| | | .frame(maxWidth: generalContentWidth, alignment: .center) |
| | | .padding() |
| | | .tabItem { Label(tr("settings.general"), systemImage: "gearshape") } |
| | | .tabItem { Label(tr("settings.general"), systemImage: "gearshape") } |
| | | |
| | | // Tab 3: Tags |
| | | // Tab 3: Hotkeys |
| | | ScrollView(.vertical, showsIndicators: true) { |
| | | VStack(alignment: .leading, spacing: 16) { |
| | | hotkeySettingsPanel() |
| | | .frame(width: generalContentWidth, alignment: .leading) |
| | | } |
| | | .frame(maxWidth: generalContentWidth, alignment: .center) |
| | | .padding() |
| | | } |
| | | .tabItem { Label(tr("quickSearch.hotkeys"), systemImage: "keyboard") } |
| | | |
| | | // Tab 4: Tags |
| | | VStack(spacing: 0) { |
| | | TagEditorView( |
| | | tagColors: $tagColors, |
| | |
| | | .tabItem { Label(tr("settings.tags"), systemImage: "tag.fill") } |
| | | .onAppear { scanApps() } |
| | | |
| | | // Tab 4: Data |
| | | // Tab 5: Data |
| | | VStack(spacing: 0) { |
| | | Spacer(minLength: 32) |
| | | |
| | |
| | | .padding() |
| | | .onAppear { refreshDataState() } |
| | | |
| | | // Tab 5: About |
| | | // Tab 6: About |
| | | VStack(spacing: 0) { |
| | | Spacer(minLength: 72) |
| | | |
| | |
| | | .frame(width: 560, alignment: .leading) |
| | | |
| | | Spacer(minLength: 30) |
| | | ShortcutHintView() |
| | | .frame(maxWidth: .infinity, alignment: .center) |
| | | Spacer(minLength: 18) |
| | | } |
| | | .tabItem { Label(tr("settings.about"), systemImage: "info.circle") } |
| | |
| | | } |
| | | .onReceive(NotificationCenter.default.publisher(for: .tagLauncherDataDidChange)) { _ in |
| | | refreshDataState() |
| | | } |
| | | .onReceive(NotificationCenter.default.publisher(for: .tagLauncherHotkeysChanged)) { _ in |
| | | hotkeyRefreshToken &+= 1 |
| | | } |
| | | |
| | | if isRefreshingLanguage { |
| | |
| | | .onAppear { |
| | | syncSelectedLanguage() |
| | | } |
| | | .onDisappear { |
| | | finishHotkeyRecording() |
| | | } |
| | | } |
| | | |
| | | private func showLanguageRefresh() { |
| | |
| | | isRefreshingLanguage = false |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | private struct StaticHotkeyInfoRow: View { |
| | | let title: String |
| | | let description: String |
| | | let displayText: String |
| | | let statusText: String |
| | | |
| | | var body: some View { |
| | | VStack(alignment: .leading, spacing: 12) { |
| | | HStack(alignment: .top, spacing: 12) { |
| | | VStack(alignment: .leading, spacing: 3) { |
| | | Text(title) |
| | | .font(.system(size: 13, weight: .semibold)) |
| | | Text(description) |
| | | .font(.caption) |
| | | .foregroundStyle(.secondary) |
| | | .fixedSize(horizontal: false, vertical: true) |
| | | } |
| | | .frame(maxWidth: .infinity, alignment: .leading) |
| | | |
| | | VStack(alignment: .leading, spacing: 4) { |
| | | Text(displayText) |
| | | .font(.system(size: 15, weight: .semibold)) |
| | | .lineLimit(1) |
| | | .minimumScaleFactor(0.85) |
| | | Text(statusText) |
| | | .font(.system(size: 11, weight: .semibold)) |
| | | .foregroundStyle(Color.secondary) |
| | | .padding(.horizontal, 8) |
| | | .padding(.vertical, 3) |
| | | .background(Capsule().fill(Color.secondary.opacity(0.13))) |
| | | } |
| | | .frame(width: 180, alignment: .leading) |
| | | } |
| | | } |
| | | .padding(12) |
| | | .background( |
| | | RoundedRectangle(cornerRadius: 8, style: .continuous) |
| | | .fill(Color(nsColor: .controlBackgroundColor)) |
| | | ) |
| | | .overlay( |
| | | RoundedRectangle(cornerRadius: 8, style: .continuous) |
| | | .stroke(Color.secondary.opacity(0.14), lineWidth: 1) |
| | | ) |
| | | } |
| | | } |
| | | |
| | | private struct HotkeySettingRow: View { |
| | | let title: String |
| | | let description: String |
| | | let kind: LauncherHotkeyKind |
| | | let isRecording: Bool |
| | | let allowsEditing: Bool |
| | | let refreshToken: Int |
| | | let onBeginRecording: () -> Void |
| | | let onRestoreDefault: (() -> Void)? |
| | | let onRetry: () -> Void |
| | | let onCancelRecording: () -> Void |
| | | |
| | | private var activeHotkey: LauncherHotkey? { |
| | | _ = refreshToken |
| | | return LauncherHotkeyStore.hotkey(for: kind) |
| | | } |
| | | |
| | | private var pendingHotkey: LauncherHotkey? { |
| | | _ = refreshToken |
| | | return LauncherHotkeyStore.pendingHotkey(for: kind) |
| | | } |
| | | |
| | | private var status: LauncherHotkeyStatus { |
| | | _ = refreshToken |
| | | return LauncherHotkeyStore.status(for: kind) |
| | | } |
| | | |
| | | private var conflictMessage: String { |
| | | _ = refreshToken |
| | | return LauncherHotkeyStore.conflictMessage(for: kind) |
| | | } |
| | | |
| | | var body: some View { |
| | | VStack(alignment: .leading, spacing: 12) { |
| | | HStack(alignment: .top, spacing: 12) { |
| | | VStack(alignment: .leading, spacing: 3) { |
| | | Text(title) |
| | | .font(.system(size: 13, weight: .semibold)) |
| | | Text(description) |
| | | .font(.caption) |
| | | .foregroundStyle(.secondary) |
| | | .fixedSize(horizontal: false, vertical: true) |
| | | } |
| | | .frame(maxWidth: .infinity, alignment: .leading) |
| | | |
| | | VStack(alignment: .leading, spacing: 4) { |
| | | Text(displayText) |
| | | .font(.system(size: 15, weight: .semibold)) |
| | | .lineLimit(1) |
| | | .minimumScaleFactor(0.85) |
| | | statusLabel |
| | | } |
| | | .frame(width: 180, alignment: .leading) |
| | | } |
| | | |
| | | if allowsEditing { |
| | | HStack(spacing: 8) { |
| | | hotkeyButton( |
| | | isRecording ? tr("quickSearch.recording") : tr("quickSearch.record"), |
| | | systemImage: isRecording ? "keyboard.badge.ellipsis" : "keyboard", |
| | | prominent: true, |
| | | action: onBeginRecording |
| | | ) |
| | | if let onRestoreDefault { |
| | | hotkeyButton( |
| | | tr("quickSearch.restoreDefault"), |
| | | systemImage: "arrow.counterclockwise", |
| | | prominent: false, |
| | | action: onRestoreDefault |
| | | ) |
| | | } |
| | | if status == .conflict { |
| | | hotkeyButton( |
| | | tr("quickSearch.retry"), |
| | | systemImage: "arrow.clockwise", |
| | | prominent: false, |
| | | action: onRetry |
| | | ) |
| | | } |
| | | Spacer(minLength: 0) |
| | | } |
| | | } |
| | | |
| | | if allowsEditing && isRecording { |
| | | HStack(spacing: 8) { |
| | | Image(systemName: "keyboard") |
| | | Text(tr("quickSearch.recordingHint")) |
| | | Spacer(minLength: 0) |
| | | Button(tr("settings.cancel"), action: onCancelRecording) |
| | | .buttonStyle(.borderless) |
| | | } |
| | | .font(.system(size: 13, weight: .semibold)) |
| | | .foregroundStyle(Color.accentColor) |
| | | .padding(.horizontal, 10) |
| | | .padding(.vertical, 10) |
| | | .background(RoundedRectangle(cornerRadius: 7).fill(Color.accentColor.opacity(0.10))) |
| | | } |
| | | |
| | | if status == .conflict && !conflictMessage.isEmpty { |
| | | HotkeyConflictNotice(text: conflictDetailText) |
| | | } |
| | | } |
| | | .padding(12) |
| | | .background( |
| | | RoundedRectangle(cornerRadius: 8, style: .continuous) |
| | | .fill(Color(nsColor: .controlBackgroundColor)) |
| | | ) |
| | | .overlay( |
| | | RoundedRectangle(cornerRadius: 8, style: .continuous) |
| | | .stroke(Color.secondary.opacity(0.14), lineWidth: 1) |
| | | ) |
| | | } |
| | | |
| | | private var displayText: String { |
| | | if status == .conflict, let pendingHotkey { |
| | | return "\(pendingHotkey.displayString) · \(tr("quickSearch.blocked"))" |
| | | } |
| | | return activeHotkey?.displayString ?? tr("quickSearch.disabled") |
| | | } |
| | | |
| | | private var conflictDetailText: String { |
| | | if let activeHotkey { |
| | | return "\(conflictMessage) \(tr("quickSearch.previousStillActive")) \(activeHotkey.displayString)" |
| | | } |
| | | return conflictMessage |
| | | } |
| | | |
| | | private var statusLabel: some View { |
| | | Text(tr("quickSearch.status.\(status.rawValue)")) |
| | | .font(.system(size: 11, weight: .semibold)) |
| | | .foregroundStyle(statusColor) |
| | | .padding(.horizontal, 8) |
| | | .padding(.vertical, 3) |
| | | .background(Capsule().fill(statusColor.opacity(0.13))) |
| | | } |
| | | |
| | | private var statusColor: Color { |
| | | switch status { |
| | | case .active: return .green |
| | | case .conflict: return .orange |
| | | case .disabled: return .secondary |
| | | } |
| | | } |
| | | |
| | | private func hotkeyButton( |
| | | _ title: String, |
| | | systemImage: String, |
| | | prominent: Bool, |
| | | action: @escaping () -> Void |
| | | ) -> some View { |
| | | Button(action: action) { |
| | | Label(title, systemImage: systemImage) |
| | | .font(.system(size: 13, weight: .semibold)) |
| | | .lineLimit(1) |
| | | .minimumScaleFactor(0.88) |
| | | .foregroundStyle(prominent ? Color.white : Color.primary) |
| | | .padding(.horizontal, 10) |
| | | .frame(height: 30) |
| | | .background( |
| | | RoundedRectangle(cornerRadius: 7, style: .continuous) |
| | | .fill(prominent ? Color.accentColor : Color(nsColor: .controlBackgroundColor)) |
| | | ) |
| | | .overlay( |
| | | RoundedRectangle(cornerRadius: 7, style: .continuous) |
| | | .stroke(prominent ? Color.accentColor.opacity(0.24) : Color.secondary.opacity(0.22), lineWidth: 1) |
| | | ) |
| | | } |
| | | .buttonStyle(.plain) |
| | | .help(title) |
| | | .accessibilityLabel(title) |
| | | } |
| | | } |
| | | |
| | | private struct HotkeyConflictNotice: View { |
| | | let text: String |
| | | |
| | | var body: some View { |
| | | HStack(alignment: .top, spacing: 8) { |
| | | Image(systemName: "exclamationmark.triangle.fill") |
| | | .font(.system(size: 13, weight: .semibold)) |
| | | .foregroundStyle(Color.white.opacity(0.94)) |
| | | .padding(.top, 1) |
| | | Text(text) |
| | | .font(.system(size: 12, weight: .medium)) |
| | | .foregroundStyle(Color.white) |
| | | .fixedSize(horizontal: false, vertical: true) |
| | | Spacer(minLength: 0) |
| | | } |
| | | .padding(.horizontal, 10) |
| | | .padding(.vertical, 9) |
| | | .background( |
| | | RoundedRectangle(cornerRadius: 8, style: .continuous) |
| | | .fill(Color.black.opacity(0.92)) |
| | | ) |
| | | .shadow(color: .black.opacity(0.24), radius: 16, y: 8) |
| | | } |
| | | } |
| | | |
| | |
| | | } |
| | | let palette: [Color] = [.green, .purple, .blue, .orange] |
| | | return palette[index % palette.count].opacity(0.78) |
| | | } |
| | | } |
| | | |
| | | private struct ShortcutHintView: View { |
| | | var body: some View { |
| | | HStack(spacing: 12) { |
| | | ShortcutKeycap(symbol: "⌥") |
| | | Text("+") |
| | | .font(.system(size: 22, weight: .semibold)) |
| | | .foregroundStyle(.tertiary) |
| | | ShortcutKeycap(symbol: "⇧") |
| | | Text("+") |
| | | .font(.system(size: 22, weight: .semibold)) |
| | | .foregroundStyle(.tertiary) |
| | | SpacebarKeycap() |
| | | } |
| | | .accessibilityLabel("⌥ ⇧ Space") |
| | | .allowsHitTesting(false) |
| | | } |
| | | } |
| | | |
| | | private struct ShortcutKeycap: View { |
| | | let symbol: String |
| | | |
| | | var body: some View { |
| | | Text(symbol) |
| | | .font(.system(size: 36, weight: .medium)) |
| | | .foregroundStyle(.secondary) |
| | | .frame(width: 74, height: 58) |
| | | .background(KeycapBackground()) |
| | | } |
| | | } |
| | | |
| | | private struct SpacebarKeycap: View { |
| | | var body: some View { |
| | | ZStack { |
| | | KeycapBackground() |
| | | Capsule() |
| | | .fill(Color.secondary.opacity(0.38)) |
| | | .frame(width: 72, height: 3) |
| | | .offset(y: 13) |
| | | } |
| | | .frame(width: 190, height: 58) |
| | | } |
| | | } |
| | | |
| | | private struct KeycapBackground: View { |
| | | var body: some View { |
| | | RoundedRectangle(cornerRadius: 10, style: .continuous) |
| | | .fill(Color(nsColor: .controlBackgroundColor)) |
| | | .overlay( |
| | | RoundedRectangle(cornerRadius: 10, style: .continuous) |
| | | .stroke(Color.primary.opacity(0.12), lineWidth: 1) |
| | | ) |
| | | .shadow(color: .black.opacity(0.10), radius: 8, x: 0, y: 3) |
| | | } |
| | | } |
| New file |
| | |
| | | import SwiftUI |
| | | import AppKit |
| | | import Carbon |
| | | |
| | | extension Notification.Name { |
| | | static let tagLauncherQuickSearchRequested = Notification.Name("TagLauncherQuickSearchRequested") |
| | | static let tagLauncherQuickSearchDismissRequested = Notification.Name("TagLauncherQuickSearchDismissRequested") |
| | | static let tagLauncherQuickSearchVisibilityChanged = Notification.Name("TagLauncherQuickSearchVisibilityChanged") |
| | | static let tagLauncherHotkeysChanged = Notification.Name("TagLauncherHotkeysChanged") |
| | | } |
| | | |
| | | enum QuickSearchOpenSource { |
| | | static let mainOverlay = "mainOverlay" |
| | | static let globalHidden = "globalHidden" |
| | | static let globalVisible = "globalVisible" |
| | | } |
| | | |
| | | // MARK: - Hotkeys |
| | | |
| | | struct LauncherHotkey: Equatable { |
| | | let keyCode: UInt32 |
| | | let modifiers: UInt32 |
| | | |
| | | var serialized: String { |
| | | "\(keyCode):\(modifiers)" |
| | | } |
| | | |
| | | var displayString: String { |
| | | let ordered: [(UInt32, String)] = [ |
| | | (UInt32(controlKey), "⌃"), |
| | | (UInt32(optionKey), "⌥"), |
| | | (UInt32(shiftKey), "⇧"), |
| | | (UInt32(cmdKey), "⌘"), |
| | | (UInt32(kEventKeyModifierFnMask), "Fn+") |
| | | ] |
| | | let modifierGlyphs = ordered |
| | | .filter { modifiers & $0.0 != 0 } |
| | | .map(\.1) |
| | | .joined() |
| | | return modifierGlyphs + LauncherHotkey.keyDisplayName(for: keyCode) |
| | | } |
| | | |
| | | static var defaultMain: LauncherHotkey { |
| | | LauncherHotkey(keyCode: UInt32(kVK_Space), modifiers: UInt32(shiftKey | optionKey)) |
| | | } |
| | | |
| | | static var defaultQuickSearch: LauncherHotkey { |
| | | LauncherHotkey(keyCode: UInt32(kVK_Space), modifiers: UInt32(kEventKeyModifierFnMask)) |
| | | } |
| | | |
| | | static func deserialize(_ value: String?) -> LauncherHotkey? { |
| | | guard let value, !value.isEmpty else { return nil } |
| | | let parts = value.split(separator: ":") |
| | | guard parts.count == 2, |
| | | let keyCode = UInt32(parts[0]), |
| | | let modifiers = UInt32(parts[1]) |
| | | else { return nil } |
| | | return LauncherHotkey(keyCode: keyCode, modifiers: modifiers) |
| | | } |
| | | |
| | | static func from(event: NSEvent) -> LauncherHotkey? { |
| | | let ignoredKeyCodes: Set<UInt16> = [54, 55, 56, 57, 58, 59, 60, 61, 62, 63] |
| | | guard !ignoredKeyCodes.contains(event.keyCode) else { return nil } |
| | | |
| | | var carbonModifiers: UInt32 = 0 |
| | | if event.modifierFlags.contains(.shift) { carbonModifiers |= UInt32(shiftKey) } |
| | | if event.modifierFlags.contains(.option) { carbonModifiers |= UInt32(optionKey) } |
| | | if event.modifierFlags.contains(.control) { carbonModifiers |= UInt32(controlKey) } |
| | | if event.modifierFlags.contains(.command) { carbonModifiers |= UInt32(cmdKey) } |
| | | if event.modifierFlags.contains(.function) { carbonModifiers |= UInt32(kEventKeyModifierFnMask) } |
| | | guard carbonModifiers != 0 else { return nil } |
| | | |
| | | return LauncherHotkey(keyCode: UInt32(event.keyCode), modifiers: carbonModifiers) |
| | | } |
| | | |
| | | static func keyDisplayName(for keyCode: UInt32) -> String { |
| | | switch Int(keyCode) { |
| | | case kVK_Space: return "Space" |
| | | case kVK_Return: return "Return" |
| | | case kVK_Escape: return "Esc" |
| | | case kVK_Delete: return "Delete" |
| | | case kVK_Tab: return "Tab" |
| | | case kVK_LeftArrow: return "←" |
| | | case kVK_RightArrow: return "→" |
| | | case kVK_UpArrow: return "↑" |
| | | case kVK_DownArrow: return "↓" |
| | | case kVK_F1: return "F1" |
| | | case kVK_F2: return "F2" |
| | | case kVK_F3: return "F3" |
| | | case kVK_F4: return "F4" |
| | | case kVK_F5: return "F5" |
| | | case kVK_F6: return "F6" |
| | | case kVK_F7: return "F7" |
| | | case kVK_F8: return "F8" |
| | | case kVK_F9: return "F9" |
| | | case kVK_F10: return "F10" |
| | | case kVK_F11: return "F11" |
| | | case kVK_F12: return "F12" |
| | | default: |
| | | if let scalar = keyCodeToPrintableScalar[Int(keyCode)] { |
| | | return String(scalar) |
| | | } |
| | | return "Key \(keyCode)" |
| | | } |
| | | } |
| | | |
| | | private static let keyCodeToPrintableScalar: [Int: Character] = [ |
| | | kVK_ANSI_A: "A", kVK_ANSI_B: "B", kVK_ANSI_C: "C", kVK_ANSI_D: "D", |
| | | kVK_ANSI_E: "E", kVK_ANSI_F: "F", kVK_ANSI_G: "G", kVK_ANSI_H: "H", |
| | | kVK_ANSI_I: "I", kVK_ANSI_J: "J", kVK_ANSI_K: "K", kVK_ANSI_L: "L", |
| | | kVK_ANSI_M: "M", kVK_ANSI_N: "N", kVK_ANSI_O: "O", kVK_ANSI_P: "P", |
| | | kVK_ANSI_Q: "Q", kVK_ANSI_R: "R", kVK_ANSI_S: "S", kVK_ANSI_T: "T", |
| | | kVK_ANSI_U: "U", kVK_ANSI_V: "V", kVK_ANSI_W: "W", kVK_ANSI_X: "X", |
| | | kVK_ANSI_Y: "Y", kVK_ANSI_Z: "Z", kVK_ANSI_0: "0", kVK_ANSI_1: "1", |
| | | kVK_ANSI_2: "2", kVK_ANSI_3: "3", kVK_ANSI_4: "4", kVK_ANSI_5: "5", |
| | | kVK_ANSI_6: "6", kVK_ANSI_7: "7", kVK_ANSI_8: "8", kVK_ANSI_9: "9" |
| | | ] |
| | | } |
| | | |
| | | enum LauncherHotkeyKind: String { |
| | | case main |
| | | case quickSearch |
| | | |
| | | var storageKey: String { |
| | | switch self { |
| | | case .main: return "mainHotkey" |
| | | case .quickSearch: return "quickSearchHotkey" |
| | | } |
| | | } |
| | | |
| | | var statusKey: String { |
| | | switch self { |
| | | case .main: return "mainHotkeyStatus" |
| | | case .quickSearch: return "quickSearchHotkeyStatus" |
| | | } |
| | | } |
| | | |
| | | var conflictMessageKey: String { |
| | | switch self { |
| | | case .main: return "mainHotkeyConflictMessage" |
| | | case .quickSearch: return "quickSearchHotkeyConflictMessage" |
| | | } |
| | | } |
| | | |
| | | var pendingStorageKey: String { |
| | | switch self { |
| | | case .main: return "mainHotkeyPending" |
| | | case .quickSearch: return "quickSearchHotkeyPending" |
| | | } |
| | | } |
| | | |
| | | var eventID: UInt32 { |
| | | switch self { |
| | | case .main: return 1 |
| | | case .quickSearch: return 2 |
| | | } |
| | | } |
| | | } |
| | | |
| | | enum LauncherHotkeyStatus: String { |
| | | case active = "Active" |
| | | case conflict = "Conflict" |
| | | case disabled = "Disabled" |
| | | } |
| | | |
| | | enum LauncherHotkeyStore { |
| | | static func hotkey(for kind: LauncherHotkeyKind) -> LauncherHotkey? { |
| | | if kind == .main { |
| | | return .defaultMain |
| | | } |
| | | if kind == .quickSearch && !AppDefaults.hasStoredValue(for: kind.storageKey) { |
| | | return .defaultQuickSearch |
| | | } |
| | | return LauncherHotkey.deserialize(UserDefaults.standard.string(forKey: kind.storageKey)) |
| | | } |
| | | |
| | | static func save(_ hotkey: LauncherHotkey?, for kind: LauncherHotkeyKind) { |
| | | UserDefaults.standard.set(hotkey?.serialized ?? "", forKey: kind.storageKey) |
| | | UserDefaults.standard.removeObject(forKey: kind.pendingStorageKey) |
| | | } |
| | | |
| | | static func savePending(_ hotkey: LauncherHotkey, for kind: LauncherHotkeyKind) { |
| | | UserDefaults.standard.set(hotkey.serialized, forKey: kind.pendingStorageKey) |
| | | } |
| | | |
| | | static func pendingHotkey(for kind: LauncherHotkeyKind) -> LauncherHotkey? { |
| | | LauncherHotkey.deserialize(UserDefaults.standard.string(forKey: kind.pendingStorageKey)) |
| | | } |
| | | |
| | | static func status(for kind: LauncherHotkeyKind) -> LauncherHotkeyStatus { |
| | | let raw = UserDefaults.standard.string(forKey: kind.statusKey) |
| | | return LauncherHotkeyStatus(rawValue: raw ?? "") ?? (hotkey(for: kind) == nil ? .disabled : .active) |
| | | } |
| | | |
| | | static func setStatus(_ status: LauncherHotkeyStatus, message: String?, for kind: LauncherHotkeyKind) { |
| | | UserDefaults.standard.set(status.rawValue, forKey: kind.statusKey) |
| | | if let message, !message.isEmpty { |
| | | UserDefaults.standard.set(message, forKey: kind.conflictMessageKey) |
| | | } else { |
| | | UserDefaults.standard.removeObject(forKey: kind.conflictMessageKey) |
| | | } |
| | | NotificationCenter.default.post(name: .tagLauncherHotkeysChanged, object: nil) |
| | | } |
| | | |
| | | static func conflictMessage(for kind: LauncherHotkeyKind) -> String { |
| | | UserDefaults.standard.string(forKey: kind.conflictMessageKey) ?? "" |
| | | } |
| | | |
| | | static func knownConflictMessage(for hotkey: LauncherHotkey, status: OSStatus) -> String? { |
| | | guard status != noErr else { return nil } |
| | | switch (hotkey.keyCode, hotkey.modifiers) { |
| | | case (UInt32(kVK_Space), UInt32(cmdKey)): |
| | | return tr("quickSearch.hotkeyConflict.spotlight") |
| | | case (UInt32(kVK_Space), UInt32(optionKey | cmdKey)): |
| | | return tr("quickSearch.hotkeyConflict.finderSearch") |
| | | case (UInt32(kVK_Space), UInt32(controlKey)): |
| | | return tr("quickSearch.hotkeyConflict.previousInput") |
| | | case (UInt32(kVK_Space), UInt32(controlKey | optionKey)): |
| | | return tr("quickSearch.hotkeyConflict.nextInput") |
| | | case (UInt32(kVK_Space), UInt32(controlKey | cmdKey)): |
| | | return tr("quickSearch.hotkeyConflict.emoji") |
| | | case (UInt32(kVK_Space), UInt32(kEventKeyModifierFnMask)): |
| | | return tr("quickSearch.hotkeyConflict.fnSpace") |
| | | default: |
| | | return nil |
| | | } |
| | | } |
| | | } |
| | | |
| | | // MARK: - Search Documents |
| | | |
| | | struct QuickSearchDocument: Identifiable { |
| | | var id: URL { app.id } |
| | | let app: AppInfo |
| | | let localizedNames: [String] |
| | | let internalBundleNames: [String] |
| | | let tagNames: [String] |
| | | let note: String |
| | | let bundleIdentifier: String |
| | | let lastOpenedAt: Date? |
| | | let openCount: Int |
| | | } |
| | | |
| | | struct QuickSearchResult: Identifiable { |
| | | var id: URL { document.id } |
| | | let document: QuickSearchDocument |
| | | let finalScore: Double |
| | | let textScore: Double |
| | | let bestFieldRank: Int |
| | | let matchedTagName: String? |
| | | let noteSnippet: String? |
| | | |
| | | var app: AppInfo { document.app } |
| | | } |
| | | |
| | | enum QuickSearchEngine { |
| | | private enum FieldKind: Int { |
| | | case name = 0 |
| | | case tag = 1 |
| | | case note = 2 |
| | | case bundleIdentifier = 3 |
| | | case internalBundleName = 4 |
| | | |
| | | var weight: Double { |
| | | switch self { |
| | | case .name: return 100 |
| | | case .tag: return 70 |
| | | case .note: return 45 |
| | | case .internalBundleName: return 30 |
| | | case .bundleIdentifier: return 20 |
| | | } |
| | | } |
| | | } |
| | | |
| | | private enum MatchKind { |
| | | case exact |
| | | case prefix |
| | | case substring |
| | | case acronym |
| | | case fuzzy |
| | | |
| | | var weight: Double { |
| | | switch self { |
| | | case .exact: return 100 |
| | | case .prefix: return 80 |
| | | case .substring: return 60 |
| | | case .acronym: return 55 |
| | | case .fuzzy: return 35 |
| | | } |
| | | } |
| | | } |
| | | |
| | | private struct Field { |
| | | let kind: FieldKind |
| | | let text: String |
| | | let normalized: String |
| | | let acronym: String |
| | | let pinyinCandidates: [String] |
| | | } |
| | | |
| | | private struct MatchOptions { |
| | | let allowSubstring: Bool |
| | | let allowFuzzySubsequence: Bool |
| | | } |
| | | |
| | | private struct TokenMatch { |
| | | let score: Double |
| | | let fieldRank: Int |
| | | let fieldKind: FieldKind |
| | | let originalText: String |
| | | } |
| | | |
| | | static func makeDocuments(apps: [AppInfo], store: TagDatabase.Store) -> [QuickSearchDocument] { |
| | | apps.map { app in |
| | | QuickSearchDocument( |
| | | app: app, |
| | | localizedNames: localizedNames(for: app), |
| | | internalBundleNames: internalBundleNames(for: app), |
| | | tagNames: app.tags, |
| | | note: store.appNotes[app.path.path] ?? app.note ?? "", |
| | | bundleIdentifier: app.bundleIdentifier ?? "", |
| | | lastOpenedAt: store.appLastOpenedAt[app.path.path], |
| | | openCount: store.appOpenCounts[app.path.path] ?? 0 |
| | | ) |
| | | } |
| | | } |
| | | |
| | | static func search(_ query: String, documents: [QuickSearchDocument], limit: Int = 50) -> [QuickSearchResult] { |
| | | let normalizedQuery = normalizeQuery(query) |
| | | guard !normalizedQuery.isEmpty else { |
| | | return emptyQueryResults(documents: documents, limit: min(limit, 6)) |
| | | } |
| | | |
| | | let tokens = normalizedQuery.split(separator: " ").map(String.init) |
| | | let results = documents.compactMap { result(for: $0, tokens: tokens) } |
| | | return results.sorted(by: rank).prefix(limit).map { $0 } |
| | | } |
| | | |
| | | static func normalizeQuery(_ value: String) -> String { |
| | | value |
| | | .trimmingCharacters(in: .whitespacesAndNewlines) |
| | | .components(separatedBy: .whitespacesAndNewlines) |
| | | .filter { !$0.isEmpty } |
| | | .joined(separator: " ") |
| | | .folding(options: [.caseInsensitive, .diacriticInsensitive], locale: .current) |
| | | .lowercased() |
| | | } |
| | | |
| | | private static func result(for document: QuickSearchDocument, tokens: [String]) -> QuickSearchResult? { |
| | | let fields = searchableFields(for: document) |
| | | var textScore: Double = 0 |
| | | var bestFieldRank = Int.max |
| | | var matchedTagName: String? |
| | | var noteSnippet: String? |
| | | |
| | | for token in tokens { |
| | | guard let tokenMatch = fields |
| | | .compactMap({ match(token: token, field: $0) }) |
| | | .max(by: { $0.score < $1.score }) |
| | | else { |
| | | return nil |
| | | } |
| | | textScore += tokenMatch.score |
| | | bestFieldRank = min(bestFieldRank, tokenMatch.fieldRank) |
| | | if tokenMatch.fieldKind == .tag { |
| | | matchedTagName = tokenMatch.originalText |
| | | } else if tokenMatch.fieldKind == .note { |
| | | noteSnippet = snippet(from: document.note, token: token) |
| | | } |
| | | } |
| | | |
| | | let finalScore = textScore + behaviorBoost(for: document) |
| | | return QuickSearchResult( |
| | | document: document, |
| | | finalScore: finalScore, |
| | | textScore: textScore, |
| | | bestFieldRank: bestFieldRank, |
| | | matchedTagName: matchedTagName, |
| | | noteSnippet: noteSnippet |
| | | ) |
| | | } |
| | | |
| | | private static func searchableFields(for document: QuickSearchDocument) -> [Field] { |
| | | let names = uniqueOrdered([document.app.name] + document.localizedNames) |
| | | let nameFields = names.map { |
| | | Field( |
| | | kind: .name, |
| | | text: $0, |
| | | normalized: normalizeField($0), |
| | | acronym: acronym(for: $0), |
| | | pinyinCandidates: pinyinCandidates(for: $0) |
| | | ) |
| | | } |
| | | let tagFields = document.tagNames.map { |
| | | Field( |
| | | kind: .tag, |
| | | text: $0, |
| | | normalized: normalizeField($0), |
| | | acronym: "", |
| | | pinyinCandidates: pinyinCandidates(for: $0) |
| | | ) |
| | | } |
| | | let noteFields = document.note.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? [] : [ |
| | | Field( |
| | | kind: .note, |
| | | text: document.note, |
| | | normalized: normalizeField(document.note), |
| | | acronym: "", |
| | | pinyinCandidates: pinyinCandidates(for: document.note) |
| | | ) |
| | | ] |
| | | let bundleFields = document.bundleIdentifier.isEmpty ? [] : [ |
| | | Field( |
| | | kind: .bundleIdentifier, |
| | | text: document.bundleIdentifier, |
| | | normalized: normalizeField(document.bundleIdentifier), |
| | | acronym: "", |
| | | pinyinCandidates: [] |
| | | ) |
| | | ] |
| | | let internalBundleNameFields = document.internalBundleNames.map { |
| | | Field( |
| | | kind: .internalBundleName, |
| | | text: $0, |
| | | normalized: normalizeField($0), |
| | | acronym: "", |
| | | pinyinCandidates: [] |
| | | ) |
| | | } |
| | | return nameFields + tagFields + noteFields + bundleFields + internalBundleNameFields |
| | | } |
| | | |
| | | private static func match(token: String, field: Field) -> TokenMatch? { |
| | | guard let candidate = bestMatchCandidate(token: token, field: field) else { return nil } |
| | | let positionBoost = candidate.0 == .exact ? 0 : max(0, 10 - min(candidate.1, 10)) |
| | | let score = field.kind.weight + candidate.0.weight + Double(positionBoost) |
| | | return TokenMatch( |
| | | score: score, |
| | | fieldRank: field.kind.rawValue, |
| | | fieldKind: field.kind, |
| | | originalText: field.text |
| | | ) |
| | | } |
| | | |
| | | private static func bestMatchCandidate(token: String, field: Field) -> (MatchKind, Int)? { |
| | | var candidates: [(MatchKind, Int)] = [] |
| | | |
| | | if let textCandidate = matchCandidate( |
| | | token: token, |
| | | normalized: field.normalized, |
| | | options: matchOptions(for: field.kind) |
| | | ) { |
| | | candidates.append(textCandidate) |
| | | } |
| | | if field.kind == .name && !field.acronym.isEmpty && field.acronym.hasPrefix(token) { |
| | | candidates.append((.acronym, 0)) |
| | | } |
| | | for pinyin in field.pinyinCandidates { |
| | | if let pinyinCandidate = matchPinyinCandidate(token: token, normalized: pinyin) { |
| | | candidates.append(pinyinCandidate) |
| | | } |
| | | } |
| | | |
| | | return candidates.max { lhs, rhs in |
| | | if lhs.0.weight != rhs.0.weight { return lhs.0.weight < rhs.0.weight } |
| | | return lhs.1 > rhs.1 |
| | | } |
| | | } |
| | | |
| | | private static func matchOptions(for fieldKind: FieldKind) -> MatchOptions { |
| | | switch fieldKind { |
| | | case .name: |
| | | return MatchOptions(allowSubstring: true, allowFuzzySubsequence: true) |
| | | case .tag: |
| | | return MatchOptions(allowSubstring: true, allowFuzzySubsequence: true) |
| | | case .note: |
| | | return MatchOptions(allowSubstring: true, allowFuzzySubsequence: false) |
| | | case .bundleIdentifier, .internalBundleName: |
| | | return MatchOptions(allowSubstring: false, allowFuzzySubsequence: false) |
| | | } |
| | | } |
| | | |
| | | private static func matchCandidate( |
| | | token: String, |
| | | normalized: String, |
| | | options: MatchOptions |
| | | ) -> (MatchKind, Int)? { |
| | | guard !normalized.isEmpty else { return nil } |
| | | if normalized == token { |
| | | return (.exact, 0) |
| | | } |
| | | if normalized.hasPrefix(token) { |
| | | return (.prefix, 0) |
| | | } |
| | | if options.allowSubstring, let range = normalized.range(of: token) { |
| | | return (.substring, normalized.distance(from: normalized.startIndex, to: range.lowerBound)) |
| | | } |
| | | if options.allowFuzzySubsequence, token.count >= 4, isSubsequence(token, of: normalized) { |
| | | return (.fuzzy, 10) |
| | | } |
| | | return nil |
| | | } |
| | | |
| | | private static func matchPinyinCandidate(token: String, normalized: String) -> (MatchKind, Int)? { |
| | | guard !normalized.isEmpty else { return nil } |
| | | if normalized == token { |
| | | return (.exact, 0) |
| | | } |
| | | if normalized.hasPrefix(token) { |
| | | return (.prefix, 0) |
| | | } |
| | | return nil |
| | | } |
| | | |
| | | private static func emptyQueryResults(documents: [QuickSearchDocument], limit: Int) -> [QuickSearchResult] { |
| | | var used = Set<URL>() |
| | | let recent = documents |
| | | .filter { $0.lastOpenedAt != nil } |
| | | .sorted { |
| | | if ($0.lastOpenedAt ?? .distantPast) != ($1.lastOpenedAt ?? .distantPast) { |
| | | return ($0.lastOpenedAt ?? .distantPast) > ($1.lastOpenedAt ?? .distantPast) |
| | | } |
| | | return $0.app.name.localizedStandardCompare($1.app.name) == .orderedAscending |
| | | } |
| | | |
| | | let frequent = documents |
| | | .filter { $0.openCount > 0 } |
| | | .sorted { |
| | | if $0.openCount != $1.openCount { return $0.openCount > $1.openCount } |
| | | return $0.app.name.localizedStandardCompare($1.app.name) == .orderedAscending |
| | | } |
| | | |
| | | let ordered = (recent + frequent).filter { used.insert($0.id).inserted } |
| | | return ordered.prefix(limit).map { |
| | | QuickSearchResult( |
| | | document: $0, |
| | | finalScore: behaviorBoost(for: $0), |
| | | textScore: 0, |
| | | bestFieldRank: Int.max, |
| | | matchedTagName: nil, |
| | | noteSnippet: nil |
| | | ) |
| | | } |
| | | } |
| | | |
| | | private static func rank(_ lhs: QuickSearchResult, _ rhs: QuickSearchResult) -> Bool { |
| | | if lhs.finalScore != rhs.finalScore { return lhs.finalScore > rhs.finalScore } |
| | | if lhs.textScore != rhs.textScore { return lhs.textScore > rhs.textScore } |
| | | if lhs.bestFieldRank != rhs.bestFieldRank { return lhs.bestFieldRank < rhs.bestFieldRank } |
| | | let leftDate = lhs.document.lastOpenedAt ?? .distantPast |
| | | let rightDate = rhs.document.lastOpenedAt ?? .distantPast |
| | | if leftDate != rightDate { return leftDate > rightDate } |
| | | if lhs.document.openCount != rhs.document.openCount { |
| | | return lhs.document.openCount > rhs.document.openCount |
| | | } |
| | | if lhs.app.name.count != rhs.app.name.count { |
| | | return lhs.app.name.count < rhs.app.name.count |
| | | } |
| | | return lhs.app.name.localizedStandardCompare(rhs.app.name) == .orderedAscending |
| | | } |
| | | |
| | | private static func behaviorBoost(for document: QuickSearchDocument) -> Double { |
| | | min(recentBoost(for: document.lastOpenedAt) + frequencyBoost(for: document.openCount), 20) |
| | | } |
| | | |
| | | private static func recentBoost(for date: Date?) -> Double { |
| | | guard let date else { return 0 } |
| | | let age = Date().timeIntervalSince(date) |
| | | if age <= 24 * 60 * 60 { return 15 } |
| | | if age <= 7 * 24 * 60 * 60 { return 10 } |
| | | if age <= 30 * 24 * 60 * 60 { return 5 } |
| | | return 2 |
| | | } |
| | | |
| | | private static func frequencyBoost(for openCount: Int) -> Double { |
| | | min(Double(openCount), 10) |
| | | } |
| | | |
| | | private static func normalizeField(_ value: String) -> String { |
| | | value |
| | | .folding(options: [.caseInsensitive, .diacriticInsensitive], locale: .current) |
| | | .lowercased() |
| | | } |
| | | |
| | | private static func pinyinCandidates(for value: String) -> [String] { |
| | | guard value.range(of: #"\p{Han}"#, options: .regularExpression) != nil else { return [] } |
| | | let mutable = NSMutableString(string: value) |
| | | CFStringTransform(mutable, nil, kCFStringTransformToLatin, false) |
| | | CFStringTransform(mutable, nil, kCFStringTransformStripCombiningMarks, false) |
| | | |
| | | let spaced = normalizeField(mutable as String) |
| | | .components(separatedBy: CharacterSet.alphanumerics.inverted) |
| | | .filter { !$0.isEmpty } |
| | | .joined(separator: " ") |
| | | guard !spaced.isEmpty else { return [] } |
| | | |
| | | let compact = spaced.replacingOccurrences(of: " ", with: "") |
| | | let initials = spaced |
| | | .split(separator: " ") |
| | | .compactMap(\.first) |
| | | .map(String.init) |
| | | .joined() |
| | | return uniqueOrdered([spaced, compact, initials].filter { !$0.isEmpty }) |
| | | } |
| | | |
| | | private static func isSubsequence(_ token: String, of value: String) -> Bool { |
| | | var searchStart = value.startIndex |
| | | for character in token { |
| | | guard let index = value[searchStart...].firstIndex(of: character) else { return false } |
| | | searchStart = value.index(after: index) |
| | | } |
| | | return true |
| | | } |
| | | |
| | | private static func acronym(for value: String) -> String { |
| | | var parts: [Character] = [] |
| | | var nextStartsWord = true |
| | | var previousWasLowercase = false |
| | | for character in value { |
| | | let current = String(character) |
| | | let isAlphanumeric = current.rangeOfCharacter(from: .alphanumerics) != nil |
| | | guard isAlphanumeric else { |
| | | nextStartsWord = true |
| | | previousWasLowercase = false |
| | | continue |
| | | } |
| | | |
| | | let isUppercase = current.rangeOfCharacter(from: .uppercaseLetters) != nil |
| | | let isLowercase = current.rangeOfCharacter(from: .lowercaseLetters) != nil |
| | | if nextStartsWord || (isUppercase && previousWasLowercase) { |
| | | parts.append(character) |
| | | } |
| | | nextStartsWord = false |
| | | previousWasLowercase = isLowercase |
| | | } |
| | | return normalizeField(String(parts)) |
| | | } |
| | | |
| | | private static func localizedNames(for app: AppInfo) -> [String] { |
| | | guard let bundle = Bundle(url: app.path) else { return [] } |
| | | let values = [ |
| | | bundle.localizedInfoDictionary?["CFBundleDisplayName"] as? String, |
| | | bundle.infoDictionary?["CFBundleDisplayName"] as? String, |
| | | FileManager.default.displayName(atPath: app.path.path).replacingOccurrences(of: ".app", with: "") |
| | | ] |
| | | return uniqueOrdered(values.compactMap { $0 }.compactMap { value in |
| | | let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) |
| | | return trimmed.isEmpty || trimmed == app.name ? nil : trimmed |
| | | }) |
| | | } |
| | | |
| | | private static func internalBundleNames(for app: AppInfo) -> [String] { |
| | | guard let bundle = Bundle(url: app.path) else { return [] } |
| | | let values = [ |
| | | bundle.localizedInfoDictionary?["CFBundleName"] as? String, |
| | | bundle.infoDictionary?["CFBundleName"] as? String |
| | | ] |
| | | return uniqueOrdered(values.compactMap { $0 }.compactMap { value in |
| | | let trimmed = value.trimmingCharacters(in: .whitespacesAndNewlines) |
| | | return trimmed.isEmpty || trimmed == app.name ? nil : trimmed |
| | | }) |
| | | } |
| | | |
| | | private static func snippet(from note: String, token: String) -> String { |
| | | let trimmed = note.trimmingCharacters(in: .whitespacesAndNewlines) |
| | | guard trimmed.count > 80 else { return trimmed } |
| | | return String(trimmed.prefix(77)) + "..." |
| | | } |
| | | |
| | | private static func uniqueOrdered(_ values: [String]) -> [String] { |
| | | var seen = Set<String>() |
| | | return values.filter { seen.insert(normalizeField($0)).inserted } |
| | | } |
| | | } |
| | | |
| | | // MARK: - Quick Search UI |
| | | |
| | | enum QuickSearchCommand { |
| | | case moveUp |
| | | case moveDown |
| | | case submit |
| | | case dismiss |
| | | } |
| | | |
| | | struct QuickSearchOverlayView: View { |
| | | @Binding var query: String |
| | | let results: [QuickSearchResult] |
| | | let selectedID: URL? |
| | | let focusToken: Int |
| | | let isLoading: Bool |
| | | let maxVisibleRows: Int |
| | | let errorMessage: String? |
| | | let onCommand: (QuickSearchCommand) -> Void |
| | | let onHover: (QuickSearchResult) -> Void |
| | | let onLaunch: (QuickSearchResult) -> Void |
| | | |
| | | private let panelWidth: CGFloat = 760 |
| | | private let rowHeight: CGFloat = 74 |
| | | |
| | | var body: some View { |
| | | VStack(alignment: .leading, spacing: 0) { |
| | | HStack(spacing: 18) { |
| | | Image(systemName: "magnifyingglass") |
| | | .font(.system(size: 29, weight: .regular)) |
| | | .foregroundStyle(Color.primary.opacity(0.48)) |
| | | .frame(width: 34) |
| | | |
| | | QuickSearchTextField( |
| | | text: $query, |
| | | placeholder: tr("quickSearch.placeholder"), |
| | | focusToken: focusToken, |
| | | onCommand: onCommand |
| | | ) |
| | | .frame(height: 44) |
| | | } |
| | | .padding(.horizontal, 28) |
| | | .padding(.top, 22) |
| | | .padding(.bottom, 18) |
| | | |
| | | Divider().opacity(0.35) |
| | | |
| | | if isLoading { |
| | | HStack(spacing: 10) { |
| | | ProgressView() |
| | | .controlSize(.small) |
| | | Text(tr("quickSearch.loading")) |
| | | .font(.system(size: 15, weight: .medium)) |
| | | .foregroundStyle(.secondary) |
| | | Spacer(minLength: 0) |
| | | } |
| | | .padding(.horizontal, 28) |
| | | .padding(.vertical, 24) |
| | | .frame(minHeight: 86) |
| | | } else if let errorMessage { |
| | | QuickSearchMessageRow( |
| | | systemImage: "exclamationmark.triangle.fill", |
| | | message: errorMessage, |
| | | tint: .orange |
| | | ) |
| | | } else if results.isEmpty { |
| | | QuickSearchMessageRow( |
| | | systemImage: query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? "keyboard" : "magnifyingglass", |
| | | message: query.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty |
| | | ? tr("quickSearch.emptyPrompt") |
| | | : tr("quickSearch.noResults"), |
| | | tint: .secondary |
| | | ) |
| | | } else { |
| | | ScrollViewReader { scrollProxy in |
| | | ScrollView(.vertical, showsIndicators: results.count > maxVisibleRows) { |
| | | LazyVStack(spacing: 2) { |
| | | ForEach(results) { result in |
| | | QuickSearchResultRow( |
| | | result: result, |
| | | isSelected: result.id == selectedID |
| | | ) |
| | | .frame(height: rowHeight) |
| | | .id(result.id) |
| | | .contentShape(Rectangle()) |
| | | .onHover { hovering in |
| | | if hovering { onHover(result) } |
| | | } |
| | | .onTapGesture { |
| | | onLaunch(result) |
| | | } |
| | | } |
| | | } |
| | | .padding(.horizontal, 10) |
| | | .padding(.vertical, 10) |
| | | } |
| | | .frame(height: CGFloat(min(results.count, maxVisibleRows)) * (rowHeight + 2) + 20) |
| | | .onChange(of: selectedID) { _, id in |
| | | guard let id else { return } |
| | | withAnimation(.easeOut(duration: 0.08)) { |
| | | scrollProxy.scrollTo(id, anchor: .center) |
| | | } |
| | | } |
| | | } |
| | | } |
| | | } |
| | | .frame(width: panelWidth) |
| | | .background( |
| | | RoundedRectangle(cornerRadius: 34, style: .continuous) |
| | | .fill(.regularMaterial) |
| | | .shadow(color: .black.opacity(0.18), radius: 36, y: 18) |
| | | ) |
| | | .overlay( |
| | | RoundedRectangle(cornerRadius: 34, style: .continuous) |
| | | .stroke(Color.primary.opacity(0.10), lineWidth: 1) |
| | | ) |
| | | .accessibilityElement(children: .contain) |
| | | .accessibilityLabel(tr("quickSearch.title")) |
| | | } |
| | | } |
| | | |
| | | private struct QuickSearchResultRow: View { |
| | | let result: QuickSearchResult |
| | | let isSelected: Bool |
| | | @Environment(\.colorScheme) private var colorScheme |
| | | |
| | | var body: some View { |
| | | HStack(spacing: 16) { |
| | | Image(nsImage: result.app.icon) |
| | | .resizable() |
| | | .frame(width: 46, height: 46) |
| | | .cornerRadius(10) |
| | | |
| | | VStack(alignment: .leading, spacing: 4) { |
| | | Text(result.app.name) |
| | | .font(.system(size: 20, weight: .semibold)) |
| | | .foregroundStyle(.primary) |
| | | .lineLimit(1) |
| | | .truncationMode(.tail) |
| | | |
| | | if let detailText { |
| | | Text(detailText) |
| | | .font(.system(size: 16, weight: .medium)) |
| | | .foregroundStyle(Color.primary.opacity(0.38)) |
| | | .lineLimit(1) |
| | | .truncationMode(.tail) |
| | | } |
| | | } |
| | | .layoutPriority(1) |
| | | |
| | | Spacer(minLength: 8) |
| | | |
| | | if let tagName = rightTagName { |
| | | Text(tagName) |
| | | .font(.system(size: 14, weight: .semibold)) |
| | | .foregroundStyle(Color.primary.opacity(0.46)) |
| | | .lineLimit(1) |
| | | .truncationMode(.tail) |
| | | .padding(.horizontal, 12) |
| | | .frame(height: 32) |
| | | .frame(maxWidth: 128) |
| | | .background( |
| | | Capsule(style: .continuous) |
| | | .fill(Color.primary.opacity(colorScheme == .dark ? 0.12 : 0.07)) |
| | | ) |
| | | } |
| | | } |
| | | .padding(.horizontal, 18) |
| | | .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) |
| | | .background( |
| | | RoundedRectangle(cornerRadius: 18, style: .continuous) |
| | | .fill(isSelected ? selectedFill : Color.clear) |
| | | ) |
| | | .accessibilityLabel(accessibilityText) |
| | | } |
| | | |
| | | private var selectedFill: Color { |
| | | colorScheme == .dark ? Color.white.opacity(0.14) : Color.black.opacity(0.075) |
| | | } |
| | | |
| | | private var detailText: String? { |
| | | if let note = result.noteSnippet, !note.isEmpty { |
| | | return note |
| | | } |
| | | let note = result.document.note.trimmingCharacters(in: .whitespacesAndNewlines) |
| | | if !note.isEmpty { |
| | | return note.count > 72 ? String(note.prefix(69)) + "..." : note |
| | | } |
| | | return nil |
| | | } |
| | | |
| | | private var rightTagName: String? { |
| | | let tag = result.matchedTagName ?? result.document.tagNames.first |
| | | guard let tag, !tag.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty else { return nil } |
| | | return tag |
| | | } |
| | | |
| | | private var accessibilityText: String { |
| | | [result.app.name, detailText].compactMap { $0 }.joined(separator: ", ") |
| | | } |
| | | } |
| | | |
| | | private struct QuickSearchMessageRow: View { |
| | | let systemImage: String |
| | | let message: String |
| | | let tint: Color |
| | | |
| | | var body: some View { |
| | | HStack(spacing: 10) { |
| | | Image(systemName: systemImage) |
| | | .font(.system(size: 20, weight: .regular)) |
| | | .foregroundStyle(tint) |
| | | .frame(width: 28) |
| | | Text(message) |
| | | .font(.system(size: 15, weight: .medium)) |
| | | .foregroundStyle(.secondary) |
| | | .lineLimit(2) |
| | | .fixedSize(horizontal: false, vertical: true) |
| | | Spacer(minLength: 0) |
| | | } |
| | | .padding(.horizontal, 28) |
| | | .padding(.vertical, 24) |
| | | .frame(minHeight: 86) |
| | | .accessibilityLabel(message) |
| | | } |
| | | } |
| | | |
| | | private struct QuickSearchTextField: NSViewRepresentable { |
| | | @Binding var text: String |
| | | let placeholder: String |
| | | let focusToken: Int |
| | | let onCommand: (QuickSearchCommand) -> Void |
| | | |
| | | func makeNSView(context: Context) -> QuickSearchNativeTextField { |
| | | let field = QuickSearchNativeTextField() |
| | | field.isBordered = false |
| | | field.isBezeled = false |
| | | field.drawsBackground = false |
| | | field.focusRingType = .none |
| | | field.font = NSFont.systemFont(ofSize: 28, weight: .regular) |
| | | field.placeholderString = placeholder |
| | | field.delegate = context.coordinator |
| | | field.onCommand = onCommand |
| | | field.setAccessibilityLabel(tr("quickSearch.inputAccessibility")) |
| | | context.coordinator.field = field |
| | | DispatchQueue.main.async { |
| | | field.window?.makeFirstResponder(field) |
| | | } |
| | | return field |
| | | } |
| | | |
| | | func updateNSView(_ field: QuickSearchNativeTextField, context: Context) { |
| | | if field.stringValue != text { |
| | | field.stringValue = text |
| | | } |
| | | field.placeholderString = placeholder |
| | | field.onCommand = onCommand |
| | | if context.coordinator.lastFocusToken != focusToken { |
| | | context.coordinator.lastFocusToken = focusToken |
| | | DispatchQueue.main.async { |
| | | field.window?.makeFirstResponder(field) |
| | | } |
| | | } |
| | | } |
| | | |
| | | func makeCoordinator() -> Coordinator { |
| | | Coordinator(text: $text) |
| | | } |
| | | |
| | | final class Coordinator: NSObject, NSTextFieldDelegate { |
| | | var text: Binding<String> |
| | | var lastFocusToken = 0 |
| | | weak var field: NSTextField? |
| | | |
| | | init(text: Binding<String>) { |
| | | self.text = text |
| | | } |
| | | |
| | | func controlTextDidChange(_ obj: Notification) { |
| | | guard let field = obj.object as? NSTextField else { return } |
| | | text.wrappedValue = field.stringValue |
| | | } |
| | | } |
| | | } |
| | | |
| | | private final class QuickSearchNativeTextField: NSTextField { |
| | | var onCommand: ((QuickSearchCommand) -> Void)? |
| | | |
| | | override func keyDown(with event: NSEvent) { |
| | | switch Int(event.keyCode) { |
| | | case kVK_UpArrow: |
| | | onCommand?(.moveUp) |
| | | case kVK_DownArrow: |
| | | onCommand?(.moveDown) |
| | | case kVK_Return: |
| | | onCommand?(.submit) |
| | | case kVK_Escape: |
| | | onCommand?(.dismiss) |
| | | default: |
| | | super.keyDown(with: event) |
| | | } |
| | | } |
| | | } |
| | | |
| | | struct QuickSearchBackdropClickView: NSViewRepresentable { |
| | | let onClick: () -> Void |
| | | |
| | | func makeNSView(context: Context) -> QuickSearchBackdropNSView { |
| | | let view = QuickSearchBackdropNSView() |
| | | view.onClick = onClick |
| | | return view |
| | | } |
| | | |
| | | func updateNSView(_ view: QuickSearchBackdropNSView, context: Context) { |
| | | view.onClick = onClick |
| | | } |
| | | } |
| | | |
| | | final class QuickSearchBackdropNSView: NSView { |
| | | var onClick: (() -> Void)? |
| | | |
| | | override func hitTest(_ point: NSPoint) -> NSView? { |
| | | bounds.contains(point) ? self : nil |
| | | } |
| | | |
| | | override func mouseDown(with event: NSEvent) { |
| | | onClick?() |
| | | } |
| | | } |
| | | |
| | | // MARK: - Hotkey Capture UI |
| | | |
| | | struct HotkeyCaptureView: NSViewRepresentable { |
| | | let isActive: Bool |
| | | let onCapture: (LauncherHotkey) -> Void |
| | | let onCancel: () -> Void |
| | | |
| | | func makeNSView(context: Context) -> HotkeyCaptureNSView { |
| | | let view = HotkeyCaptureNSView() |
| | | view.onCapture = onCapture |
| | | view.onCancel = onCancel |
| | | return view |
| | | } |
| | | |
| | | func updateNSView(_ view: HotkeyCaptureNSView, context: Context) { |
| | | view.onCapture = onCapture |
| | | view.onCancel = onCancel |
| | | view.setActive(isActive) |
| | | } |
| | | } |
| | | |
| | | final class HotkeyCaptureNSView: NSView { |
| | | var onCapture: ((LauncherHotkey) -> Void)? |
| | | var onCancel: (() -> Void)? |
| | | private var keyMonitor: Any? |
| | | |
| | | override var acceptsFirstResponder: Bool { true } |
| | | |
| | | deinit { |
| | | removeKeyMonitor() |
| | | } |
| | | |
| | | func setActive(_ active: Bool) { |
| | | if active { |
| | | installKeyMonitorIfNeeded() |
| | | DispatchQueue.main.async { [weak self] in |
| | | guard let self else { return } |
| | | self.window?.makeFirstResponder(self) |
| | | } |
| | | } else { |
| | | removeKeyMonitor() |
| | | } |
| | | } |
| | | |
| | | override func keyDown(with event: NSEvent) { |
| | | handle(event) |
| | | } |
| | | |
| | | private func installKeyMonitorIfNeeded() { |
| | | guard keyMonitor == nil else { return } |
| | | keyMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in |
| | | guard let self else { return event } |
| | | self.handle(event) |
| | | return nil |
| | | } |
| | | } |
| | | |
| | | private func removeKeyMonitor() { |
| | | if let keyMonitor { |
| | | NSEvent.removeMonitor(keyMonitor) |
| | | self.keyMonitor = nil |
| | | } |
| | | } |
| | | |
| | | private func handle(_ event: NSEvent) { |
| | | if event.keyCode == UInt16(kVK_Escape) { |
| | | onCancel?() |
| | | return |
| | | } |
| | | if let hotkey = LauncherHotkey.from(event: event) { |
| | | onCapture?(hotkey) |
| | | } else { |
| | | NSSound.beep() |
| | | } |
| | | } |
| | | } |
| | |
| | | # Apptag Changelog |
| | | |
| | | ## [7.5.1] — 2026-05-19 |
| | | |
| | | - 设置页新增独立「快捷键」页签,将主界面快捷键与 Quick Search 直接快捷键管理从「通用」页拆出 |
| | | - 移除设置页中与当前快捷键设置重复的静态按键帽提示,避免「关于」等页面继续展示旧快捷键视觉元素 |
| | | - 按 C 版规则收窄「快捷搜索」匹配:短查询不再放大无关结果,备注恢复拼音前缀匹配,Tag 支持原文非连续字符匹配,Bundle ID 与内部 Bundle 名仅支持精确/前缀命中 |
| | | - 版本号更新为 `7.5.1`,Build 更新为 `751` |
| | | |
| | | ## [7.5.0] — 2026-05-19 |
| | | |
| | | - 新增 Quick Search:在 App list 中按 `Space` 打开快速搜索浮层,支持按 App 名称、本地化名称、标签、备注和 Bundle ID 本地搜索 |
| | | - Quick Search 支持精确、前缀、子串、首字母缩写、非连续字符、中文直接匹配和本地中文拼音候选匹配 |
| | | - 空查询显示最近/常用 App 建议,支持键盘上下选择、回车启动、鼠标悬停选择和点击启动 |
| | | - 新增直接打开 Quick Search 的可配置全局快捷键,默认关闭,并支持快捷键录制、清除、恢复默认、重试注册和冲突状态提示 |
| | | - 快捷键冲突状态区分 `Active` / `Conflict` / `Disabled`,已知 macOS 默认快捷键显示专用说明,注册失败时保留旧快捷键可用 |
| | | - Quick Search 使用 macOS Spotlight 风格玻璃面板,支持外部点击关闭、Esc 两级退出、结果滚动和稳定顶部定位 |
| | | - 启动历史增加最近成功启动时间,用于 Quick Search 空查询建议排序;启动失败不更新历史 |
| | | - 所有 Quick Search 用户可见文案接入本地化资源 |
| | | - 版本号更新为 `7.5.0`,Build 更新为 `750` |
| | | |
| | | ## [7.3.5] — 2026-05-18 |
| | | |
| | | - 为菜单栏图标设置稳定 `NSStatusItem.autosaveName`,让 macOS 与 Thaw 更容易识别为同一个菜单栏项 |