diff --git a/Shared/Models/WriteFreelyModel.swift b/Shared/Models/WriteFreelyModel.swift index 617385f..942ff76 100644 --- a/Shared/Models/WriteFreelyModel.swift +++ b/Shared/Models/WriteFreelyModel.swift @@ -1,102 +1,114 @@ import Foundation import WriteFreely import Security import Network // MARK: - WriteFreelyModel final class WriteFreelyModel: ObservableObject { // MARK: - Models @Published var account = AccountModel() @Published var preferences = PreferencesModel() @Published var posts = PostListModel() @Published var editor = PostEditorModel() // MARK: - Error handling @Published var hasError: Bool = false var currentError: Error? { didSet { + if let localizedErrorDescription = currentError?.localizedDescription, + localizedErrorDescription == "The operation couldn’t be completed. (WriteFreely.WFError error -2.)", + !hasNetworkConnection { + #if DEBUG + print("⚠️ currentError is WriteFreely.WFError -2 and there is no network connection.") + #endif + currentError = NetworkError.noConnectionError + } #if DEBUG print("⚠️ currentError -> didSet \(currentError?.localizedDescription ?? "nil")") print(" > hasError was: \(self.hasError)") #endif DispatchQueue.main.async { #if DEBUG print(" > self.currentError != nil: \(self.currentError != nil)") #endif self.hasError = self.currentError != nil #if DEBUG print(" > hasError is now: \(self.hasError)") #endif } } } // MARK: - State @Published var isLoggingIn: Bool = false @Published var isProcessingRequest: Bool = false @Published var hasNetworkConnection: Bool = true @Published var selectedPost: WFAPost? @Published var selectedCollection: WFACollection? @Published var showAllPosts: Bool = true @Published var isPresentingDeleteAlert: Bool = false @Published var postToDelete: WFAPost? #if os(iOS) @Published var isPresentingSettingsView: Bool = false #endif static var shared = WriteFreelyModel() // swiftlint:disable line_length let helpURL = URL(string: "https://discuss.write.as/c/help/5")! let howToURL = URL(string: "https://discuss.write.as/t/using-the-writefreely-ios-app/1946")! let reviewURL = URL(string: "https://apps.apple.com/app/id1531530896?action=write-review")! let licensesURL = URL(string: "https://github.com/writeas/writefreely-swiftui-multiplatform/tree/main/Shared/Resources/Licenses")! // swiftlint:enable line_length internal var client: WFClient? private let defaults = UserDefaults.shared private let monitor = NWPathMonitor() private let queue = DispatchQueue(label: "NetworkMonitor") internal var postToUpdate: WFAPost? init() { DispatchQueue.main.async { self.preferences.appearance = self.defaults.integer(forKey: WFDefaults.colorSchemeIntegerKey) self.preferences.font = self.defaults.integer(forKey: WFDefaults.defaultFontIntegerKey) self.account.restoreState() + + // Set the appearance + self.preferences.updateAppearance(to: Appearance(rawValue: self.preferences.appearance) ?? .system) + if self.account.isLoggedIn { guard let serverURL = URL(string: self.account.server) else { self.currentError = AccountError.invalidServerURL return } do { guard let token = try self.fetchTokenFromKeychain( username: self.account.username, server: self.account.server ) else { self.currentError = KeychainError.couldNotFetchAccessToken return } self.account.login(WFUser(token: token, username: self.account.username)) self.client = WFClient(for: serverURL) self.client?.user = self.account.user self.fetchUserCollections() self.fetchUserPosts() } catch { self.currentError = KeychainError.couldNotFetchAccessToken return } } } monitor.pathUpdateHandler = { path in DispatchQueue.main.async { self.hasNetworkConnection = path.status == .satisfied } } monitor.start(queue: queue) } } diff --git a/Shared/Navigation/ContentView.swift b/Shared/Navigation/ContentView.swift index 3a088d3..f67cda9 100644 --- a/Shared/Navigation/ContentView.swift +++ b/Shared/Navigation/ContentView.swift @@ -1,94 +1,87 @@ import SwiftUI struct ContentView: View { @EnvironmentObject var model: WriteFreelyModel @EnvironmentObject var errorHandling: ErrorHandling var body: some View { NavigationView { #if os(macOS) CollectionListView() .withErrorHandling() .toolbar { Button( action: { NSApp.keyWindow?.contentViewController?.tryToPerform( #selector(NSSplitViewController.toggleSidebar(_:)), with: nil ) }, label: { Image(systemName: "sidebar.left") } ) .help("Toggle the sidebar's visibility.") Spacer() Button(action: { withAnimation { // Un-set the currently selected post self.model.selectedPost = nil } // Create the new-post managed object let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { DispatchQueue.main.async { // Load the new post in the editor self.model.selectedPost = managedPost } } }, label: { Image(systemName: "square.and.pencil") }) .help("Create a new local draft.") } .frame(width: 200) #else CollectionListView() .withErrorHandling() #endif #if os(macOS) ZStack { PostListView(selectedCollection: model.selectedCollection, showAllPosts: model.showAllPosts) .withErrorHandling() .frame(width: 300) if model.isProcessingRequest { ZStack { Color(NSColor.controlBackgroundColor).opacity(0.75) ProgressView() } } } #else PostListView(selectedCollection: model.selectedCollection, showAllPosts: model.showAllPosts) .withErrorHandling() #endif - #if os(macOS) - Text("Select a post, or create a new local draft.") - .foregroundColor(.secondary) - .frame(width: 500, height: 500) - #else - Text("Select a post, or create a new local draft.") - .foregroundColor(.secondary) - #endif + NoSelectedPostView(isConnected: $model.hasNetworkConnection) } .environmentObject(model) .onChange(of: model.hasError) { value in if value { if let error = model.currentError { self.errorHandling.handle(error: error) } else { self.errorHandling.handle(error: AppError.genericError()) } model.hasError = false } } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.standard.container.viewContext let model = WriteFreelyModel() return ContentView() .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/Shared/Navigation/NoSelectedPostView.swift b/Shared/Navigation/NoSelectedPostView.swift new file mode 100644 index 0000000..6c4939a --- /dev/null +++ b/Shared/Navigation/NoSelectedPostView.swift @@ -0,0 +1,29 @@ +import SwiftUI + +struct NoSelectedPostView: View { + @Binding var isConnected: Bool + + var body: some View { + VStack(spacing: 8) { + Text("Select a post, or create a new local draft.") + if !isConnected { + Label("You are not connected to the internet", systemImage: "wifi.exclamationmark") + .font(.caption) + .foregroundColor(.secondary) + } + } + .frame(width: 500, height: 500) + } +} + +struct NoSelectedPostViewIsDisconnected_Previews: PreviewProvider { + static var previews: some View { + NoSelectedPostView(isConnected: Binding.constant(true)) + } +} + +struct NoSelectedPostViewIsConnected_Previews: PreviewProvider { + static var previews: some View { + NoSelectedPostView(isConnected: Binding.constant(false)) + } +} diff --git a/Shared/PostList/PostListView.swift b/Shared/PostList/PostListView.swift index 93d0567..89fe9fa 100644 --- a/Shared/PostList/PostListView.swift +++ b/Shared/PostList/PostListView.swift @@ -1,190 +1,197 @@ import SwiftUI import Combine struct PostListView: View { @EnvironmentObject var model: WriteFreelyModel @EnvironmentObject var errorHandling: ErrorHandling @Environment(\.managedObjectContext) var managedObjectContext @State private var postCount: Int = 0 @State private var filteredListViewId: Int = 0 var selectedCollection: WFACollection? var showAllPosts: Bool #if os(iOS) private var frameHeight: CGFloat { var height: CGFloat = 50 let bottom = UIApplication.shared.windows.first?.safeAreaInsets.bottom ?? 0 height += bottom return height } #endif var body: some View { #if os(iOS) ZStack(alignment: .bottom) { PostListFilteredView( collection: selectedCollection, showAllPosts: showAllPosts, postCount: $postCount ) .id(self.filteredListViewId) .navigationTitle( showAllPosts ? "All Posts" : selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) .toolbar { ToolbarItem(placement: .primaryAction) { ZStack { // We have to add a Spacer as a sibling view to the Button in some kind of Stack so that any // a11y modifiers are applied as expected: bug report filed as FB8956392. if #unavailable(iOS 16) { Spacer() } Button(action: { let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { self.model.showAllPosts = false DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.model.selectedPost = managedPost } } }, label: { ZStack { Image("does.not.exist") .accessibilityHidden(true) Image(systemName: "square.and.pencil") .accessibilityHidden(true) .imageScale(.large) // These modifiers compensate for the resizing .padding(.vertical, 12) // done to the Image (and the button tap target) .padding(.leading, 12) // by the SwiftUI layout system from adding a .padding(.trailing, 8) // Spacer in this ZStack (FB8956392). } .frame(maxWidth: .infinity, maxHeight: .infinity) }) .accessibilityLabel(Text("Compose")) .accessibilityHint(Text("Compose a new local draft")) } } } VStack { HStack(spacing: 0) { Button(action: { model.isPresentingSettingsView = true }, label: { Image(systemName: "gear") .padding(.vertical, 4) .padding(.horizontal, 8) }) .accessibilityLabel(Text("Settings")) .accessibilityHint(Text("Open the Settings sheet")) .sheet( isPresented: $model.isPresentingSettingsView, onDismiss: { model.isPresentingSettingsView = false }, content: { SettingsView() .environmentObject(model) } ) Spacer() Text(postCount == 1 ? "\(postCount) post" : "\(postCount) posts") .foregroundColor(.secondary) Spacer() if model.isProcessingRequest { ProgressView() .padding(.vertical, 4) .padding(.horizontal, 8) } else { - Button(action: { - DispatchQueue.main.async { - model.fetchUserCollections() - model.fetchUserPosts() - } - }, label: { - Image(systemName: "arrow.clockwise") + if model.hasNetworkConnection { + Button(action: { + DispatchQueue.main.async { + model.fetchUserCollections() + model.fetchUserPosts() + } + }, label: { + Image(systemName: "arrow.clockwise") + .padding(.vertical, 4) + .padding(.horizontal, 8) + }) + .accessibilityLabel(Text("Refresh Posts")) + .accessibilityHint(Text("Fetch changes from the server")) + .disabled(!model.account.isLoggedIn) + } else { + Image(systemName: "wifi.exclamationmark") .padding(.vertical, 4) .padding(.horizontal, 8) - }) - .accessibilityLabel(Text("Refresh Posts")) - .accessibilityHint(Text("Fetch changes from the server")) - .disabled(!model.account.isLoggedIn) + .foregroundColor(.secondary) + } } } .padding(.top, 8) .padding(.horizontal, 8) Spacer() } .frame(height: frameHeight) .background(Color(UIColor.systemGray5)) .overlay(Divider(), alignment: .top) .onReceive(NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)) { _ in // We use this to invalidate and refresh the view, so that new posts created outside of the app (e.g., // in the action extension) show up. withAnimation { self.filteredListViewId += 1 } } } .ignoresSafeArea(.all, edges: .bottom) .onAppear { model.selectedCollection = selectedCollection model.showAllPosts = showAllPosts } .onChange(of: model.hasError) { value in if value { if let error = model.currentError { self.errorHandling.handle(error: error) } else { self.errorHandling.handle(error: AppError.genericError()) } model.hasError = false } } #else PostListFilteredView( collection: selectedCollection, showAllPosts: showAllPosts, postCount: $postCount ) .toolbar { ToolbarItemGroup(placement: .primaryAction) { if model.selectedPost != nil { ActivePostToolbarView(activePost: model.selectedPost!) } } } .navigationTitle( showAllPosts ? "All Posts" : selectedCollection?.title ?? ( model.account.server == "https://write.as" ? "Anonymous" : "Drafts" ) ) .onAppear { model.selectedCollection = selectedCollection model.showAllPosts = showAllPosts } .onChange(of: model.hasError) { value in if value { if let error = model.currentError { self.errorHandling.handle(error: error) } else { self.errorHandling.handle(error: AppError.genericError()) } model.hasError = false } } #endif } } struct PostListView_Previews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.standard.container.viewContext let model = WriteFreelyModel() return PostListView(showAllPosts: true) .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/Shared/Preferences/PreferencesModel.swift b/Shared/Preferences/PreferencesModel.swift index 0fde8a6..a63748a 100644 --- a/Shared/Preferences/PreferencesModel.swift +++ b/Shared/Preferences/PreferencesModel.swift @@ -1,67 +1,54 @@ import SwiftUI +enum Appearance: Int { + case system = 0 + case light = 1 + case dark = 2 +} + class PreferencesModel: ObservableObject { private let defaults = UserDefaults.shared - /* We're stuck dropping into AppKit/UIKit to set light/dark schemes for now, - * because setting the .preferredColorScheme modifier on views in SwiftUI is - * currently unreliable. - * - * Feedback submitted to Apple: - * - * FB8382883: "On macOS 11β4, preferredColorScheme modifier does not respect .light ColorScheme" - * FB8383053: "On iOS 14β4/macOS 11β4, it is not possible to unset preferredColorScheme after setting - * it to either .light or .dark" - */ - - #if os(iOS) - @available(iOSApplicationExtension, unavailable) - var window: UIWindow? { - guard let scene = UIApplication.shared.connectedScenes.first, - let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, - let window = windowSceneDelegate.window else { - return nil + @Published var selectedColorScheme: ColorScheme? + @Published var appearance: Int = 0 + @Published var font: Int = 0 { + didSet { + defaults.set(font, forKey: WFDefaults.defaultFontIntegerKey) } - return window } - #endif - - @available(iOSApplicationExtension, unavailable) - @Published var selectedColorScheme: ColorScheme? @available(iOSApplicationExtension, unavailable) - @Published var appearance: Int = 0 { - didSet { - switch appearance { - case 1: -// selectedColorScheme = .light - #if os(macOS) - NSApp.appearance = NSAppearance(named: .aqua) - #else - window?.overrideUserInterfaceStyle = .light - #endif - case 2: -// selectedColorScheme = .dark - #if os(macOS) - NSApp.appearance = NSAppearance(named: .darkAqua) - #else - window?.overrideUserInterfaceStyle = .dark - #endif - default: -// selectedColorScheme = .none - #if os(macOS) - NSApp.appearance = nil - #else - window?.overrideUserInterfaceStyle = .unspecified - #endif + func updateAppearance(to appearance: Appearance) { + #if os(iOS) + var window: UIWindow? { + guard let scene = UIApplication.shared.connectedScenes.first, + let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, + let window = windowSceneDelegate.window else { + return nil } - - defaults.set(appearance, forKey: WFDefaults.colorSchemeIntegerKey) + return window } - } - @Published var font: Int = 0 { - didSet { - defaults.set(font, forKey: WFDefaults.defaultFontIntegerKey) + #endif + + switch appearance { + case .light: + #if os(macOS) + NSApp.appearance = NSAppearance(named: .aqua) + #else + window?.overrideUserInterfaceStyle = .light + #endif + case .dark: + #if os(macOS) + NSApp.appearance = NSAppearance(named: .darkAqua) + #else + window?.overrideUserInterfaceStyle = .dark + #endif + default: + #if os(macOS) + NSApp.appearance = nil + #else + window?.overrideUserInterfaceStyle = .unspecified + #endif } } } diff --git a/Shared/Preferences/PreferencesView.swift b/Shared/Preferences/PreferencesView.swift index 1522450..902d809 100644 --- a/Shared/Preferences/PreferencesView.swift +++ b/Shared/Preferences/PreferencesView.swift @@ -1,56 +1,82 @@ import SwiftUI struct PreferencesView: View { @ObservedObject var preferences: PreferencesModel + /* We're stuck dropping into AppKit/UIKit to set light/dark schemes for now, + * because setting the .preferredColorScheme modifier on views in SwiftUI is + * currently unreliable. + * + * Feedback submitted to Apple: + * + * FB8382883: "On macOS 11β4, preferredColorScheme modifier does not respect .light ColorScheme" + * FB8383053: "On iOS 14β4/macOS 11β4, it is not possible to unset preferredColorScheme after setting + * it to either .light or .dark" + */ + + #if os(iOS) + var window: UIWindow? { + guard let scene = UIApplication.shared.connectedScenes.first, + let windowSceneDelegate = scene.delegate as? UIWindowSceneDelegate, + let window = windowSceneDelegate.window else { + return nil + } + return window + } + #endif + var body: some View { VStack { VStack { Text("Choose the preferred appearance for the app.") .font(.caption) .foregroundColor(.secondary) Picker(selection: $preferences.appearance, label: Text("Appearance")) { Text("System").tag(0) Text("Light").tag(1) Text("Dark").tag(2) } .pickerStyle(SegmentedPickerStyle()) } .padding(.bottom) VStack { Text("Choose the default font for new posts.") .font(.caption) .foregroundColor(.secondary) Picker(selection: $preferences.font, label: Text("Default Font")) { Text("Serif").tag(0) Text("Sans-Serif").tag(1) Text("Monospace").tag(2) } .pickerStyle(SegmentedPickerStyle()) .padding(.bottom) switch preferences.font { case 1: Text("Sample Text") .frame(width: 240, height: 50, alignment: .center) .font(.custom("OpenSans-Regular", size: 20)) case 2: Text("Sample Text") .frame(width: 240, height: 50, alignment: .center) .font(.custom("Hack-Regular", size: 20)) default: Text("Sample Text") .frame(width: 240, height: 50, alignment: .center) .font(.custom("Lora", size: 20)) } } .padding(.bottom) } + .onChange(of: preferences.appearance) { value in + preferences.updateAppearance(to: Appearance(rawValue: value) ?? .system) + UserDefaults.shared.set(value, forKey: WFDefaults.colorSchemeIntegerKey) + } } } struct SwiftUIView_Previews: PreviewProvider { static var previews: some View { PreferencesView(preferences: PreferencesModel()) } } diff --git a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj index 91713ff..a646875 100644 --- a/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj +++ b/WriteFreely-MultiPlatform.xcodeproj/project.pbxproj @@ -1,1542 +1,1548 @@ // !$*UTF8*$! { archiveVersion = 1; classes = { }; objectVersion = 54; objects = { /* Begin PBXBuildFile section */ 17027E25286741B90062EB29 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17027E24286741B80062EB29 /* Logging.swift */; }; 17027E26286741B90062EB29 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17027E24286741B80062EB29 /* Logging.swift */; }; 17027E27286757650062EB29 /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17027E24286741B80062EB29 /* Logging.swift */; }; 170DFA34251BBC44001D82A0 /* PostEditorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170DFA33251BBC44001D82A0 /* PostEditorModel.swift */; }; 170DFA35251BBC44001D82A0 /* PostEditorModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 170DFA33251BBC44001D82A0 /* PostEditorModel.swift */; }; 17120DA124E19839002B9F6C /* AccountView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388D24DDEC7400DEFF9A /* AccountView.swift */; }; 17120DA224E1985C002B9F6C /* AccountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5388B24DDC83F00DEFF9A /* AccountModel.swift */; }; 17120DA324E19A42002B9F6C /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5389124DDED0000DEFF9A /* PreferencesView.swift */; }; 17120DA724E19D11002B9F6C /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DA424E19CBF002B9F6C /* SettingsView.swift */; }; 17120DA924E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DA824E1B2F5002B9F6C /* AccountLogoutView.swift */; }; 17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DA824E1B2F5002B9F6C /* AccountLogoutView.swift */; }; 17120DAC24E1B99F002B9F6C /* AccountLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */; }; 17120DAD24E1B99F002B9F6C /* AccountLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */; }; 17120DB224E1E19C002B9F6C /* SettingsHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17120DB124E1E19C002B9F6C /* SettingsHeaderView.swift */; }; 171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionListView.swift */; }; 171BFDFB24D4AF8300888236 /* CollectionListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171BFDF924D4AF8300888236 /* CollectionListView.swift */; }; 171DC677272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */; }; 171DC678272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */; }; 1727526628099802003D0A6A /* ErrorConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1727526528099802003D0A6A /* ErrorConstants.swift */; }; 1727526728099802003D0A6A /* ErrorConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1727526528099802003D0A6A /* ErrorConstants.swift */; }; 1727526828099802003D0A6A /* ErrorConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1727526528099802003D0A6A /* ErrorConstants.swift */; }; 1727526A2809991A003D0A6A /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172752692809991A003D0A6A /* ErrorHandling.swift */; }; 1727526B2809991A003D0A6A /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172752692809991A003D0A6A /* ErrorHandling.swift */; }; 1727526C2809991A003D0A6A /* ErrorHandling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172752692809991A003D0A6A /* ErrorHandling.swift */; }; 172C492E2593981900E20ADF /* MacUpdatesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172C492D2593981900E20ADF /* MacUpdatesView.swift */; }; 172E10012735B83E00061372 /* UniformTypeIdentifiers.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */; platformFilter = maccatalyst; }; 172E10042735B83E00061372 /* Media.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 172E10032735B83E00061372 /* Media.xcassets */; }; 172E10062735B83E00061372 /* ActionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10052735B83E00061372 /* ActionViewController.swift */; }; 172E100D2735B83E00061372 /* ActionExtension-iOS.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 172E10132735BB6200061372 /* Action.js in Resources */ = {isa = PBXBuildFile; fileRef = 172E10122735BB6200061372 /* Action.js */; }; 172E10152735C2BD00061372 /* UIHostingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10142735C2BD00061372 /* UIHostingView.swift */; }; 172E10172735C2DF00061372 /* EnvironmentValues+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */; }; 172E10192735C3DB00061372 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 172E10182735C3DB00061372 /* ContentView.swift */; }; 172E101C2735C57400061372 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; }; 172E101D2735C5AB00061372 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; }; 172E101E2735C62F00061372 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; 172E101F2735C64600061372 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; }; 172E10202735C64600061372 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; 172E10212735C64600061372 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; 172E10222735C64600061372 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; }; 172E10232735C6FF00061372 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; 172E10242735C72500061372 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; 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 */; }; 174D313324EC2831006CA9EE /* WriteFreelyModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */; }; 1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1753F6AB24E431CC00309365 /* MacPreferencesView.swift */; }; 1756AE6E24CB255B00FD7257 /* PostListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostListModel.swift */; }; 1756AE6F24CB255B00FD7257 /* PostListModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE6D24CB255B00FD7257 /* PostListModel.swift */; }; 1756AE7424CB26FA00FD7257 /* PostCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7324CB26FA00FD7257 /* PostCellView.swift */; }; 1756AE7524CB26FA00FD7257 /* PostCellView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7324CB26FA00FD7257 /* PostCellView.swift */; }; 1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */; }; 1756AE7A24CB65DF00FD7257 /* PostListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7924CB65DF00FD7257 /* PostListView.swift */; }; 1756AE7B24CB65DF00FD7257 /* PostListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE7924CB65DF00FD7257 /* PostListView.swift */; }; 1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756AE8024CB844500FD7257 /* View+Keyboard.swift */; }; 1756DBB324FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */; }; 1756DBB424FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */; }; 1756DBB724FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; }; 1756DBB824FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */; }; 1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; }; 1756DBBB24FED45500207AB8 /* LocalStorageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */; }; 1756DC0124FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; 1756DC0224FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */; }; 1756DC0324FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; 1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */; }; 1780F6EF25895EDB00FE45FF /* PostCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1780F6EE25895EDB00FE45FF /* PostCommands.swift */; }; 17836C14273EFB870047AF61 /* UserDefaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */; }; 17836C15273F0FBB0047AF61 /* Hack-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */; }; 17836C16273F0FBB0047AF61 /* LoraGX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */; }; 17836C17273F0FBB0047AF61 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */; }; 1784D2ED27946D880033E72E /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 1784D2EC27946D880033E72E /* WriteFreely */; }; 1784D2EF27946D9A0033E72E /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 1784D2EE27946D9A0033E72E /* WriteFreely */; }; 1784D2F127946DA10033E72E /* WriteFreely in Frameworks */ = {isa = PBXBuildFile; productRef = 1784D2F027946DA10033E72E /* WriteFreely */; }; 17A4FEED25927E730037E96B /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A4FEEC25927E730037E96B /* AppDelegate.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 */; }; 17A5389324DDED0000DEFF9A /* PreferencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A5389124DDED0000DEFF9A /* PreferencesView.swift */; }; 17A67CAF251A5DD7002F163D /* PostEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17A67CAE251A5DD7002F163D /* PostEditorView.swift */; }; 17B37C4B25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */; }; 17B37C4C25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */; }; 17B37C5625C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */; }; 17B37C5725C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */; }; 17B37C5D25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */; }; 17B37C5E25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */; }; 17B3E965250FAA9000EE9748 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 17B3E964250FAA9000EE9748 /* LaunchScreen.storyboard */; }; 17B5103B2515448D00E9631F /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 17B5103A2515448D00E9631F /* Credits.rtf */; }; 17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; }; 17B996D92502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */; }; 17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; }; 17B996DB2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */; }; 17BC618A25715318003363CA /* ActivePostToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17BC617825715068003363CA /* ActivePostToolbarView.swift */; }; 17C42E622507D8E600072984 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; 17C42E632507D8E600072984 /* PostStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E612507D8E600072984 /* PostStatus.swift */; }; 17C42E652509237800072984 /* PostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E642509237800072984 /* PostListFilteredView.swift */; }; 17C42E662509237800072984 /* PostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E642509237800072984 /* PostListFilteredView.swift */; }; 17C42E70250AA12300072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; 17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */; }; 17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; 17D435E924E3128F0036B539 /* PreferencesModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D435E724E3128F0036B539 /* PreferencesModel.swift */; }; 17D4926527947B4D0035BD7E /* Sparkle in Frameworks */ = {isa = PBXBuildFile; productRef = 17D4926427947B4D0035BD7E /* Sparkle */; }; 17D4926727947D780035BD7E /* MacUpdatesViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17D4926627947D780035BD7E /* MacUpdatesViewModel.swift */; }; 17D4F36C2514EE2F00517CE6 /* LoraGX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */; }; 17D4F36D2514EE2F00517CE6 /* LoraGX.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */; }; 17D4F39E2514F0E500517CE6 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */; }; 17D4F39F2514F0E500517CE6 /* OpenSans-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */; }; 17D4F3A52514F1E900517CE6 /* Hack-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */; }; 17D4F3A62514F1E900517CE6 /* Hack-Regular.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */; }; 17DF329D24C87D3500BCE2E3 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */; }; 17DF32A824C87D3500BCE2E3 /* Tests_macOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32A724C87D3500BCE2E3 /* Tests_macOS.swift */; }; 17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */; }; 17DF32AB24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */; }; 17DF32AC24C87D3500BCE2E3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328224C87D3300BCE2E3 /* ContentView.swift */; }; 17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF328224C87D3300BCE2E3 /* ContentView.swift */; }; 17DF32AE24C87D3500BCE2E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17DF328324C87D3500BCE2E3 /* Assets.xcassets */; }; 17DF32AF24C87D3500BCE2E3 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 17DF328324C87D3500BCE2E3 /* Assets.xcassets */; }; 17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */; }; 17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */; }; 17DFDE87251D309400A25F31 /* Hack-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE84251D309400A25F31 /* Hack-License.txt */; }; 17DFDE88251D309400A25F31 /* Hack-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE84251D309400A25F31 /* Hack-License.txt */; }; 17DFDE89251D309400A25F31 /* Lora-Cyrillic-OFL.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */; }; 17DFDE8A251D309400A25F31 /* Lora-Cyrillic-OFL.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */; }; 17DFDE8B251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; }; 17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */ = {isa = PBXBuildFile; fileRef = 17DFDE86251D309400A25F31 /* OpenSans-License.txt */; }; 17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */; }; + 37095AE02AA4A0E700C9C5F8 /* NoSelectedPostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37113EF82A98C10A00B36B98 /* NoSelectedPostView.swift */; }; + 37113EF92A98C10A00B36B98 /* NoSelectedPostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37113EF82A98C10A00B36B98 /* NoSelectedPostView.swift */; }; 375A67E828FC555C007A1AC0 /* MultilineTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 375A67E728FC555C007A1AC0 /* MultilineTextView.swift */; }; 3779389729EC0C880032D6C1 /* HelpCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3779389629EC0C880032D6C1 /* HelpCommands.swift */; }; 37F749D129B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */; }; 37F749D229B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ 172E100B2735B83E00061372 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 17DF327C24C87D3300BCE2E3 /* Project object */; proxyType = 1; remoteGlobalIDString = 172E0FFE2735B83E00061372; remoteInfo = "ActionExtension-iOS"; }; 17DF329924C87D3500BCE2E3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 17DF327C24C87D3300BCE2E3 /* Project object */; proxyType = 1; remoteGlobalIDString = 17DF328724C87D3500BCE2E3; remoteInfo = "WriteFreely-MultiPlatform (iOS)"; }; 17DF32A424C87D3500BCE2E3 /* PBXContainerItemProxy */ = { isa = PBXContainerItemProxy; containerPortal = 17DF327C24C87D3300BCE2E3 /* Project object */; proxyType = 1; remoteGlobalIDString = 17DF328F24C87D3500BCE2E3; remoteInfo = "WriteFreely-MultiPlatform (macOS)"; }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ 172E100E2735B83E00061372 /* Embed App Extensions */ = { isa = PBXCopyFilesBuildPhase; buildActionMask = 2147483647; dstPath = ""; dstSubfolderSpec = 13; files = ( 172E100D2735B83E00061372 /* ActionExtension-iOS.appex in Embed App Extensions */, ); name = "Embed App Extensions"; runOnlyForDeploymentPostprocessing = 0; }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ 17027E24286741B80062EB29 /* Logging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = ""; }; 1709ADDF251B9A110053AF79 /* EditorLaunchingPolicy.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = EditorLaunchingPolicy.md; sourceTree = ""; }; 170DFA33251BBC44001D82A0 /* PostEditorModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorModel.swift; sourceTree = ""; }; 17120DA424E19CBF002B9F6C /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; 17120DA824E1B2F5002B9F6C /* AccountLogoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLogoutView.swift; sourceTree = ""; }; 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountLoginView.swift; sourceTree = ""; }; 17120DB124E1E19C002B9F6C /* SettingsHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsHeaderView.swift; sourceTree = ""; }; 171BFDF924D4AF8300888236 /* CollectionListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionListView.swift; sourceTree = ""; }; 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Extensions.swift"; sourceTree = ""; }; 1727526528099802003D0A6A /* ErrorConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorConstants.swift; sourceTree = ""; }; 172752692809991A003D0A6A /* ErrorHandling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorHandling.swift; sourceTree = ""; }; 172C492D2593981900E20ADF /* MacUpdatesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacUpdatesView.swift; sourceTree = ""; }; 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "ActionExtension-iOS.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; 172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UniformTypeIdentifiers.framework; path = System/Library/Frameworks/UniformTypeIdentifiers.framework; sourceTree = SDKROOT; }; 172E10032735B83E00061372 /* Media.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Media.xcassets; sourceTree = ""; }; 172E10052735B83E00061372 /* ActionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionViewController.swift; sourceTree = ""; }; 172E100A2735B83E00061372 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; usesTabs = 1; }; 172E10122735BB6200061372 /* Action.js */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.javascript; path = Action.js; sourceTree = ""; }; 172E10142735C2BD00061372 /* UIHostingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIHostingView.swift; sourceTree = ""; }; 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EnvironmentValues+Extensions.swift"; sourceTree = ""; }; 172E10182735C3DB00061372 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 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 = ""; }; 1756AE6D24CB255B00FD7257 /* PostListModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListModel.swift; sourceTree = ""; }; 1756AE7324CB26FA00FD7257 /* PostCellView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCellView.swift; sourceTree = ""; }; 1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorView.swift; sourceTree = ""; }; 1756AE7924CB65DF00FD7257 /* PostListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListView.swift; sourceTree = ""; }; 1756AE8024CB844500FD7257 /* View+Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View+Keyboard.swift"; sourceTree = ""; }; 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorStatusToolbarView.swift; sourceTree = ""; }; 1756DBB624FED3A400207AB8 /* LocalStorageModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = LocalStorageModel.xcdatamodel; sourceTree = ""; }; 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalStorageManager.swift; sourceTree = ""; }; 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFACollection+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; 1780F6EE25895EDB00FE45FF /* PostCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostCommands.swift; sourceTree = ""; }; 17836C18273F10C40047AF61 /* ActionExtension-iOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "ActionExtension-iOS.entitlements"; sourceTree = ""; }; 17A355D3271A052C007C7A47 /* WriteFreely-MultiPlatform (iOS).entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "WriteFreely-MultiPlatform (iOS).entitlements"; sourceTree = ""; }; 17A4FEDF25924E810037E96B /* MacSoftwareUpdater.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = MacSoftwareUpdater.md; sourceTree = ""; }; 17A4FEEC25927E730037E96B /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.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 = ""; }; 17A5389124DDED0000DEFF9A /* PreferencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesView.swift; sourceTree = ""; }; 17A67CAE251A5DD7002F163D /* PostEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostEditorView.swift; sourceTree = ""; }; 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+Keychain.swift"; sourceTree = ""; }; 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+API.swift"; sourceTree = ""; }; 17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WriteFreelyModel+APIHandlers.swift"; sourceTree = ""; }; 17B3E964250FAA9000EE9748 /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = ""; }; 17B5103A2515448D00E9631F /* Credits.rtf */ = {isa = PBXFileReference; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 17B68D4F25A4FED2005ED37C /* Sparkle-License.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "Sparkle-License.txt"; sourceTree = ""; }; 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFAPost+CoreDataClass.swift"; sourceTree = SOURCE_ROOT; }; 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WFAPost+CoreDataProperties.swift"; sourceTree = SOURCE_ROOT; }; 17BC617825715068003363CA /* ActivePostToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivePostToolbarView.swift; sourceTree = ""; }; 17C42E612507D8E600072984 /* PostStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStatus.swift; sourceTree = ""; }; 17C42E642509237800072984 /* PostListFilteredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostListFilteredView.swift; sourceTree = ""; }; 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+ExecuteAndMergeChanges.swift"; sourceTree = ""; }; 17D435E724E3128F0036B539 /* PreferencesModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferencesModel.swift; sourceTree = ""; }; 17D4926627947D780035BD7E /* MacUpdatesViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MacUpdatesViewModel.swift; sourceTree = ""; }; 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = LoraGX.ttf; sourceTree = ""; }; 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "OpenSans-Regular.ttf"; sourceTree = ""; }; 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "Hack-Regular.ttf"; sourceTree = ""; }; 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteFreely_MultiPlatformApp.swift; sourceTree = ""; }; 17DF328224C87D3300BCE2E3 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 17DF328324C87D3500BCE2E3 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 17DF328824C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WriteFreely-MultiPlatform.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 17DF328B24C87D3500BCE2E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17DF329024C87D3500BCE2E3 /* WriteFreely for Mac.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "WriteFreely for Mac.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 17DF329224C87D3500BCE2E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17DF329324C87D3500BCE2E3 /* macOS.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = macOS.entitlements; sourceTree = ""; }; 17DF329824C87D3500BCE2E3 /* Tests iOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests iOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_iOS.swift; sourceTree = ""; }; 17DF329E24C87D3500BCE2E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17DF32A324C87D3500BCE2E3 /* Tests macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Tests macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 17DF32A724C87D3500BCE2E3 /* Tests_macOS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Tests_macOS.swift; sourceTree = ""; }; 17DF32A924C87D3500BCE2E3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 17DF32C624C884FF00BCE2E3 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 17DF32C724C8853700BCE2E3 /* CODE_OF_CONDUCT.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CODE_OF_CONDUCT.md; sourceTree = ""; }; 17DF32C824C8854B00BCE2E3 /* CONTRIBUTING.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CONTRIBUTING.md; sourceTree = ""; }; 17DF32C924C8855E00BCE2E3 /* LICENSE.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = LICENSE.md; sourceTree = ""; }; 17DF32CA24C8856C00BCE2E3 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = ""; }; 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostStatusBadgeView.swift; sourceTree = ""; }; 17DFDE84251D309400A25F31 /* Hack-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Hack-License.txt"; sourceTree = ""; }; 17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "Lora-Cyrillic-OFL.txt"; sourceTree = ""; }; 17DFDE86251D309400A25F31 /* OpenSans-License.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "OpenSans-License.txt"; sourceTree = ""; }; 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostTextEditingView.swift; sourceTree = ""; }; + 37113EF82A98C10A00B36B98 /* NoSelectedPostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoSelectedPostView.swift; sourceTree = ""; }; 375A67E728FC555C007A1AC0 /* MultilineTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultilineTextView.swift; sourceTree = ""; }; 3779389629EC0C880032D6C1 /* HelpCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HelpCommands.swift; sourceTree = ""; }; 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchablePostListFilteredView.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ 172E0FFC2735B83E00061372 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 172E10012735B83E00061372 /* UniformTypeIdentifiers.framework in Frameworks */, 1784D2F127946DA10033E72E /* WriteFreely in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328524C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 1784D2ED27946D880033E72E /* WriteFreely in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328D24C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( 1784D2EF27946D9A0033E72E /* WriteFreely in Frameworks */, 17D4926527947B4D0035BD7E /* Sparkle in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF329524C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 17DF32A024C87D3500BCE2E3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ 17027E23286741910062EB29 /* Logging */ = { isa = PBXGroup; children = ( 17027E24286741B80062EB29 /* Logging.swift */, ); path = Logging; sourceTree = ""; }; 1709ADDE251B99D40053AF79 /* Technotes */ = { isa = PBXGroup; children = ( 1709ADDF251B9A110053AF79 /* EditorLaunchingPolicy.md */, 17A4FEDF25924E810037E96B /* MacSoftwareUpdater.md */, ); path = Technotes; sourceTree = ""; }; 17120DA624E19CE2002B9F6C /* Settings */ = { isa = PBXGroup; children = ( 17120DB124E1E19C002B9F6C /* SettingsHeaderView.swift */, 17120DA424E19CBF002B9F6C /* SettingsView.swift */, ); path = Settings; sourceTree = ""; }; 17275264280997BF003D0A6A /* ErrorHandling */ = { isa = PBXGroup; children = ( 1727526528099802003D0A6A /* ErrorConstants.swift */, 172752692809991A003D0A6A /* ErrorHandling.swift */, ); path = ErrorHandling; sourceTree = ""; }; 172E10022735B83E00061372 /* ActionExtension-iOS */ = { isa = PBXGroup; children = ( 17836C18273F10C40047AF61 /* ActionExtension-iOS.entitlements */, 172E10032735B83E00061372 /* Media.xcassets */, 172E10182735C3DB00061372 /* ContentView.swift */, 172E10052735B83E00061372 /* ActionViewController.swift */, 172E100A2735B83E00061372 /* Info.plist */, 172E10122735BB6200061372 /* Action.js */, ); path = "ActionExtension-iOS"; sourceTree = ""; }; 1739B8D324EAFAB700DA7421 /* PostEditor */ = { isa = PBXGroup; children = ( 170DFA33251BBC44001D82A0 /* PostEditorModel.swift */, 1756DBB224FECDBB00207AB8 /* PostEditorStatusToolbarView.swift */, ); path = PostEditor; sourceTree = ""; }; 1756AE7F24CB841200FD7257 /* Extensions */ = { isa = PBXGroup; children = ( 17480CA4251272EE00EB7765 /* Bundle+AppVersion.swift */, 17C42E6F250AA12200072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift */, 17B37C5525C8679800FE75E9 /* WriteFreelyModel+API.swift */, 17B37C5C25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift */, 17B37C4A25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift */, 171DC676272C7D0B002B9B8A /* UserDefaults+Extensions.swift */, ); path = Extensions; sourceTree = ""; }; 1762DCB124EB07680019C4EB /* Models */ = { isa = PBXGroup; children = ( 1756DBFF24FEE18400207AB8 /* WFACollection+CoreDataClass.swift */, 1756DC0024FEE18400207AB8 /* WFACollection+CoreDataProperties.swift */, 17B996D62502D23E0017B536 /* WFAPost+CoreDataClass.swift */, 17B996D72502D23E0017B536 /* WFAPost+CoreDataProperties.swift */, 17C42E612507D8E600072984 /* PostStatus.swift */, 174D313124EC2831006CA9EE /* WriteFreelyModel.swift */, 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */, ); path = Models; sourceTree = ""; }; 1765F62C24E1924800C9EBF0 /* Preferences */ = { isa = PBXGroup; children = ( 17D435E724E3128F0036B539 /* PreferencesModel.swift */, 17A5389124DDED0000DEFF9A /* PreferencesView.swift */, ); path = Preferences; sourceTree = ""; }; 17681E3F251940F200D394AE /* Extensions */ = { isa = PBXGroup; children = ( 1756AE8024CB844500FD7257 /* View+Keyboard.swift */, 172E10142735C2BD00061372 /* UIHostingView.swift */, 172E10162735C2DF00061372 /* EnvironmentValues+Extensions.swift */, ); path = Extensions; sourceTree = ""; }; 17A5388924DDA50500DEFF9A /* Settings */ = { isa = PBXGroup; children = ( 17A5388724DDA31F00DEFF9A /* MacAccountView.swift */, 1753F6AB24E431CC00309365 /* MacPreferencesView.swift */, 172C492D2593981900E20ADF /* MacUpdatesView.swift */, 17D4926627947D780035BD7E /* MacUpdatesViewModel.swift */, ); path = Settings; sourceTree = ""; }; 17A67CAB251A5D7E002F163D /* PostEditor */ = { isa = PBXGroup; children = ( 1756AE7624CB2EDD00FD7257 /* PostEditorView.swift */, 173E19D0254318F600440F0F /* RemoteChangePromptView.swift */, 173E19E2254329CC00440F0F /* PostTextEditingView.swift */, 375A67E728FC555C007A1AC0 /* MultilineTextView.swift */, ); path = PostEditor; sourceTree = ""; }; 17A67CAC251A5D8D002F163D /* PostEditor */ = { isa = PBXGroup; children = ( 17479F142583D8E40072B7FB /* PostEditorSharingPicker.swift */, 17A67CAE251A5DD7002F163D /* PostEditorView.swift */, 17E5DF892543610700DCDC9B /* PostTextEditingView.swift */, 17466625256C0D0600629997 /* MacEditorTextView.swift */, ); path = PostEditor; sourceTree = ""; }; 17BC617725715042003363CA /* Navigation */ = { isa = PBXGroup; children = ( 17BC617825715068003363CA /* ActivePostToolbarView.swift */, 1780F6EE25895EDB00FE45FF /* PostCommands.swift */, 3779389629EC0C880032D6C1 /* HelpCommands.swift */, ); path = Navigation; sourceTree = ""; }; 17D4F3722514EE4400517CE6 /* Resources */ = { isa = PBXGroup; children = ( 17DFDE83251D309400A25F31 /* Licenses */, 17D4F3A42514F1E900517CE6 /* Hack-Regular.ttf */, 17D4F39D2514F0E500517CE6 /* OpenSans-Regular.ttf */, 17D4F36B2514EE2F00517CE6 /* LoraGX.ttf */, ); path = Resources; sourceTree = ""; }; 17DF327B24C87D3300BCE2E3 = { isa = PBXGroup; children = ( 17A355D3271A052C007C7A47 /* WriteFreely-MultiPlatform (iOS).entitlements */, 17DF32C624C884FF00BCE2E3 /* README.md */, 17DF32C924C8855E00BCE2E3 /* LICENSE.md */, 17DF32CA24C8856C00BCE2E3 /* CHANGELOG.md */, 17DF32C724C8853700BCE2E3 /* CODE_OF_CONDUCT.md */, 17DF32C824C8854B00BCE2E3 /* CONTRIBUTING.md */, 1709ADDE251B99D40053AF79 /* Technotes */, 17DF328024C87D3300BCE2E3 /* Shared */, 17DF328A24C87D3500BCE2E3 /* iOS */, 17DF329124C87D3500BCE2E3 /* macOS */, 17DF329B24C87D3500BCE2E3 /* Tests iOS */, 17DF32A624C87D3500BCE2E3 /* Tests macOS */, 172E10022735B83E00061372 /* ActionExtension-iOS */, 17DF328924C87D3500BCE2E3 /* Products */, 17DF32C124C87D8D00BCE2E3 /* Frameworks */, ); sourceTree = ""; }; 17DF328024C87D3300BCE2E3 /* Shared */ = { isa = PBXGroup; children = ( 17DF328124C87D3300BCE2E3 /* WriteFreely_MultiPlatformApp.swift */, 1756DBB924FED45500207AB8 /* LocalStorageManager.swift */, 17DF328324C87D3500BCE2E3 /* Assets.xcassets */, 17DF32D024C8B75C00BCE2E3 /* Account */, 1756AE7F24CB841200FD7257 /* Extensions */, 17275264280997BF003D0A6A /* ErrorHandling */, 17027E23286741910062EB29 /* Logging */, 1762DCB124EB07680019C4EB /* Models */, 17DF32CC24C8B72300BCE2E3 /* Navigation */, 1739B8D324EAFAB700DA7421 /* PostEditor */, 17DF32D124C8B78500BCE2E3 /* PostList */, 17DF32D224C8B78D00BCE2E3 /* PostCollection */, 1765F62C24E1924800C9EBF0 /* Preferences */, 17D4F3722514EE4400517CE6 /* Resources */, ); path = Shared; sourceTree = ""; }; 17DF328924C87D3500BCE2E3 /* Products */ = { isa = PBXGroup; children = ( 17DF328824C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */, 17DF329024C87D3500BCE2E3 /* WriteFreely for Mac.app */, 17DF329824C87D3500BCE2E3 /* Tests iOS.xctest */, 17DF32A324C87D3500BCE2E3 /* Tests macOS.xctest */, 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */, ); name = Products; sourceTree = ""; }; 17DF328A24C87D3500BCE2E3 /* iOS */ = { isa = PBXGroup; children = ( 17DF328B24C87D3500BCE2E3 /* Info.plist */, 17B3E964250FAA9000EE9748 /* LaunchScreen.storyboard */, 17681E3F251940F200D394AE /* Extensions */, 17A67CAB251A5D7E002F163D /* PostEditor */, 17120DA624E19CE2002B9F6C /* Settings */, ); path = iOS; sourceTree = ""; }; 17DF329124C87D3500BCE2E3 /* macOS */ = { isa = PBXGroup; children = ( 17DF329224C87D3500BCE2E3 /* Info.plist */, 17DF329324C87D3500BCE2E3 /* macOS.entitlements */, 17A4FEEC25927E730037E96B /* AppDelegate.swift */, 17BC617725715042003363CA /* Navigation */, 17A67CAC251A5D8D002F163D /* PostEditor */, 17A5388924DDA50500DEFF9A /* Settings */, 17B5103A2515448D00E9631F /* Credits.rtf */, ); path = macOS; sourceTree = ""; }; 17DF329B24C87D3500BCE2E3 /* Tests iOS */ = { isa = PBXGroup; children = ( 17DF329C24C87D3500BCE2E3 /* Tests_iOS.swift */, 17DF329E24C87D3500BCE2E3 /* Info.plist */, ); path = "Tests iOS"; sourceTree = ""; }; 17DF32A624C87D3500BCE2E3 /* Tests macOS */ = { isa = PBXGroup; children = ( 17DF32A724C87D3500BCE2E3 /* Tests_macOS.swift */, 17DF32A924C87D3500BCE2E3 /* Info.plist */, ); path = "Tests macOS"; sourceTree = ""; }; 17DF32C124C87D8D00BCE2E3 /* Frameworks */ = { isa = PBXGroup; children = ( 172E10002735B83E00061372 /* UniformTypeIdentifiers.framework */, ); name = Frameworks; sourceTree = ""; }; 17DF32CC24C8B72300BCE2E3 /* Navigation */ = { isa = PBXGroup; children = ( 17DF328224C87D3300BCE2E3 /* ContentView.swift */, + 37113EF82A98C10A00B36B98 /* NoSelectedPostView.swift */, ); path = Navigation; sourceTree = ""; }; 17DF32D024C8B75C00BCE2E3 /* Account */ = { isa = PBXGroup; children = ( 17A5388B24DDC83F00DEFF9A /* AccountModel.swift */, 17120DAB24E1B99F002B9F6C /* AccountLoginView.swift */, 17120DA824E1B2F5002B9F6C /* AccountLogoutView.swift */, 17A5388D24DDEC7400DEFF9A /* AccountView.swift */, ); path = Account; sourceTree = ""; }; 17DF32D124C8B78500BCE2E3 /* PostList */ = { isa = PBXGroup; children = ( 1756AE7324CB26FA00FD7257 /* PostCellView.swift */, 1756AE6D24CB255B00FD7257 /* PostListModel.swift */, 1756AE7924CB65DF00FD7257 /* PostListView.swift */, 17DF32D424C8CA3400BCE2E3 /* PostStatusBadgeView.swift */, 17C42E642509237800072984 /* PostListFilteredView.swift */, 37F749D029B4D3090087F0BF /* SearchablePostListFilteredView.swift */, ); path = PostList; sourceTree = ""; }; 17DF32D224C8B78D00BCE2E3 /* PostCollection */ = { isa = PBXGroup; children = ( 171BFDF924D4AF8300888236 /* CollectionListView.swift */, ); path = PostCollection; sourceTree = ""; }; 17DFDE83251D309400A25F31 /* Licenses */ = { isa = PBXGroup; children = ( 17B68D4F25A4FED2005ED37C /* Sparkle-License.txt */, 17DFDE84251D309400A25F31 /* Hack-License.txt */, 17DFDE85251D309400A25F31 /* Lora-Cyrillic-OFL.txt */, 17DFDE86251D309400A25F31 /* OpenSans-License.txt */, ); path = Licenses; sourceTree = ""; }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ 172E0FFE2735B83E00061372 /* ActionExtension-iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 172E10112735B83E00061372 /* Build configuration list for PBXNativeTarget "ActionExtension-iOS" */; buildPhases = ( 172E0FFB2735B83E00061372 /* Sources */, 172E0FFC2735B83E00061372 /* Frameworks */, 172E0FFD2735B83E00061372 /* Resources */, ); buildRules = ( ); dependencies = ( ); name = "ActionExtension-iOS"; packageProductDependencies = ( 1784D2F027946DA10033E72E /* WriteFreely */, ); productName = "ActionExtension-iOS"; productReference = 172E0FFF2735B83E00061372 /* ActionExtension-iOS.appex */; productType = "com.apple.product-type.app-extension"; }; 17DF328724C87D3500BCE2E3 /* WriteFreely-MultiPlatform (iOS) */ = { isa = PBXNativeTarget; buildConfigurationList = 17DF32B224C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "WriteFreely-MultiPlatform (iOS)" */; buildPhases = ( 17DF328424C87D3500BCE2E3 /* Sources */, 17DF328524C87D3500BCE2E3 /* Frameworks */, 17DF328624C87D3500BCE2E3 /* Resources */, 17DF32C424C87E6700BCE2E3 /* ShellScript */, 172E100E2735B83E00061372 /* Embed App Extensions */, ); buildRules = ( ); dependencies = ( 172E100C2735B83E00061372 /* PBXTargetDependency */, ); name = "WriteFreely-MultiPlatform (iOS)"; packageProductDependencies = ( 1784D2EC27946D880033E72E /* WriteFreely */, ); productName = "WriteFreely-MultiPlatform (iOS)"; productReference = 17DF328824C87D3500BCE2E3 /* WriteFreely-MultiPlatform.app */; productType = "com.apple.product-type.application"; }; 17DF328F24C87D3500BCE2E3 /* WriteFreely-MultiPlatform (macOS) */ = { isa = PBXNativeTarget; buildConfigurationList = 17DF32B524C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "WriteFreely-MultiPlatform (macOS)" */; buildPhases = ( 17DF328C24C87D3500BCE2E3 /* Sources */, 17DF328D24C87D3500BCE2E3 /* Frameworks */, 17DF328E24C87D3500BCE2E3 /* Resources */, 17DF32C524C87FDB00BCE2E3 /* ShellScript */, ); buildRules = ( ); dependencies = ( ); name = "WriteFreely-MultiPlatform (macOS)"; packageProductDependencies = ( 1784D2EE27946D9A0033E72E /* WriteFreely */, 17D4926427947B4D0035BD7E /* Sparkle */, ); productName = "WriteFreely-MultiPlatform (macOS)"; productReference = 17DF329024C87D3500BCE2E3 /* WriteFreely for Mac.app */; productType = "com.apple.product-type.application"; }; 17DF329724C87D3500BCE2E3 /* Tests iOS */ = { isa = PBXNativeTarget; buildConfigurationList = 17DF32B824C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "Tests iOS" */; buildPhases = ( 17DF329424C87D3500BCE2E3 /* Sources */, 17DF329524C87D3500BCE2E3 /* Frameworks */, 17DF329624C87D3500BCE2E3 /* Resources */, ); buildRules = ( ); dependencies = ( 17DF329A24C87D3500BCE2E3 /* PBXTargetDependency */, ); name = "Tests iOS"; productName = "Tests iOS"; productReference = 17DF329824C87D3500BCE2E3 /* Tests iOS.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; 17DF32A224C87D3500BCE2E3 /* Tests macOS */ = { isa = PBXNativeTarget; buildConfigurationList = 17DF32BB24C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "Tests macOS" */; buildPhases = ( 17DF329F24C87D3500BCE2E3 /* Sources */, 17DF32A024C87D3500BCE2E3 /* Frameworks */, 17DF32A124C87D3500BCE2E3 /* Resources */, ); buildRules = ( ); dependencies = ( 17DF32A524C87D3500BCE2E3 /* PBXTargetDependency */, ); name = "Tests macOS"; productName = "Tests macOS"; productReference = 17DF32A324C87D3500BCE2E3 /* Tests macOS.xctest */; productType = "com.apple.product-type.bundle.ui-testing"; }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ 17DF327C24C87D3300BCE2E3 /* Project object */ = { isa = PBXProject; attributes = { LastSwiftUpdateCheck = 1310; LastUpgradeCheck = 1320; TargetAttributes = { 172E0FFE2735B83E00061372 = { CreatedOnToolsVersion = 13.1; }; 17DF328724C87D3500BCE2E3 = { CreatedOnToolsVersion = 12.0; }; 17DF328F24C87D3500BCE2E3 = { CreatedOnToolsVersion = 12.0; }; 17DF329724C87D3500BCE2E3 = { CreatedOnToolsVersion = 12.0; TestTargetID = 17DF328724C87D3500BCE2E3; }; 17DF32A224C87D3500BCE2E3 = { CreatedOnToolsVersion = 12.0; TestTargetID = 17DF328F24C87D3500BCE2E3; }; }; }; buildConfigurationList = 17DF327F24C87D3300BCE2E3 /* Build configuration list for PBXProject "WriteFreely-MultiPlatform" */; compatibilityVersion = "Xcode 9.3"; developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, Base, ); mainGroup = 17DF327B24C87D3300BCE2E3; packageReferences = ( 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */, 17D4926327947B4D0035BD7E /* XCRemoteSwiftPackageReference "Sparkle" */, ); productRefGroup = 17DF328924C87D3500BCE2E3 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 17DF328724C87D3500BCE2E3 /* WriteFreely-MultiPlatform (iOS) */, 17DF328F24C87D3500BCE2E3 /* WriteFreely-MultiPlatform (macOS) */, 17DF329724C87D3500BCE2E3 /* Tests iOS */, 17DF32A224C87D3500BCE2E3 /* Tests macOS */, 172E0FFE2735B83E00061372 /* ActionExtension-iOS */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ 172E0FFD2735B83E00061372 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 17836C17273F0FBB0047AF61 /* OpenSans-Regular.ttf in Resources */, 172E10132735BB6200061372 /* Action.js in Resources */, 172E10042735B83E00061372 /* Media.xcassets in Resources */, 17836C15273F0FBB0047AF61 /* Hack-Regular.ttf in Resources */, 17836C16273F0FBB0047AF61 /* LoraGX.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328624C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 17B3E965250FAA9000EE9748 /* LaunchScreen.storyboard in Resources */, 17DFDE8B251D309400A25F31 /* OpenSans-License.txt in Resources */, 17DF32AE24C87D3500BCE2E3 /* Assets.xcassets in Resources */, 17D4F39E2514F0E500517CE6 /* OpenSans-Regular.ttf in Resources */, 17D4F36C2514EE2F00517CE6 /* LoraGX.ttf in Resources */, 17D4F3A52514F1E900517CE6 /* Hack-Regular.ttf in Resources */, 17DFDE89251D309400A25F31 /* Lora-Cyrillic-OFL.txt in Resources */, 17DFDE87251D309400A25F31 /* Hack-License.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328E24C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF32AF24C87D3500BCE2E3 /* Assets.xcassets in Resources */, 17DFDE8C251D309400A25F31 /* OpenSans-License.txt in Resources */, 17B5103B2515448D00E9631F /* Credits.rtf in Resources */, 17D4F39F2514F0E500517CE6 /* OpenSans-Regular.ttf in Resources */, 17D4F3A62514F1E900517CE6 /* Hack-Regular.ttf in Resources */, 17D4F36D2514EE2F00517CE6 /* LoraGX.ttf in Resources */, 17DFDE8A251D309400A25F31 /* Lora-Cyrillic-OFL.txt in Resources */, 17DFDE88251D309400A25F31 /* Hack-License.txt in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF329624C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; 17DF32A124C87D3500BCE2E3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ 17DF32C424C87E6700BCE2E3 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Run SwiftLint on builds\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; 17DF32C524C87FDB00BCE2E3 /* ShellScript */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); inputFileListPaths = ( ); inputPaths = ( ); outputFileListPaths = ( ); outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "# Run SwiftLint on builds\nif which swiftlint >/dev/null; then\n swiftlint\nelse\n echo \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ 172E0FFB2735B83E00061372 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 172E10062735B83E00061372 /* ActionViewController.swift in Sources */, 172E10202735C64600061372 /* WFACollection+CoreDataClass.swift in Sources */, 172E10222735C64600061372 /* WFAPost+CoreDataProperties.swift in Sources */, 172E101D2735C5AB00061372 /* LocalStorageModel.xcdatamodeld in Sources */, 17027E27286757650062EB29 /* Logging.swift in Sources */, 17836C14273EFB870047AF61 /* UserDefaults+Extensions.swift in Sources */, 172E10242735C72500061372 /* PreferencesModel.swift in Sources */, 172E10172735C2DF00061372 /* EnvironmentValues+Extensions.swift in Sources */, 172E10212735C64600061372 /* WFACollection+CoreDataProperties.swift in Sources */, 172E101C2735C57400061372 /* LocalStorageManager.swift in Sources */, 172E10192735C3DB00061372 /* ContentView.swift in Sources */, 1727526828099802003D0A6A /* ErrorConstants.swift in Sources */, 172E10152735C2BD00061372 /* UIHostingView.swift in Sources */, 172E101F2735C64600061372 /* WFAPost+CoreDataClass.swift in Sources */, 1727526C2809991A003D0A6A /* ErrorHandling.swift in Sources */, 172E10232735C6FF00061372 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, 172E101E2735C62F00061372 /* PostStatus.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328424C87D3500BCE2E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF32AC24C87D3500BCE2E3 /* ContentView.swift in Sources */, 173E19D1254318F600440F0F /* RemoteChangePromptView.swift in Sources */, 17B37C5625C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */, 17C42E622507D8E600072984 /* PostStatus.swift in Sources */, + 37095AE02AA4A0E700C9C5F8 /* NoSelectedPostView.swift in Sources */, 1756DBBA24FED45500207AB8 /* LocalStorageManager.swift in Sources */, 1727526A2809991A003D0A6A /* ErrorHandling.swift in Sources */, 1756AE8124CB844500FD7257 /* View+Keyboard.swift in Sources */, 17C42E652509237800072984 /* PostListFilteredView.swift in Sources */, 170DFA34251BBC44001D82A0 /* PostEditorModel.swift in Sources */, 17120DAC24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, 17B37C4B25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */, 17480CA5251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */, 17B37C5D25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */, 17120DA924E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, 171BFDFA24D4AF8300888236 /* CollectionListView.swift in Sources */, 1756DBB324FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */, 17120DB224E1E19C002B9F6C /* SettingsHeaderView.swift in Sources */, 375A67E828FC555C007A1AC0 /* MultilineTextView.swift in Sources */, 171DC677272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */, 1756DBB724FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */, 17B996DA2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */, 1756AE7724CB2EDD00FD7257 /* PostEditorView.swift in Sources */, 17DF32D524C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, 37F749D129B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */, 17D435E824E3128F0036B539 /* PreferencesModel.swift in Sources */, 1756AE7A24CB65DF00FD7257 /* PostListView.swift in Sources */, 17B996D82502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */, 1756DC0124FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */, 17DF32AA24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */, 17120DA724E19D11002B9F6C /* SettingsView.swift in Sources */, 17027E25286741B90062EB29 /* Logging.swift in Sources */, 1727526628099802003D0A6A /* ErrorConstants.swift in Sources */, 1756DC0324FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */, 17120DA224E1985C002B9F6C /* AccountModel.swift in Sources */, 17120DA324E19A42002B9F6C /* PreferencesView.swift in Sources */, 1756AE6E24CB255B00FD7257 /* PostListModel.swift in Sources */, 173E19E3254329CC00440F0F /* PostTextEditingView.swift in Sources */, 174D313224EC2831006CA9EE /* WriteFreelyModel.swift in Sources */, 17C42E70250AA12300072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, 17120DA124E19839002B9F6C /* AccountView.swift in Sources */, 1756AE7424CB26FA00FD7257 /* PostCellView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF328C24C87D3500BCE2E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 171DC678272C7D0B002B9B8A /* UserDefaults+Extensions.swift in Sources */, 17DF32AD24C87D3500BCE2E3 /* ContentView.swift in Sources */, 1756DBBB24FED45500207AB8 /* LocalStorageManager.swift in Sources */, 17A4FEED25927E730037E96B /* AppDelegate.swift in Sources */, 174D313324EC2831006CA9EE /* WriteFreelyModel.swift in Sources */, 17D435E924E3128F0036B539 /* PreferencesModel.swift in Sources */, 17120DAA24E1B2F5002B9F6C /* AccountLogoutView.swift in Sources */, 17DF32D624C8CA3400BCE2E3 /* PostStatusBadgeView.swift in Sources */, 172C492E2593981900E20ADF /* MacUpdatesView.swift in Sources */, + 37113EF92A98C10A00B36B98 /* NoSelectedPostView.swift in Sources */, 1727526728099802003D0A6A /* ErrorConstants.swift in Sources */, 17479F152583D8E40072B7FB /* PostEditorSharingPicker.swift in Sources */, 17480CA6251272EE00EB7765 /* Bundle+AppVersion.swift in Sources */, 3779389729EC0C880032D6C1 /* HelpCommands.swift in Sources */, 17C42E662509237800072984 /* PostListFilteredView.swift in Sources */, 17120DAD24E1B99F002B9F6C /* AccountLoginView.swift in Sources */, 17D4926727947D780035BD7E /* MacUpdatesViewModel.swift in Sources */, 17466626256C0D0600629997 /* MacEditorTextView.swift in Sources */, 17027E26286741B90062EB29 /* Logging.swift in Sources */, 1727526B2809991A003D0A6A /* ErrorHandling.swift in Sources */, 17E5DF8A2543610700DCDC9B /* PostTextEditingView.swift in Sources */, 37F749D229B4D3090087F0BF /* SearchablePostListFilteredView.swift in Sources */, 17C42E71250AAFD500072984 /* NSManagedObjectContext+ExecuteAndMergeChanges.swift in Sources */, 1756AE7B24CB65DF00FD7257 /* PostListView.swift in Sources */, 1753F6AC24E431CC00309365 /* MacPreferencesView.swift in Sources */, 1756DC0424FEE18400207AB8 /* WFACollection+CoreDataProperties.swift in Sources */, 17B996DB2502D23E0017B536 /* WFAPost+CoreDataProperties.swift in Sources */, 17BC618A25715318003363CA /* ActivePostToolbarView.swift in Sources */, 171BFDFB24D4AF8300888236 /* CollectionListView.swift in Sources */, 17A67CAF251A5DD7002F163D /* PostEditorView.swift in Sources */, 17DF32AB24C87D3500BCE2E3 /* WriteFreely_MultiPlatformApp.swift in Sources */, 17B37C5725C8679800FE75E9 /* WriteFreelyModel+API.swift in Sources */, 17A5388C24DDC83F00DEFF9A /* AccountModel.swift in Sources */, 17B996D92502D23E0017B536 /* WFAPost+CoreDataClass.swift in Sources */, 1756DBB824FED3A400207AB8 /* LocalStorageModel.xcdatamodeld in Sources */, 17B37C4C25C8661300FE75E9 /* WriteFreelyModel+Keychain.swift in Sources */, 17A5389324DDED0000DEFF9A /* PreferencesView.swift in Sources */, 1756AE6F24CB255B00FD7257 /* PostListModel.swift in Sources */, 1756DC0224FEE18400207AB8 /* WFACollection+CoreDataClass.swift in Sources */, 1756DBB424FECDBB00207AB8 /* PostEditorStatusToolbarView.swift in Sources */, 17A5388F24DDEC7400DEFF9A /* AccountView.swift in Sources */, 1780F6EF25895EDB00FE45FF /* PostCommands.swift in Sources */, 17B37C5E25C8698900FE75E9 /* WriteFreelyModel+APIHandlers.swift in Sources */, 170DFA35251BBC44001D82A0 /* PostEditorModel.swift in Sources */, 1756AE7524CB26FA00FD7257 /* PostCellView.swift in Sources */, 17A5388824DDA31F00DEFF9A /* MacAccountView.swift in Sources */, 17C42E632507D8E600072984 /* PostStatus.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF329424C87D3500BCE2E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF329D24C87D3500BCE2E3 /* Tests_iOS.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; 17DF329F24C87D3500BCE2E3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( 17DF32A824C87D3500BCE2E3 /* Tests_macOS.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ 172E100C2735B83E00061372 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 172E0FFE2735B83E00061372 /* ActionExtension-iOS */; targetProxy = 172E100B2735B83E00061372 /* PBXContainerItemProxy */; }; 17DF329A24C87D3500BCE2E3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 17DF328724C87D3500BCE2E3 /* WriteFreely-MultiPlatform (iOS) */; targetProxy = 17DF329924C87D3500BCE2E3 /* PBXContainerItemProxy */; }; 17DF32A524C87D3500BCE2E3 /* PBXTargetDependency */ = { isa = PBXTargetDependency; target = 17DF328F24C87D3500BCE2E3 /* WriteFreely-MultiPlatform (macOS) */; targetProxy = 17DF32A424C87D3500BCE2E3 /* PBXContainerItemProxy */; }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ 172E100F2735B83E00061372 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = ""; ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 704; + CURRENT_PROJECT_VERSION = 706; DEVELOPMENT_TEAM = TPPAB4YBA6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = "Create WriteFreely draft"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.16; + MARKETING_VERSION = 1.0.17; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 172E10102735B83E00061372 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_ALTERNATE_APPICON_NAMES = ""; ASSETCATALOG_COMPILER_APPICON_NAME = AppIconExtension; CLANG_CXX_LANGUAGE_STANDARD = "gnu++17"; CODE_SIGN_ENTITLEMENTS = "ActionExtension-iOS/ActionExtension-iOS.entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 704; + CURRENT_PROJECT_VERSION = 706; DEVELOPMENT_TEAM = TPPAB4YBA6; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = "ActionExtension-iOS/Info.plist"; INFOPLIST_KEY_CFBundleDisplayName = "Create WriteFreely draft"; INFOPLIST_KEY_NSHumanReadableCopyright = ""; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.0.16; + MARKETING_VERSION = 1.0.17; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform.ActionExtension-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SKIP_INSTALL = YES; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 17DF32B024C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = dwarf; ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", "$(inherited)", ); GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 17DF32B124C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; CLANG_ENABLE_OBJC_WEAK = YES; CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; GCC_C_LANGUAGE_STANDARD = gnu11; GCC_NO_COMMON_BLOCKS = YES; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; GCC_WARN_UNDECLARED_SELECTOR = YES; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SWIFT_COMPILATION_MODE = wholemodule; SWIFT_OPTIMIZATION_LEVEL = "-O"; }; name = Release; }; 17DF32B324C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 704; + CURRENT_PROJECT_VERSION = 706; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.16; + MARKETING_VERSION = 1.0.17; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely-MultiPlatform"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; }; 17DF32B424C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = "WriteFreely-MultiPlatform (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 704; + CURRENT_PROJECT_VERSION = 706; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = iOS/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.0.16; + MARKETING_VERSION = 1.0.17; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely-MultiPlatform"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; name = Release; }; 17DF32B624C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 708; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = macOS/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely for Mac"; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; name = Debug; }; 17DF32B724C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = macOS/macOS.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 708; DEVELOPMENT_TEAM = TPPAB4YBA6; ENABLE_HARDENED_RUNTIME = YES; ENABLE_PREVIEWS = YES; INFOPLIST_FILE = macOS/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.WriteFreely-MultiPlatform"; PRODUCT_NAME = "WriteFreely for Mac"; SDKROOT = macosx; SWIFT_VERSION = 5.0; }; name = Release; }; 17DF32B924C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = TPPAB4YBA6; INFOPLIST_FILE = "Tests iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "WriteFreely-MultiPlatform (iOS)"; }; name = Debug; }; 17DF32BA24C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = TPPAB4YBA6; INFOPLIST_FILE = "Tests iOS/Info.plist"; IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", "@loader_path/Frameworks", ); PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.Tests-iOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = iphoneos; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; TEST_TARGET_NAME = "WriteFreely-MultiPlatform (iOS)"; VALIDATE_PRODUCT = YES; }; name = Release; }; 17DF32BC24C87D3500BCE2E3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 702; DEVELOPMENT_TEAM = TPPAB4YBA6; INFOPLIST_FILE = "Tests macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.Tests-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = "WriteFreely-MultiPlatform (macOS)"; }; name = Debug; }; 17DF32BD24C87D3500BCE2E3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 702; DEVELOPMENT_TEAM = TPPAB4YBA6; INFOPLIST_FILE = "Tests macOS/Info.plist"; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/../Frameworks", "@loader_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 10.15; PRODUCT_BUNDLE_IDENTIFIER = "com.abunchtell.Tests-macOS"; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SWIFT_VERSION = 5.0; TEST_TARGET_NAME = "WriteFreely-MultiPlatform (macOS)"; }; name = Release; }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ 172E10112735B83E00061372 /* Build configuration list for PBXNativeTarget "ActionExtension-iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 172E100F2735B83E00061372 /* Debug */, 172E10102735B83E00061372 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 17DF327F24C87D3300BCE2E3 /* Build configuration list for PBXProject "WriteFreely-MultiPlatform" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32B024C87D3500BCE2E3 /* Debug */, 17DF32B124C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 17DF32B224C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "WriteFreely-MultiPlatform (iOS)" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32B324C87D3500BCE2E3 /* Debug */, 17DF32B424C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 17DF32B524C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "WriteFreely-MultiPlatform (macOS)" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32B624C87D3500BCE2E3 /* Debug */, 17DF32B724C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 17DF32B824C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "Tests iOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32B924C87D3500BCE2E3 /* Debug */, 17DF32BA24C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; 17DF32BB24C87D3500BCE2E3 /* Build configuration list for PBXNativeTarget "Tests macOS" */ = { isa = XCConfigurationList; buildConfigurations = ( 17DF32BC24C87D3500BCE2E3 /* Debug */, 17DF32BD24C87D3500BCE2E3 /* Release */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/writefreely/writefreely-swift"; requirement = { kind = upToNextMajorVersion; minimumVersion = 0.3.7; }; }; 17D4926327947B4D0035BD7E /* XCRemoteSwiftPackageReference "Sparkle" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/sparkle-project/Sparkle"; requirement = { kind = upToNextMinorVersion; minimumVersion = 2.3.0; }; }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ 1784D2EC27946D880033E72E /* WriteFreely */ = { isa = XCSwiftPackageProductDependency; package = 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */; productName = WriteFreely; }; 1784D2EE27946D9A0033E72E /* WriteFreely */ = { isa = XCSwiftPackageProductDependency; package = 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */; productName = WriteFreely; }; 1784D2F027946DA10033E72E /* WriteFreely */ = { isa = XCSwiftPackageProductDependency; package = 1784D2EB27946D880033E72E /* XCRemoteSwiftPackageReference "writefreely-swift" */; productName = WriteFreely; }; 17D4926427947B4D0035BD7E /* Sparkle */ = { isa = XCSwiftPackageProductDependency; package = 17D4926327947B4D0035BD7E /* XCRemoteSwiftPackageReference "Sparkle" */; productName = Sparkle; }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ 1756DBB524FED3A400207AB8 /* LocalStorageModel.xcdatamodeld */ = { isa = XCVersionGroup; children = ( 1756DBB624FED3A400207AB8 /* LocalStorageModel.xcdatamodel */, ); currentVersion = 1756DBB624FED3A400207AB8 /* LocalStorageModel.xcdatamodel */; path = LocalStorageModel.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; /* End XCVersionGroup section */ }; rootObject = 17DF327C24C87D3300BCE2E3 /* Project object */; } diff --git a/iOS/PostEditor/PostEditorView.swift b/iOS/PostEditor/PostEditorView.swift index 0a875f3..070b421 100644 --- a/iOS/PostEditor/PostEditorView.swift +++ b/iOS/PostEditor/PostEditorView.swift @@ -1,283 +1,286 @@ import SwiftUI struct PostEditorView: View { @EnvironmentObject var model: WriteFreelyModel @EnvironmentObject var errorHandling: ErrorHandling @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.managedObjectContext) var moc @Environment(\.presentationMode) var presentationMode @ObservedObject var post: WFAPost @State private var updatingTitleFromServer: Bool = false @State private var updatingBodyFromServer: 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 { VStack { if post.hasNewerRemoteCopy { RemoteChangePromptView( remoteChangeType: .remoteCopyUpdated, buttonHandler: { model.updateFromServer(post: post) } ) } else if post.wasDeletedFromServer { RemoteChangePromptView( remoteChangeType: .remoteCopyDeleted, buttonHandler: { self.presentationMode.wrappedValue.dismiss() DispatchQueue.main.async { model.posts.remove(post) } } ) } PostTextEditingView( post: _post, updatingTitleFromServer: $updatingTitleFromServer, updatingBodyFromServer: $updatingBodyFromServer ) .withErrorHandling() } .navigationBarTitleDisplayMode(.inline) .padding() .toolbar { ToolbarItem(placement: .principal) { PostEditorStatusToolbarView(post: post) } ToolbarItem(placement: .primaryAction) { - if model.isProcessingRequest { + if !model.hasNetworkConnection { + Image(systemName: "wifi.exclamationmark") + .foregroundColor(.secondary) + } else if model.isProcessingRequest { ProgressView() } else { Menu(content: { if post.status == PostStatus.local.rawValue { Menu(content: { Label("Publish to…", systemImage: "paperplane") Button(action: { if model.account.isLoggedIn { post.collectionAlias = nil publishPost() } else { self.model.isPresentingSettingsView = true } }, label: { Text(" \(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")") }) ForEach(collections) { collection in Button(action: { if model.account.isLoggedIn { post.collectionAlias = collection.alias publishPost() } else { self.model.isPresentingSettingsView = true } }, label: { Text(" \(collection.title)") }) } }, label: { Label("Publish…", systemImage: "paperplane") }) .accessibilityHint(Text("Choose the blog you want to publish this post to")) .disabled(post.body.count == 0) } else if post.status == PostStatus.edited.rawValue { Button(action: { if model.account.isLoggedIn { publishPost() } else { self.model.isPresentingSettingsView = true } }, label: { Label("Publish", systemImage: "paperplane") }) .disabled(post.status == PostStatus.published.rawValue || post.body.count == 0) Button(action: { model.updateFromServer(post: post) }, label: { Label("Revert", systemImage: "clock.arrow.circlepath") }) .accessibilityHint(Text("Replace the edited post with the published version from the server")) .disabled(post.status != PostStatus.edited.rawValue) } Button(action: { sharePost() }, label: { Label("Share", systemImage: "square.and.arrow.up") }) .accessibilityHint(Text("Open the system share sheet to share a link to this post")) .disabled(post.postId == nil) if model.account.isLoggedIn && post.status != PostStatus.local.rawValue { Section(header: Text("Move To Collection")) { Label("Move to:", systemImage: "arrowshape.zigzag.right") Picker(selection: $selectedCollection, label: Text("Move to…")) { Text( " \(model.account.server == "https://write.as" ? "Anonymous" : "Drafts")" ).tag(nil as WFACollection?) ForEach(collections) { collection in Text(" \(collection.title)").tag(collection as WFACollection?) } } } } }, label: { ZStack { Image("does.not.exist") .accessibilityHidden(true) Image(systemName: "ellipsis.circle") .imageScale(.large) .accessibilityHidden(true) } }) .accessibilityLabel(Text("Menu")) .accessibilityHint(Text("Opens a context menu to publish, share, or move the post")) .onTapGesture { hideKeyboard() } .disabled(post.body.count == 0) } } } .onChange(of: post.hasNewerRemoteCopy, perform: { _ in if !post.hasNewerRemoteCopy { updatingTitleFromServer = true updatingBodyFromServer = true } }) .onChange(of: selectedCollection, perform: { [selectedCollection] newCollection in if post.collectionAlias == newCollection?.alias { return } else { post.collectionAlias = newCollection?.alias model.move(post: post, from: selectedCollection, to: newCollection) } }) .onChange(of: post.status, perform: { value in if value != PostStatus.published.rawValue { self.model.editor.saveLastDraft(post) } else { self.model.editor.clearLastDraft() } DispatchQueue.main.async { LocalStorageManager.standard.saveContext() } }) .onAppear(perform: { self.selectedCollection = collections.first { $0.alias == post.collectionAlias } model.editor.setInitialValues(for: post) if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { self.model.editor.saveLastDraft(post) } } else { self.model.editor.clearLastDraft() } }) .onChange(of: model.hasError) { value in if value { if let error = model.currentError { self.errorHandling.handle(error: error) } else { self.errorHandling.handle(error: AppError.genericError()) } model.hasError = false } } .onDisappear(perform: { self.model.editor.clearLastDraft() if post.title.count == 0 && post.body.count == 0 && post.status == PostStatus.local.rawValue && post.updatedDate == nil && post.postId == nil { DispatchQueue.main.async { model.posts.remove(post) } } else if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { LocalStorageManager.standard.saveContext() } } }) } private func publishPost() { DispatchQueue.main.async { LocalStorageManager.standard.saveContext() model.publish(post: post) } model.editor.setInitialValues(for: post) self.hideKeyboard() } private func sharePost() { // If the post doesn't have a post ID, it isn't published, and therefore can't be shared, so return early. guard let postId = post.postId else { return } var urlString: String if let postSlug = post.slug, let postCollectionAlias = post.collectionAlias { // This post is in a collection, so share the URL as baseURL/postSlug. let urls = collections.filter { $0.alias == postCollectionAlias } let baseURL = urls.first?.url ?? "\(model.account.server)/\(postCollectionAlias)/" urlString = "\(baseURL)\(postSlug)" } else { // This is a draft post, so share the URL as server/postID urlString = "\(model.account.server)/\(postId)" } guard let data = URL(string: urlString) else { return } let activityView = UIActivityViewController(activityItems: [data], applicationActivities: nil) UIApplication.shared.windows.first?.rootViewController?.present(activityView, animated: true, completion: nil) if UIDevice.current.userInterfaceIdiom == .pad { activityView.popoverPresentationController?.permittedArrowDirections = .up activityView.popoverPresentationController?.sourceView = UIApplication.shared.windows.first activityView.popoverPresentationController?.sourceRect = CGRect( x: UIScreen.main.bounds.width, y: -125, width: 200, height: 200 ) } } } struct PostEditorView_EmptyPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.standard.container.viewContext let testPost = WFAPost(context: context) testPost.createdDate = Date() testPost.appearance = "norm" let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } } struct PostEditorView_ExistingPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.standard.container.viewContext let testPost = WFAPost(context: context) testPost.title = "Test Post Title" testPost.body = "Here's some cool sample body text." testPost.createdDate = Date() testPost.appearance = "code" testPost.hasNewerRemoteCopy = true let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/macOS/PostEditor/MacEditorTextView.swift b/macOS/PostEditor/MacEditorTextView.swift index 9c11d8a..7c411e1 100644 --- a/macOS/PostEditor/MacEditorTextView.swift +++ b/macOS/PostEditor/MacEditorTextView.swift @@ -1,217 +1,220 @@ // Based on: // // MacEditorTextView // Copyright (c) Thiago Holanda 2020 // https://twitter.com/tholanda // // MIT license // // See: https://gist.github.com/unnamedd/6e8c3fbc806b8deb60fa65d6b9affab0 import Combine import SwiftUI struct MacEditorTextView: NSViewRepresentable { @Binding var text: String var isFirstResponder: Bool = false var isEditable: Bool = true var font: NSFont? = NSFont(name: PostAppearance.serif.rawValue, size: 17) var onEditingChanged: () -> Void = {} var onCommit: () -> Void = {} var onTextChange: (String) -> Void = { _ in } func makeCoordinator() -> Coordinator { Coordinator(self) } func makeNSView(context: Context) -> CustomTextView { let textView = CustomTextView( text: text, isEditable: isEditable, isFirstResponder: isFirstResponder, font: font ) textView.delegate = context.coordinator return textView } func updateNSView(_ view: CustomTextView, context: Context) { view.text = text view.selectedRanges = context.coordinator.selectedRanges } } // MARK: - Coordinator extension MacEditorTextView { class Coordinator: NSObject, NSTextViewDelegate { var parent: MacEditorTextView var selectedRanges: [NSValue] = [] var didBecomeFirstResponder: Bool = false init(_ parent: MacEditorTextView) { self.parent = parent } func textDidBeginEditing(_ notification: Notification) { guard let textView = notification.object as? NSTextView else { return } self.parent.text = textView.string self.parent.onEditingChanged() } func textDidChange(_ notification: Notification) { guard let textView = notification.object as? NSTextView else { return } self.parent.text = textView.string self.selectedRanges = textView.selectedRanges self.parent.onTextChange(textView.string) } func textDidEndEditing(_ notification: Notification) { guard let textView = notification.object as? NSTextView else { return } self.parent.text = textView.string self.parent.onCommit() } } } // MARK: - CustomTextView final class CustomTextView: NSView { private var isFirstResponder: Bool private var isEditable: Bool private var font: NSFont? weak var delegate: NSTextViewDelegate? var text: String { didSet { textView.string = text } } var selectedRanges: [NSValue] = [] { didSet { guard selectedRanges.count > 0 else { return } textView.selectedRanges = selectedRanges } } private lazy var scrollView: NSScrollView = { let scrollView = NSScrollView() scrollView.drawsBackground = false scrollView.borderType = .noBorder + scrollView.autohidesScrollers = true scrollView.hasVerticalScroller = true scrollView.hasHorizontalRuler = false scrollView.autoresizingMask = [.width, .height] scrollView.translatesAutoresizingMaskIntoConstraints = false return scrollView }() private lazy var textView: NSTextView = { let contentSize = scrollView.contentSize let textStorage = NSTextStorage() let layoutManager = NSLayoutManager() textStorage.addLayoutManager(layoutManager) let textContainer = NSTextContainer(containerSize: scrollView.frame.size) textContainer.widthTracksTextView = true textContainer.containerSize = NSSize( width: contentSize.width, height: CGFloat.greatestFiniteMagnitude ) layoutManager.addTextContainer(textContainer) let paragraphStyle = NSMutableParagraphStyle() let lineSpacing: CGFloat = 8.5 paragraphStyle.lineSpacing = lineSpacing let textView = NSTextView(frame: .zero, textContainer: textContainer) textView.autoresizingMask = .width textView.delegate = self.delegate textView.drawsBackground = false textView.font = self.font textView.defaultParagraphStyle = paragraphStyle textView.isEditable = self.isEditable textView.isHorizontallyResizable = false textView.isVerticallyResizable = true textView.maxSize = NSSize( width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude ) textView.minSize = NSSize(width: 0, height: contentSize.height) textView.textColor = NSColor.labelColor textView.allowsUndo = true textView.usesFindPanel = true textView.isAutomaticDashSubstitutionEnabled = false textView.isRichText = false textView.typingAttributes = [ .paragraphStyle: paragraphStyle, // H/T Daniel Jalkut .font: font ?? NSFont.systemFont(ofSize: 17), // Fall back to system font if we can't unwrap font argument .foregroundColor: NSColor.labelColor ] + textView.textContainer?.lineFragmentPadding = 16 + textView.textContainerInset = NSSize(width: 0, height: 16) return textView }() // MARK: - Init init(text: String, isEditable: Bool, isFirstResponder: Bool, font: NSFont?) { self.font = font self.isFirstResponder = isFirstResponder self.isEditable = isEditable self.text = text super.init(frame: .zero) } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } // MARK: - Life cycle override func viewWillDraw() { super.viewWillDraw() setupScrollViewConstraints() setupTextView() if isFirstResponder { self.window?.makeFirstResponder(self.textView) } } func setupScrollViewConstraints() { scrollView.translatesAutoresizingMaskIntoConstraints = false addSubview(scrollView) NSLayoutConstraint.activate([ scrollView.topAnchor.constraint(equalTo: topAnchor), scrollView.trailingAnchor.constraint(equalTo: trailingAnchor), scrollView.bottomAnchor.constraint(equalTo: bottomAnchor), scrollView.leadingAnchor.constraint(equalTo: leadingAnchor) ]) } func setupTextView() { scrollView.documentView = textView } } diff --git a/macOS/PostEditor/PostEditorView.swift b/macOS/PostEditor/PostEditorView.swift index a6d928c..fc6fb62 100644 --- a/macOS/PostEditor/PostEditorView.swift +++ b/macOS/PostEditor/PostEditorView.swift @@ -1,104 +1,111 @@ import SwiftUI struct PostEditorView: View { @EnvironmentObject var model: WriteFreelyModel @EnvironmentObject var errorHandling: ErrorHandling @ObservedObject var post: WFAPost @State private var isHovering: Bool = false @State private var updatingFromServer: Bool = false var body: some View { - PostTextEditingView( - post: post, - updatingFromServer: $updatingFromServer - ) - .padding() - .background(Color(NSColor.controlBackgroundColor)) - .onAppear(perform: { - model.editor.setInitialValues(for: post) - if post.status != PostStatus.published.rawValue { - DispatchQueue.main.async { - self.model.editor.saveLastDraft(post) - } - } else { - self.model.editor.clearLastDraft() - } - }) - .onChange(of: post.hasNewerRemoteCopy, perform: { _ in - if !post.hasNewerRemoteCopy { - self.updatingFromServer = true - } - }) - .onChange(of: post.status, perform: { value in - if value != PostStatus.published.rawValue { - self.model.editor.saveLastDraft(post) - } else { - self.model.editor.clearLastDraft() + VStack { + if !model.hasNetworkConnection { + Label("You are not connected to the internet", systemImage: "wifi.exclamationmark") + .font(.caption) + .foregroundColor(.secondary) + .padding(.top, 8) } - DispatchQueue.main.async { - LocalStorageManager.standard.saveContext() - } - }) - .onChange(of: model.hasError) { value in - if value { - if let error = model.currentError { - self.errorHandling.handle(error: error) + PostTextEditingView( + post: post, + updatingFromServer: $updatingFromServer + ) + .background(Color(NSColor.controlBackgroundColor)) + .onAppear(perform: { + model.editor.setInitialValues(for: post) + if post.status != PostStatus.published.rawValue { + DispatchQueue.main.async { + self.model.editor.saveLastDraft(post) + } } else { - self.errorHandling.handle(error: AppError.genericError()) + self.model.editor.clearLastDraft() } - model.hasError = false - } - } - .onDisappear(perform: { - DispatchQueue.main.async { - model.editor.clearLastDraft() - } - if post.title.count == 0 - && post.body.count == 0 - && post.status == PostStatus.local.rawValue - && post.updatedDate == nil - && post.postId == nil { - DispatchQueue.main.async { - model.posts.remove(post) + }) + .onChange(of: post.hasNewerRemoteCopy, perform: { _ in + if !post.hasNewerRemoteCopy { + self.updatingFromServer = true + } + }) + .onChange(of: post.status, perform: { value in + if value != PostStatus.published.rawValue { + self.model.editor.saveLastDraft(post) + } else { + self.model.editor.clearLastDraft() } - } else if post.status != PostStatus.published.rawValue { DispatchQueue.main.async { LocalStorageManager.standard.saveContext() } + }) + .onChange(of: model.hasError) { value in + if value { + if let error = model.currentError { + self.errorHandling.handle(error: error) + } else { + self.errorHandling.handle(error: AppError.genericError()) + } + model.hasError = false + } } - }) + .onDisappear(perform: { + DispatchQueue.main.async { + model.editor.clearLastDraft() + } + if post.title.count == 0 + && post.body.count == 0 + && post.status == PostStatus.local.rawValue + && post.updatedDate == nil + && post.postId == nil { + DispatchQueue.main.async { + model.posts.remove(post) + } + } else if post.status != PostStatus.published.rawValue { + DispatchQueue.main.async { + LocalStorageManager.standard.saveContext() + } + } + }) + } } } struct PostEditorView_EmptyPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.standard.container.viewContext let testPost = WFAPost(context: context) testPost.createdDate = Date() testPost.appearance = "norm" let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } } struct PostEditorView_ExistingPostPreviews: PreviewProvider { static var previews: some View { let context = LocalStorageManager.standard.container.viewContext let testPost = WFAPost(context: context) testPost.title = "Test Post Title" testPost.body = "Here's some cool sample body text." testPost.createdDate = Date() testPost.appearance = "code" let model = WriteFreelyModel() return PostEditorView(post: testPost) .environment(\.managedObjectContext, context) .environmentObject(model) } } diff --git a/macOS/PostEditor/PostTextEditingView.swift b/macOS/PostEditor/PostTextEditingView.swift index 8922952..6dd74bf 100644 --- a/macOS/PostEditor/PostTextEditingView.swift +++ b/macOS/PostEditor/PostTextEditingView.swift @@ -1,123 +1,124 @@ import SwiftUI struct PostTextEditingView: View { @EnvironmentObject var model: WriteFreelyModel @ObservedObject var post: WFAPost @Binding var updatingFromServer: Bool @State private var appearance: PostAppearance = .serif @State private var combinedText = "" @State private var hasBeenEdited: Bool = false let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() var body: some View { ZStack(alignment: .topLeading) { if combinedText.count == 0 { Text("Write…") .foregroundColor(Color(NSColor.placeholderTextColor)) - .padding(.horizontal, 5) + .padding(.horizontal, 16) + .padding(.vertical, 16) .font(.custom(appearance.rawValue, size: 17, relativeTo: .body)) } if post.appearance == "sans" { MacEditorTextView( text: $combinedText, isFirstResponder: combinedText.isEmpty, isEditable: true, font: NSFont(name: PostAppearance.sans.rawValue, size: 17), onEditingChanged: onEditingChanged, onCommit: onCommit, onTextChange: onTextChange ) } else if post.appearance == "wrap" || post.appearance == "mono" || post.appearance == "code" { MacEditorTextView( text: $combinedText, isFirstResponder: combinedText.isEmpty, isEditable: true, font: NSFont(name: PostAppearance.mono.rawValue, size: 17), onEditingChanged: onEditingChanged, onCommit: onCommit, onTextChange: onTextChange ) } else { MacEditorTextView( text: $combinedText, isFirstResponder: combinedText.isEmpty, isEditable: true, font: NSFont(name: PostAppearance.serif.rawValue, size: 17), onEditingChanged: onEditingChanged, onCommit: onCommit, onTextChange: onTextChange ) } } .background(Color(NSColor.controlBackgroundColor)) .onAppear(perform: { if post.title.isEmpty { self.combinedText = post.body } else { self.combinedText = "# \(post.title)\n\n\(post.body)" } }) .onReceive(timer) { _ in if !post.body.isEmpty && hasBeenEdited { DispatchQueue.main.async { LocalStorageManager.standard.saveContext() hasBeenEdited = false } } } } private func onEditingChanged() { hasBeenEdited = true } private func onTextChange(_ text: String) { extractTitle(text) if !updatingFromServer { if post.status == PostStatus.published.rawValue { post.status = PostStatus.edited.rawValue } if post.status == PostStatus.edited.rawValue, post.title == model.editor.initialPostTitle, post.body == model.editor.initialPostBody { post.status = PostStatus.published.rawValue } } if updatingFromServer { self.updatingFromServer = false } hasBeenEdited = true } private func onCommit() { if !post.body.isEmpty && hasBeenEdited { DispatchQueue.main.async { LocalStorageManager.standard.saveContext() } } hasBeenEdited = false } private func extractTitle(_ text: String) { var detectedTitle: String if text.hasPrefix("# ") { let endOfTitleIndex = text.firstIndex(of: "\n") ?? text.endIndex detectedTitle = String(text[..