Release v0.2
This commit is contained in:
@@ -69,6 +69,9 @@ struct CommentEditorView: View {
|
||||
.onChange(of: model.author) { _ in
|
||||
model.updateDraft()
|
||||
}
|
||||
.commitOnPlainReturn {
|
||||
model.commit()
|
||||
}
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
|
||||
@@ -3,154 +3,176 @@ import SwiftUI
|
||||
|
||||
@main
|
||||
struct IHatePDFsApp: App {
|
||||
@StateObject private var appState = AppState()
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
MainView()
|
||||
.environmentObject(appState)
|
||||
.onOpenURL { url in
|
||||
appState.loadDocument(from: url)
|
||||
}
|
||||
AppWindowRoot()
|
||||
}
|
||||
.windowStyle(.titleBar)
|
||||
.commands {
|
||||
CommandGroup(replacing: .newItem) {
|
||||
Button("Open...") {
|
||||
appState.openDocument()
|
||||
}
|
||||
.keyboardShortcut("o")
|
||||
|
||||
Button("Save") {
|
||||
appState.saveDocument()
|
||||
}
|
||||
.keyboardShortcut("s")
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Save As...") {
|
||||
appState.saveDocumentAs()
|
||||
}
|
||||
.keyboardShortcut("s", modifiers: [.command, .shift])
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Share...") {
|
||||
appState.shareDocument()
|
||||
}
|
||||
.keyboardShortcut("e", modifiers: [.command, .shift])
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Divider()
|
||||
|
||||
Button("Close PDF") {
|
||||
appState.closeDocument()
|
||||
}
|
||||
.keyboardShortcut("w")
|
||||
.disabled(appState.document == nil)
|
||||
}
|
||||
|
||||
CommandGroup(after: .textEditing) {
|
||||
Button("Find in PDF") {
|
||||
appState.showSearch()
|
||||
}
|
||||
.keyboardShortcut("f")
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Find Next") {
|
||||
appState.nextSearchResult()
|
||||
}
|
||||
.keyboardShortcut("g")
|
||||
.disabled(appState.searchResults.isEmpty)
|
||||
|
||||
Button("Find Previous") {
|
||||
appState.previousSearchResult()
|
||||
}
|
||||
.keyboardShortcut("g", modifiers: [.command, .shift])
|
||||
.disabled(appState.searchResults.isEmpty)
|
||||
}
|
||||
|
||||
CommandMenu("View") {
|
||||
Button("Toggle Page Sidebar") {
|
||||
appState.showLeftSidebar.toggle()
|
||||
}
|
||||
.keyboardShortcut("0", modifiers: [.command, .option])
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Toggle Comments Sidebar") {
|
||||
appState.showCommentsSidebar.toggle()
|
||||
}
|
||||
.keyboardShortcut("1", modifiers: [.command, .option])
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Divider()
|
||||
|
||||
Button("Zoom In") {
|
||||
appState.zoomIn()
|
||||
}
|
||||
.keyboardShortcut("+")
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Zoom Out") {
|
||||
appState.zoomOut()
|
||||
}
|
||||
.keyboardShortcut("-")
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Fit to Width") {
|
||||
appState.fitWidth()
|
||||
}
|
||||
.keyboardShortcut("9", modifiers: [.command])
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Fit to Page") {
|
||||
appState.fitPage()
|
||||
}
|
||||
.keyboardShortcut("8", modifiers: [.command])
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Two Pages Continuous") {
|
||||
appState.twoPageContinuous()
|
||||
}
|
||||
.keyboardShortcut("7", modifiers: [.command])
|
||||
.disabled(appState.document == nil)
|
||||
}
|
||||
|
||||
CommandMenu("Annotate") {
|
||||
Button("Highlight Selection") {
|
||||
appState.addHighlight()
|
||||
}
|
||||
.keyboardShortcut("h", modifiers: [.command, .shift])
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Underline Selection") {
|
||||
appState.addUnderline()
|
||||
}
|
||||
.keyboardShortcut("u", modifiers: [.command, .shift])
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Comment on Selection") {
|
||||
appState.addComment()
|
||||
}
|
||||
.keyboardShortcut("n", modifiers: [.command, .shift])
|
||||
.disabled(appState.document == nil)
|
||||
|
||||
Button("Add Free Text") {
|
||||
appState.addFreeText()
|
||||
}
|
||||
.keyboardShortcut("t", modifiers: [.command, .shift])
|
||||
.disabled(appState.document == nil)
|
||||
}
|
||||
|
||||
CommandGroup(after: .windowArrangement) {
|
||||
Button("Minimize") {
|
||||
appState.minimizeWindow()
|
||||
}
|
||||
.keyboardShortcut("m", modifiers: [.command])
|
||||
|
||||
Button("Toggle Full Screen") {
|
||||
appState.toggleFullScreen()
|
||||
}
|
||||
.keyboardShortcut("f", modifiers: [.command, .control])
|
||||
}
|
||||
AppCommands()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AppWindowRoot: View {
|
||||
@StateObject private var appState = AppState()
|
||||
|
||||
var body: some View {
|
||||
MainView()
|
||||
.environmentObject(appState)
|
||||
.focusedObject(appState)
|
||||
.onOpenURL { url in
|
||||
appState.loadDocument(from: url)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct AppCommands: Commands {
|
||||
@FocusedObject private var appState: AppState?
|
||||
|
||||
private var hasDocument: Bool {
|
||||
appState?.document != nil
|
||||
}
|
||||
|
||||
var body: some Commands {
|
||||
CommandGroup(replacing: .newItem) {
|
||||
Button("Open...") {
|
||||
appState?.openDocument()
|
||||
}
|
||||
.keyboardShortcut("o")
|
||||
.disabled(appState == nil)
|
||||
|
||||
Button("Save") {
|
||||
appState?.saveDocument()
|
||||
}
|
||||
.keyboardShortcut("s")
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Save As...") {
|
||||
appState?.saveDocumentAs()
|
||||
}
|
||||
.keyboardShortcut("s", modifiers: [.command, .shift])
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Share...") {
|
||||
appState?.shareDocument()
|
||||
}
|
||||
.keyboardShortcut("e", modifiers: [.command, .shift])
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Divider()
|
||||
|
||||
Button("Close PDF") {
|
||||
appState?.closeDocument()
|
||||
}
|
||||
.keyboardShortcut("w")
|
||||
.disabled(!hasDocument)
|
||||
}
|
||||
|
||||
CommandGroup(after: .textEditing) {
|
||||
Button("Find in PDF") {
|
||||
appState?.showSearch()
|
||||
}
|
||||
.keyboardShortcut("f")
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Find Next") {
|
||||
appState?.nextSearchResult()
|
||||
}
|
||||
.keyboardShortcut("g")
|
||||
.disabled(appState?.searchResults.isEmpty != false)
|
||||
|
||||
Button("Find Previous") {
|
||||
appState?.previousSearchResult()
|
||||
}
|
||||
.keyboardShortcut("g", modifiers: [.command, .shift])
|
||||
.disabled(appState?.searchResults.isEmpty != false)
|
||||
}
|
||||
|
||||
CommandMenu("View") {
|
||||
Button("Toggle Page Sidebar") {
|
||||
appState?.showLeftSidebar.toggle()
|
||||
}
|
||||
.keyboardShortcut("0", modifiers: [.command, .option])
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Toggle Comments Sidebar") {
|
||||
appState?.showCommentsSidebar.toggle()
|
||||
}
|
||||
.keyboardShortcut("1", modifiers: [.command, .option])
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Divider()
|
||||
|
||||
Button("Zoom In") {
|
||||
appState?.zoomIn()
|
||||
}
|
||||
.keyboardShortcut("+")
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Zoom Out") {
|
||||
appState?.zoomOut()
|
||||
}
|
||||
.keyboardShortcut("-")
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Fit to Width") {
|
||||
appState?.fitWidth()
|
||||
}
|
||||
.keyboardShortcut("9", modifiers: [.command])
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Fit to Page") {
|
||||
appState?.fitPage()
|
||||
}
|
||||
.keyboardShortcut("8", modifiers: [.command])
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Two Pages Continuous") {
|
||||
appState?.twoPageContinuous()
|
||||
}
|
||||
.keyboardShortcut("7", modifiers: [.command])
|
||||
.disabled(!hasDocument)
|
||||
}
|
||||
|
||||
CommandMenu("Annotate") {
|
||||
Button("Highlight Selection") {
|
||||
appState?.addHighlight()
|
||||
}
|
||||
.keyboardShortcut("h", modifiers: [.command, .shift])
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Underline Selection") {
|
||||
appState?.addUnderline()
|
||||
}
|
||||
.keyboardShortcut("u", modifiers: [.command, .shift])
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Comment on Selection") {
|
||||
appState?.addComment()
|
||||
}
|
||||
.keyboardShortcut("n", modifiers: [.command, .shift])
|
||||
.disabled(!hasDocument)
|
||||
|
||||
Button("Add Free Text") {
|
||||
appState?.addFreeText()
|
||||
}
|
||||
.keyboardShortcut("t", modifiers: [.command, .shift])
|
||||
.disabled(!hasDocument)
|
||||
}
|
||||
|
||||
CommandGroup(after: .windowArrangement) {
|
||||
Button("Minimize") {
|
||||
appState?.minimizeWindow()
|
||||
}
|
||||
.keyboardShortcut("m", modifiers: [.command])
|
||||
.disabled(appState == nil)
|
||||
|
||||
Button("Toggle Full Screen") {
|
||||
appState?.toggleFullScreen()
|
||||
}
|
||||
.keyboardShortcut("f", modifiers: [.command, .control])
|
||||
.disabled(appState == nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +129,15 @@ private struct ReaderToolbar: ToolbarContent {
|
||||
}
|
||||
.disabled(appState.document == nil)
|
||||
.help("Toggle Page Sidebar")
|
||||
|
||||
Button {
|
||||
appState.showCommentsSidebar.toggle()
|
||||
} label: {
|
||||
Label("Comments Sidebar", systemImage: "sidebar.right")
|
||||
}
|
||||
.disabled(appState.document == nil)
|
||||
.help(appState.showCommentsSidebar ? "Hide Comments Sidebar" : "Show Comments Sidebar")
|
||||
.accessibilityLabel("Toggle Comments Sidebar")
|
||||
}
|
||||
|
||||
ToolbarItemGroup(placement: .principal) {
|
||||
@@ -288,16 +297,5 @@ private struct ReaderToolbar: ToolbarContent {
|
||||
.disabled(appState.document == nil)
|
||||
.help("Share PDF")
|
||||
}
|
||||
|
||||
ToolbarItemGroup(placement: .primaryAction) {
|
||||
Button {
|
||||
appState.showCommentsSidebar.toggle()
|
||||
} label: {
|
||||
Label("Comments Sidebar", systemImage: "sidebar.right")
|
||||
}
|
||||
.disabled(appState.document == nil)
|
||||
.help(appState.showCommentsSidebar ? "Hide Comments Sidebar" : "Show Comments Sidebar")
|
||||
.accessibilityLabel("Toggle Comments Sidebar")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
45
Sources/IHatePDFs/ReturnKeyCommitMonitor.swift
Normal file
45
Sources/IHatePDFs/ReturnKeyCommitMonitor.swift
Normal file
@@ -0,0 +1,45 @@
|
||||
import AppKit
|
||||
import SwiftUI
|
||||
|
||||
extension View {
|
||||
func commitOnPlainReturn(_ action: @escaping () -> Void) -> some View {
|
||||
modifier(ReturnKeyCommitMonitor(action: action))
|
||||
}
|
||||
}
|
||||
|
||||
private struct ReturnKeyCommitMonitor: ViewModifier {
|
||||
let action: () -> Void
|
||||
@State private var monitor: Any?
|
||||
|
||||
func body(content: Content) -> some View {
|
||||
content
|
||||
.onAppear {
|
||||
installMonitor()
|
||||
}
|
||||
.onDisappear {
|
||||
removeMonitor()
|
||||
}
|
||||
}
|
||||
|
||||
private func installMonitor() {
|
||||
removeMonitor()
|
||||
monitor = NSEvent.addLocalMonitorForEvents(matching: .keyDown) { event in
|
||||
guard isPlainReturn(event) else { return event }
|
||||
action()
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func removeMonitor() {
|
||||
guard let monitor else { return }
|
||||
NSEvent.removeMonitor(monitor)
|
||||
self.monitor = nil
|
||||
}
|
||||
|
||||
private func isPlainReturn(_ event: NSEvent) -> Bool {
|
||||
guard event.keyCode == 36 || event.keyCode == 76 else { return false }
|
||||
|
||||
let multilineModifiers: NSEvent.ModifierFlags = [.shift, .option, .command, .control]
|
||||
return event.modifierFlags.intersection(multilineModifiers).isEmpty
|
||||
}
|
||||
}
|
||||
@@ -574,6 +574,11 @@ private struct SidebarReplyComposer: View {
|
||||
isFocused = true
|
||||
}
|
||||
}
|
||||
.commitOnPlainReturn {
|
||||
if !appState.sidebarReplyDraft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||
appState.commitSidebarReply()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user