import SwiftUI import AppKit extension Notification.Name { static let promptPanelDidShow = Notification.Name("PromptPanelDidShowNotification") } private final class KeyablePanel: NSPanel { override var canBecomeKey: Bool { true } override var canBecomeMain: Bool { false } } @MainActor final class PanelController: NSObject { static let shared = PanelController() private var panel: KeyablePanel? private var pasteMonitor: Any? let editController = EditWindowController() var isVisible: Bool { panel?.isVisible ?? false } func toggle() { isVisible ? hide() : show() } func show() { let panel = panel ?? makePanel() if UserDefaults.standard.string(forKey: "NSWindow Frame PromptPanel") == nil { positionAtRightEdge(panel) } NSApp.activate(ignoringOtherApps: true) panel.makeKeyAndOrderFront(nil) NSLog("Prompt: panel shown") NotificationCenter.default.post(name: .promptPanelDidShow, object: nil) } func hide() { panel?.orderOut(nil) NSApp.deactivate() NSLog("Prompt: panel hidden (app keeps running for the Shift-Shift hotkeys)") } private func makePanel() -> KeyablePanel { let panel = KeyablePanel( contentRect: NSRect(x: 0, y: 0, width: 360, height: 640), styleMask: [.titled, .closable, .resizable, .nonactivatingPanel, .fullSizeContentView], backing: .buffered, defer: false ) panel.titleVisibility = .hidden panel.titlebarAppearsTransparent = true panel.standardWindowButton(.miniaturizeButton)?.isHidden = true panel.standardWindowButton(.zoomButton)?.isHidden = true panel.level = .floating panel.isMovableByWindowBackground = true panel.hidesOnDeactivate = false panel.isReleasedWhenClosed = false panel.collectionBehavior = [.canJoinAllSpaces, .fullScreenAuxiliary] panel.contentView = NSHostingView(rootView: PanelView(store: Store.shared)) panel.setFrameAutosaveName("PromptPanel") self.panel = panel installPasteMonitor() return panel } private func positionAtRightEdge(_ panel: NSPanel) { guard let screen = NSScreen.main?.visibleFrame else { return } let size = NSSize(width: 360, height: screen.height - 24) panel.setContentSize(size) panel.setFrameOrigin(NSPoint(x: screen.maxX - size.width - 12, y: screen.minY + 12)) } // Intercept Cmd+V when the panel is key and the clipboard holds an image // (and no text, so normal text paste into the composer is unaffected). private func installPasteMonitor() { guard pasteMonitor == nil else { return } pasteMonitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in guard let self, let panel = self.panel, event.window == panel else { return event } let modifiers = event.modifierFlags.intersection(.deviceIndependentFlagsMask) if modifiers == [.command], event.charactersIgnoringModifiers == "v" { let pasteboard = NSPasteboard.general if pasteboard.string(forType: .string) == nil, let images = pasteboard.readObjects(forClasses: [NSImage.self], options: nil) as? [NSImage], let image = images.first { Task { @MainActor in Store.shared.addImage(image) } return nil } } return event } } } @MainActor final class EditWindowController: NSObject { private var window: NSWindow? func edit(itemID: UUID, text: String) { let view = EditView( text: text, onSave: { [weak self] newText in Store.shared.updateText(itemID, newText: newText) self?.window?.close() }, onCancel: { [weak self] in self?.window?.close() } ) let window = NSWindow( contentRect: NSRect(x: 0, y: 0, width: 480, height: 320), styleMask: [.titled, .closable, .resizable], backing: .buffered, defer: false ) window.title = "Edit Note" window.contentView = NSHostingView(rootView: view) window.isReleasedWhenClosed = false window.center() window.makeKeyAndOrderFront(nil) NSApp.activate(ignoringOtherApps: true) self.window = window } } private struct EditView: View { @State var text: String var onSave: (String) -> Void var onCancel: () -> Void var body: some View { VStack(spacing: 12) { TextEditor(text: $text) .font(.body) .padding(4) .background(Color.primary.opacity(0.05)) .cornerRadius(6) HStack { Spacer() Button("Cancel", action: onCancel) .keyboardShortcut(.cancelAction) Button("Save") { onSave(text) } .keyboardShortcut(.defaultAction) .keyboardShortcut("s", modifiers: .command) } } .padding(16) } }