Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions Sources/Markee/MarkeeApp.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ struct MarkeeApp: App {
}
.defaultSize(width: 1000, height: 800)
.commands {
CommandGroup(after: .appInfo) {
Button("Check for Updates…") {
Updater.shared.checkForUpdatesMenuAction()
}
}
CommandGroup(after: .newItem) {
Divider()
Button("Install Command Line Tool…") {
Expand Down Expand Up @@ -60,6 +65,15 @@ struct MarkeeApp: App {
NotificationCenter.default.post(name: .openInEditor, object: nil)
}
.keyboardShortcut("E", modifiers: [.command, .option])
Divider()
Button("Copy Markdown Source") {
NotificationCenter.default.post(name: .copyMarkdownSource, object: nil)
}
.keyboardShortcut("C", modifiers: [.command, .shift])
Button("Reveal in Finder") {
NotificationCenter.default.post(name: .revealInFinder, object: nil)
}
.keyboardShortcut("R", modifiers: [.command, .shift])
}
CommandGroup(replacing: .printItem) {
Button("Print…") {
Expand Down Expand Up @@ -98,6 +112,8 @@ extension Notification.Name {
static let findNext = Notification.Name("MarkeeFindNext")
static let findPrevious = Notification.Name("MarkeeFindPrevious")
static let reloadFile = Notification.Name("MarkeeReloadFile")
static let copyMarkdownSource = Notification.Name("MarkeeCopyMarkdownSource")
static let revealInFinder = Notification.Name("MarkeeRevealInFinder")
}

final class AppDelegate: NSObject, NSApplicationDelegate {
Expand All @@ -109,6 +125,10 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
false
}

func applicationDidFinishLaunching(_ notification: Notification) {
Updater.shared.checkOnLaunch()
}

static func installCLI() {
guard let bundleCLI = Bundle.main.url(forResource: "cli/markee", withExtension: nil) else {
NSSound.beep(); return
Expand Down
14 changes: 13 additions & 1 deletion Sources/Markee/MarkeeTitlebar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ struct MarkeeTitlebar: View {
Button(action: onToggleOutline) {
Image(systemName: "sidebar.left")
.font(.system(size: 14, weight: .regular))
.foregroundStyle(Color.secondary)
.foregroundStyle(toggleIconColor)
.frame(width: 24, height: 24)
.contentShape(Rectangle())
}
Expand Down Expand Up @@ -73,6 +73,18 @@ struct MarkeeTitlebar: View {
})
}

/// Explicit appearance-aware color for the outline-toggle icon, matching
/// the titlebar's own `NSColor`-backed color treatment.
private var toggleIconColor: Color {
Color(nsColor: NSColor(name: nil) { appearance in
if appearance.bestMatch(from: [.darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua]) != nil {
return NSColor(red: 0x8c/255.0, green: 0x8e/255.0, blue: 0x97/255.0, alpha: 1)
} else {
return NSColor(red: 0x6a/255.0, green: 0x6c/255.0, blue: 0x74/255.0, alpha: 1)
}
})
}

private var titlebarTopColor: Color {
Color(nsColor: NSColor(name: nil) { appearance in
if appearance.bestMatch(from: [.darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua]) != nil {
Expand Down
39 changes: 39 additions & 0 deletions Sources/Markee/MarkeeWebView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import WebKit

/// WKWebView subclass that appends Markee's own items to the native
/// right-click context menu. WKWebView owns its context menu, so SwiftUI's
/// `.contextMenu` cannot reach it — we extend `willOpenMenu` instead.
final class MarkeeWebView: WKWebView {
/// Weak to avoid a retain cycle: PreviewController owns this webView.
weak var controller: PreviewController?

override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) {
super.willOpenMenu(menu, with: event)

// No isKeyWindow guard needed: a right-click targets this exact
// window directly (unlike the broadcast-to-all-windows menu commands).
menu.addItem(.separator())

let copyItem = NSMenuItem(
title: "Copy Markdown Source",
action: #selector(copyMarkdownSourceAction),
keyEquivalent: "")
copyItem.target = self
menu.addItem(copyItem)

let revealItem = NSMenuItem(
title: "Reveal in Finder",
action: #selector(revealInFinderAction),
keyEquivalent: "")
revealItem.target = self
menu.addItem(revealItem)
}

@objc private func copyMarkdownSourceAction() {
controller?.copyMarkdownSource()
}

@objc private func revealInFinderAction() {
controller?.revealInFinder()
}
}
44 changes: 42 additions & 2 deletions Sources/Markee/PreviewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ final class PreviewController: NSObject, ObservableObject, WKScriptMessageHandle
@Published var findQuery: String = ""
@Published var findNotFound: Bool = false

let webView: WKWebView
let webView: MarkeeWebView
let bundleHandler = BundleSchemeHandler()
let docHandler: DocSchemeHandler

Expand Down Expand Up @@ -69,9 +69,11 @@ final class PreviewController: NSObject, ObservableObject, WKScriptMessageHandle
config.setURLSchemeHandler(bundleHandler, forURLScheme: BundleSchemeHandler.scheme)
config.setURLSchemeHandler(docHandler, forURLScheme: DocSchemeHandler.scheme)

self.webView = WKWebView(frame: .zero, configuration: config)
self.webView = MarkeeWebView(frame: .zero, configuration: config)
super.init()

self.webView.controller = self

userContent.add(self, name: "markee")
self.webView.navigationDelegate = self
self.webView.allowsBackForwardNavigationGestures = false
Expand Down Expand Up @@ -116,6 +118,12 @@ final class PreviewController: NSObject, ObservableObject, WKScriptMessageHandle
NotificationCenter.default.addObserver(
self, selector: #selector(handleReload),
name: .reloadFile, object: nil)
NotificationCenter.default.addObserver(
self, selector: #selector(handleCopyMarkdownSource),
name: .copyMarkdownSource, object: nil)
NotificationCenter.default.addObserver(
self, selector: #selector(handleRevealInFinder),
name: .revealInFinder, object: nil)
}

deinit {
Expand Down Expand Up @@ -235,6 +243,38 @@ final class PreviewController: NSObject, ObservableObject, WKScriptMessageHandle
loadFromDisk(reason: "manual")
}

/// ⌘⇧C — copy the file's raw Markdown to the clipboard. Reads fresh from
/// disk via the renderer's read path so the clipboard never holds a stale
/// snapshot. Public so MarkeeWebView's context menu can call it directly.
func copyMarkdownSource() {
let source: String
do {
source = try Self.readFileWithFallback(at: fileURL)
} catch {
self.errorBanner = "Couldn't read \(fileURL.lastPathComponent): \(error.localizedDescription)"
return
}
let pasteboard = NSPasteboard.general
pasteboard.clearContents()
pasteboard.setString(source, forType: .string)
}

/// ⌘⇧R — reveal the current file in Finder. Public so MarkeeWebView's
/// context menu can call it directly.
func revealInFinder() {
NSWorkspace.shared.activateFileViewerSelecting([fileURL])
}

@objc private func handleCopyMarkdownSource() {
guard webView.window?.isKeyWindow == true else { return }
copyMarkdownSource()
}

@objc private func handleRevealInFinder() {
guard webView.window?.isKeyWindow == true else { return }
revealInFinder()
}

/// ⌘⇧G — mirror of handleFindNext, searching backward.
@objc private func handleFindPrevious() {
guard webView.window?.isKeyWindow == true else { return }
Expand Down
6 changes: 5 additions & 1 deletion Sources/Markee/PreviewView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,11 @@ private struct PreviewContent: View {
private func configureWindow(_ window: NSWindow) {
window.titlebarAppearsTransparent = true
window.titleVisibility = .hidden
window.styleMask.insert(.fullSizeContentView)
// Only assign styleMask when it actually changes — reassigning it
// forces an NSThemeFrame rebuild. This runs on every becomeKey.
if !window.styleMask.contains(.fullSizeContentView) {
window.styleMask.insert(.fullSizeContentView)
}
window.isMovableByWindowBackground = false
}
}
Expand Down
Loading
Loading