diff --git a/Shared/WriteFreely_MultiPlatformApp.swift b/Shared/WriteFreely_MultiPlatformApp.swift index c05344a..bca2dd9 100644 --- a/Shared/WriteFreely_MultiPlatformApp.swift +++ b/Shared/WriteFreely_MultiPlatformApp.swift @@ -1,147 +1,149 @@ import SwiftUI #if os(macOS) import Sparkle #endif @main struct CheckForDebugModifier { static func main() { #if os(macOS) if NSEvent.modifierFlags.contains(.shift) { // Clear the launch-to-last-draft values to load a new draft. UserDefaults.standard.setValue(false, forKey: "showAllPostsFlag") UserDefaults.standard.setValue(nil, forKey: "selectedCollectionURL") UserDefaults.standard.setValue(nil, forKey: "lastDraftURL") } else { // No-op } #endif WriteFreely_MultiPlatformApp.main() } } struct WriteFreely_MultiPlatformApp: App { @StateObject private var model = WriteFreelyModel() #if os(macOS) // swiftlint:disable:next weak_delegate @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate @State private var selectedTab = 0 #endif var body: some Scene { WindowGroup { ContentView() .onAppear(perform: { if model.editor.showAllPostsFlag { DispatchQueue.main.async { self.model.selectedCollection = nil self.model.showAllPosts = true } } else { DispatchQueue.main.async { self.model.selectedCollection = model.editor.fetchSelectedCollectionFromAppStorage() self.model.showAllPosts = false } } DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { if model.editor.lastDraftURL != nil { self.model.selectedPost = model.editor.fetchLastDraftFromAppStorage() } else { createNewLocalPost() } } }) .environmentObject(model) .environment(\.managedObjectContext, LocalStorageManager.persistentContainer.viewContext) // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. } .commands { #if os(macOS) CommandGroup(after: .appInfo, addition: { Button("Check For Updates") { SUUpdater.shared()?.checkForUpdates(self) } }) #endif CommandGroup(replacing: .newItem, addition: { Button("New Post") { createNewLocalPost() } .keyboardShortcut("n", modifiers: [.command]) }) CommandGroup(after: .newItem) { Button("Refresh Posts") { DispatchQueue.main.async { model.fetchUserCollections() model.fetchUserPosts() } } .disabled(!model.account.isLoggedIn) .keyboardShortcut("r", modifiers: [.command]) } SidebarCommands() #if os(macOS) PostCommands(model: model) #endif CommandGroup(after: .help) { Button("Visit Support Forum") { #if os(macOS) NSWorkspace().open(model.helpURL) #else UIApplication.shared.open(model.helpURL) #endif } } + ToolbarCommands() + TextEditingCommands() } #if os(macOS) Settings { TabView(selection: $selectedTab) { MacAccountView() .environmentObject(model) .tabItem { Image(systemName: "person.crop.circle") Text("Account") } .tag(0) MacPreferencesView(preferences: model.preferences) .tabItem { Image(systemName: "gear") Text("Preferences") } .tag(1) MacUpdatesView() .tabItem { Image(systemName: "arrow.down.circle") Text("Updates") } .tag(2) } .frame(minWidth: 500, maxWidth: 500, minHeight: 200) .padding() // .preferredColorScheme(preferences.selectedColorScheme) // See PreferencesModel for info. } #endif } private func createNewLocalPost() { withAnimation { // Un-set the currently selected post self.model.selectedPost = nil // Navigate to the Drafts list self.model.showAllPosts = false self.model.selectedCollection = nil } // Create the new-post managed object let managedPost = model.editor.generateNewLocalPost(withFont: model.preferences.font) withAnimation { // Set it as the selectedPost DispatchQueue.main.asyncAfter(deadline: .now()) { self.model.selectedPost = managedPost } } } } diff --git a/macOS/PostEditor/MacEditorTextView.swift b/macOS/PostEditor/MacEditorTextView.swift index 96bacc9..e5f8808 100644 --- a/macOS/PostEditor/MacEditorTextView.swift +++ b/macOS/PostEditor/MacEditorTextView.swift @@ -1,208 +1,212 @@ // 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.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() - paragraphStyle.lineSpacing = 8.5 + let lineHeightMultiple: CGFloat = 1.5 + paragraphStyle.lineHeightMultiple = lineHeightMultiple 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 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 } }