diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index cf964fb..e8cd97c 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -55,25 +55,6 @@ struct ContentView: View { #if os(macOS) ZStack { PostListView(selectedCollection: nil, showAllPosts: model.account.isLoggedIn) - .toolbar { - ToolbarItemGroup(placement: .primaryAction) { - if let selectedPost = model.selectedPost { - ActivePostToolbarView(activePost: selectedPost) - .alert(isPresented: $model.isPresentingNetworkErrorAlert, content: { - Alert( - title: Text("Connection Error"), - message: Text(""" - There is no internet connection at the moment. \ - Please reconnect or try again later. - """), - dismissButton: .default(Text("OK"), action: { - model.isPresentingNetworkErrorAlert = false - }) - ) - }) - } - } - } if model.isProcessingRequest { ZStack { Color(NSColor.controlBackgroundColor).opacity(0.75) diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 8199e17..e20651a 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -92,7 +92,8 @@ struct PostListView: View { Alert( title: Text("Connection Error"), message: Text(""" - There is no internet connection at the moment. Please reconnect or try again later. + There is no internet connection at the moment. \ + Please reconnect or try again later. """), dismissButton: .default(Text("OK"), action: { model.isPresentingNetworkErrorAlert = false diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index 4bc9109..2b249d3 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -55,6 +55,7 @@ struct WriteFreely_MultiPlatformApp: App { .keyboardShortcut("r", modifiers: [.command]) } SidebarCommands() + PostCommands(model: model) CommandGroup(after: .help) { Button("Visit Support Forum") { #if os(macOS) diff --git a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj index 489cd38..d11a2f9 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj +++ b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ 173E19D1254318F600440F0F /* RemoteChangePromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173E19D0254318F600440F0F /* RemoteChangePromptView.swift */; }; 173E19E3254329CC00440F0F /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 173E19E2254329CC00440F0F /* PostTextEditingView.swift */; }; 17466626256C0D0600629997 /* MacEditorTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17466625256C0D0600629997 /* MacEditorTextView.swift */; }; + 17479F152583D8E40072B7FB /* PostEditorSharingPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17479F142583D8E40072B7FB /* PostEditorSharingPicker.swift */; }; 17480CA5251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17480CA4251272EE00EB7765 /* Bundle+AppVersion.swift */; }; 17480CA6251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17480CA4251272EE00EB7765 /* Bundle+AppVersion.swift */; }; 174D313224EC2831006CA9EE /* WriteFreelyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */; }; @@ -49,6 +50,7 @@ 1765F62A24E18EA200C9EBF0 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1765F62924E18EA200C9EBF0 /* SidebarView.swift */; }; 1765F62B24E18EA200C9EBF0 /* SidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1765F62924E18EA200C9EBF0 /* SidebarView.swift */; }; 17681E412519410E00D394AE /* UINavigationController+Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17681E402519410E00D394AE /* UINavigationController+Appearance.swift */; }; + 1780F6EF25895EDB00FE45FF /* PostCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1780F6EE25895EDB00FE45FF /* PostCommands.swift */; }; 17A5388824DDA31F00DEFF9A /* MacAccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */; }; 17A5388C24DDC83F00DEFF9A /* AccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388B24DDC83F00DEFF9A /* AccountModel.swift */; }; 17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388D24DDEC7400DEFF9A /* AccountView.swift */; }; @@ -126,6 +128,7 @@ 173E19D0254318F600440F0F /* RemoteChangePromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteChangePromptView.swift; sourceTree = ""; }; 173E19E2254329CC00440F0F /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = ""; }; 17466625256C0D0600629997 /* MacEditorTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacEditorTextView.swift; sourceTree = ""; }; + 17479F142583D8E40072B7FB /* PostEditorSharingPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorSharingPicker.swift; sourceTree = ""; }; 17480CA4251272EE00EB7765 /* Bundle+AppVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+AppVersion.swift"; sourceTree = ""; }; 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteFreelyModel.swift; sourceTree = ""; }; 1753F6AB24E431CC00309365 /* MacPreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacPreferencesView.swift; sourceTree = ""; }; @@ -141,6 +144,7 @@ 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; 1765F62924E18EA200C9EBF0 /* SidebarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SidebarView.swift; sourceTree = ""; }; 17681E402519410E00D394AE /* UINavigationController+Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationController+Appearance.swift"; sourceTree = ""; }; + 1780F6EE25895EDB00FE45FF /* PostCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCommands.swift; sourceTree = ""; }; 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacAccountView.swift; sourceTree = ""; }; 17A5388B24DDC83F00DEFF9A /* AccountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountModel.swift; sourceTree = ""; }; 17A5388D24DDEC7400DEFF9A /* AccountView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountView.swift; sourceTree = ""; }; @@ -311,6 +315,7 @@ 17A67CAC251A5D8D002F163D /* PostEditor */ = { isa = PBXGroup; children = ( + 17479F142583D8E40072B7FB /* PostEditorSharingPicker.swift */, 17A67CAE251A5DD7002F163D /* PostEditorView.swift */, 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */, 17466625256C0D0600629997 /* MacEditorTextView.swift */, @@ -322,6 +327,7 @@ isa = PBXGroup; children = ( 17BC617825715068003363CA /* ActivePostToolbarView.swift */, + 1780F6EE25895EDB00FE45FF /* PostCommands.swift */, ); path = Navigation; sourceTree = ""; @@ -755,6 +761,7 @@ 17D435E924E3128F0036B539 /* PreferencesModel.swift in Sources */, 17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, 17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, + 17479F152583D8E40072B7FB /* PostEditorSharingPicker.swift in Sources */, 17480CA6251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */, 17C42E662509237800072984 /* PostListFilteredView.swift in Sources */, 17120DAD24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, @@ -777,6 +784,7 @@ 1756DC0224FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */, 1756DBB424FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */, 17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */, + 1780F6EF25895EDB00FE45FF /* PostCommands.swift in Sources */, 170DFA35251BBC44001D82A0 /* PostEditorModel.swift in Sources */, 1756AE7524CB26FA00FD7257 /* PostCellView.swift in Sources */, 17A5388824DDA31F00DEFF9A /* MacAccountView.swift in Sources */, diff --git a/macOS/Navigation/ActivePostToolbarView.swift b/macOS/Navigation/ActivePostToolbarView.swift index 22b02d6..20334f4 100644 --- a/macOS/Navigation/ActivePostToolbarView.swift +++ b/macOS/Navigation/ActivePostToolbarView.swift @@ -3,19 +3,115 @@ import SwiftUI struct ActivePostToolbarView: View { @EnvironmentObject var model: WriteFreelyModel @ObservedObject var activePost: WFAPost + @State private var isPresentingSharingServicePicker: Bool = false + + @State private var selectedCollection: WFACollection? + + @FetchRequest( + entity: WFACollection.entity(), + sortDescriptors: [NSSortDescriptor(keyPath: \WFACollection.title, ascending: true)] + ) var collections: FetchedResults var body: some View { - HStack(spacing: 16) { + HStack { + if model.account.isLoggedIn && + activePost.status != PostStatus.local.rawValue && + !(activePost.wasDeletedFromServer || activePost.hasNewerRemoteCopy) { + Section(header: Text("Move To:")) { + Picker(selection: $selectedCollection, label: Text("Move To…"), content: { + Text("\(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") + .tag(nil as WFACollection?) + Divider() + ForEach(collections) { collection in + Text("\(collection.title)").tag(collection as WFACollection?) + } + }) + } + } PostEditorStatusToolbarView(post: activePost) - HStack(spacing: 4) { - Button(action: {}, label: { Image(systemName: "square.and.arrow.up") }) + .frame(minWidth: 50, alignment: .center) + .layoutPriority(1) + .padding(.horizontal) + if activePost.status == PostStatus.local.rawValue { + Menu(content: { + Label("Publish To:", systemImage: "paperplane") + Divider() + Button(action: { + if model.account.isLoggedIn { + withAnimation { + activePost.collectionAlias = nil + publishPost(activePost) + } + } else { + openSettingsWindow() + } + }, label: { + Text("\(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") + }) + ForEach(collections) { collection in + Button(action: { + if model.account.isLoggedIn { + withAnimation { + activePost.collectionAlias = collection.alias + publishPost(activePost) + } + } else { + openSettingsWindow() + } + }, label: { + Text("\(collection.title)") + }) + } + }, label: { + Label("Publish…", systemImage: "paperplane") + }) + .disabled(activePost.body.isEmpty) + .help("Publish the post to the web.\(model.account.isLoggedIn ? "" : " You must be logged in to do this.")") // swiftlint:disable:this line_length + } else { + HStack(spacing: 4) { + Button( + action: { + self.isPresentingSharingServicePicker = true + }, + label: { Image(systemName: "square.and.arrow.up") } + ) .disabled(activePost.status == PostStatus.local.rawValue) .help("Copy the post's URL to your Mac's pasteboard.") - Button(action: { publishPost(activePost) }, label: { Image(systemName: "paperplane") }) - .disabled(activePost.body.isEmpty || activePost.status == PostStatus.published.rawValue) - .help("Publish the post to the web.\(model.account.isLoggedIn ? "" : "You must be logged in to do this.")") // swiftlint:disable:this line_length + .popover(isPresented: $isPresentingSharingServicePicker) { + PostEditorSharingPicker( + isPresented: $isPresentingSharingServicePicker, + sharingItems: createPostUrl() + ) + .frame(width: .zero, height: .zero) + } + Button(action: { publishPost(activePost) }, label: { Image(systemName: "paperplane") }) + .disabled(activePost.body.isEmpty || activePost.status == PostStatus.published.rawValue) + .help("Publish the post to the web.\(model.account.isLoggedIn ? "" : " You must be logged in to do this.")") // swiftlint:disable:this line_length + } } } + .onAppear(perform: { + self.selectedCollection = collections.first { $0.alias == activePost.collectionAlias } + }) + .onChange(of: selectedCollection, perform: { [selectedCollection] newCollection in + if activePost.collectionAlias == newCollection?.alias { + return + } else { + withAnimation { + activePost.collectionAlias = newCollection?.alias + model.move(post: activePost, from: selectedCollection, to: newCollection) + } + } + }) + } + + private func createPostUrl() -> [Any] { + guard let postId = activePost.postId else { return [] } + guard let urlString = activePost.slug != nil ? + "\(model.account.server)/\((activePost.collectionAlias)!)/\((activePost.slug)!)" : + "\(model.account.server)/\((postId))" else { return [] } + guard let data = URL(string: urlString) else { return [] } + return [data as NSURL] } private func publishPost(_ post: WFAPost) { @@ -24,4 +120,9 @@ struct ActivePostToolbarView: View { model.publish(post: post) } } + + private func openSettingsWindow() { + guard let menuItem = NSApplication.shared.mainMenu?.item(at: 0)?.submenu?.item(at: 2) else { return } + NSApplication.shared.sendAction(menuItem.action!, to: menuItem.target, from: nil) + } } diff --git a/macOS/Navigation/PostCommands.swift b/macOS/Navigation/PostCommands.swift new file mode 100644 index 0000000..2b2d45a --- /dev/null +++ b/macOS/Navigation/PostCommands.swift @@ -0,0 +1,25 @@ +import SwiftUI + +struct PostCommands: Commands { + @ObservedObject var model: WriteFreelyModel + + var body: some Commands { + CommandMenu("Post") { + Group { + Button(action: sendPostUrlToPasteboard, label: { Text("Copy Link To Published Post") }) + .disabled(model.selectedPost?.status == PostStatus.local.rawValue) + } + .disabled(model.selectedPost == nil || !model.account.isLoggedIn) + } + } + + private func sendPostUrlToPasteboard() { + guard let activePost = model.selectedPost else { return } + guard let postId = activePost.postId else { return } + guard let urlString = activePost.slug != nil ? + "\(model.account.server)/\((activePost.collectionAlias)!)/\((activePost.slug)!)" : + "\(model.account.server)/\((postId))" else { return } + NSPasteboard.general.clearContents() + NSPasteboard.general.setString(urlString, forType: .string) + } +} diff --git a/macOS/PostEditor/PostEditorSharingPicker.swift b/macOS/PostEditor/PostEditorSharingPicker.swift new file mode 100644 index 0000000..02f7c96 --- /dev/null +++ b/macOS/PostEditor/PostEditorSharingPicker.swift @@ -0,0 +1,39 @@ +import SwiftUI + +struct PostEditorSharingPicker: NSViewRepresentable { + @Binding var isPresented: Bool + var sharingItems: [Any] = [] + + func makeNSView(context: Context) -> some NSView { + let view = NSView() + let picker = NSSharingServicePicker(items: sharingItems) + picker.delegate = context.coordinator + + DispatchQueue.main.async { + picker.show(relativeTo: .zero, of: view, preferredEdge: .minY) + } + return view + } + + func updateNSView(_ nsView: NSViewType, context: Context) { + } + + func makeCoordinator() -> Coordinator { + Coordinator(owner: self) + } + + class Coordinator: NSObject, NSSharingServicePickerDelegate { + let owner: PostEditorSharingPicker + init(owner: PostEditorSharingPicker) { + self.owner = owner + } + + func sharingServicePicker( + _ sharingServicePicker: NSSharingServicePicker, + didChoose service: NSSharingService? + ) { + sharingServicePicker.delegate = nil + self.owner.isPresented = false + } + } +}