Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
26 changes: 12 additions & 14 deletions CoreEditor/src/@light/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ window.config = config;
const theme = new Compartment;
window.dynamics = { theme };

// Drive the theme from the system color scheme; the preview extension has no other source
const colorSchemeQuery = matchMedia('(prefers-color-scheme: dark)');
const initialTheme = preferredTheme();

colorSchemeQuery.addEventListener('change', () => {
setTheme(preferredTheme());
});

const extensions = [
// Basic
highlightSpecialChars(),
Expand All @@ -39,7 +47,7 @@ const extensions = [

// Styling
classHighlighters,
theme.of(loadTheme(config.theme)),
theme.of(initialTheme),
renderExtensions,
linkStyles,
];
Expand All @@ -48,7 +56,7 @@ const doc = config.text;
const parent = document.querySelector('#editor') ?? document.body;

window.editor = new EditorView({ doc, parent, extensions });
setUp(config, loadTheme(config.theme).colors);
setUp(config, initialTheme.colors);

// Makes sure the content doesn't have unwanted inset
scrollIntoView(0);
Expand All @@ -59,10 +67,6 @@ scrollIntoView(0);
const bridge = window as any;
const storage: { scrollbarOffset?: number } = {};

bridge.setTheme = (name: string) => {
setTheme(loadTheme(name));
};

bridge.startDragging = (original: number) => {
// scrollbarOffset is the distance between the top of the scrollbar and the mouse location
const location = convertToLocal(original);
Expand Down Expand Up @@ -90,14 +94,8 @@ bridge.cancelDragging = () => {
// Zoom in and out using the trackpad
enablePinchZoom(bridge as PinchZoomBridge);

// There're only two themes in the preview extension,
// use a simplified "loadTheme" to avoid bundling unused themes.
function loadTheme(name: string) {
if (name === 'github-dark') {
return GitHubDark();
} else {
return GitHubLight();
}
function preferredTheme() {
return colorSchemeQuery.matches ? GitHubDark() : GitHubLight();
}

function scrollerElement(): HTMLElement | null {
Expand Down
43 changes: 43 additions & 0 deletions MarkEditCore/Sources/Extensions/UserDefaults+Extension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// UserDefaults+Extension.swift
//
// Created by cyan on 6/17/26.
//

import Foundation

public enum ForcedColorScheme: String {
case system
case light
case dark
}

public extension UserDefaults {
static var forcedColorScheme: ForcedColorScheme {
get {
guard let value = appGroup?.string(forKey: forcedColorSchemeKey) else {
return .system
}

return ForcedColorScheme(rawValue: value) ?? .system
}
set {
guard newValue != .system else {
appGroup?.removeObject(forKey: forcedColorSchemeKey)
return
}

appGroup?.set(newValue.rawValue, forKey: forcedColorSchemeKey)
}
}
}

// MARK: - Private

private extension UserDefaults {
static var appGroup: UserDefaults? {
UserDefaults(suiteName: "group.app.cyan.markedit")
}

static let forcedColorSchemeKey = "forcedColorScheme"
}
26 changes: 26 additions & 0 deletions MarkEditMac/Sources/Main/AppDocumentController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
//

import AppKit
import MarkEditCore
import MarkEditKit

/**
Expand Down Expand Up @@ -45,6 +46,18 @@ final class AppDocumentController: NSDocumentController {
openPanel.showsHiddenFiles = AppPreferences.General.showHiddenFiles
openPanel.relayoutAccessoryView()

let appearanceObservation = NSApp.observe(\.effectiveAppearance) { [weak self] _, _ in
Task { @MainActor in
self?.appearanceDidChange()
}
}
Comment thread
cyanzhong marked this conversation as resolved.

defer {
appearanceObservation.invalidate()
UserDefaults.forcedColorScheme = .system
}

appearanceDidChange()
return await super.beginOpenPanel(openPanel, forTypes: inTypes)
}

Expand Down Expand Up @@ -85,6 +98,19 @@ final class AppDocumentController: NSDocumentController {

// MARK: - Private

private extension AppDocumentController {
func appearanceDidChange() {
switch AppPreferences.General.appearance {
case .system:
UserDefaults.forcedColorScheme = .system
case .light:
UserDefaults.forcedColorScheme = .light
case .dark:
UserDefaults.forcedColorScheme = .dark
}
}
}

private extension NSOpenPanel {
/// Re-layouts the accessory view to work around internal AppKit bugs.
///
Expand Down
4 changes: 2 additions & 2 deletions PreviewExtension/PreviewViewConfig.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,10 @@ extension WKWebViewConfiguration {
}

extension EditorConfig {
static func previewConfig(fileData: Data, theme: String) -> Self {
static func previewConfig(fileData: Data) -> Self {
.init(
text: fileData.toString() ?? "",
theme: theme,
theme: "github-light", // Ignored by @light editor
fontFace: WebFontFace(family: "ui-monospace", weight: nil, style: nil),
fontSize: 12,
showLineNumbers: false,
Expand Down
47 changes: 28 additions & 19 deletions PreviewExtension/PreviewViewController+UI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,42 @@
//

import AppKit
import MarkEditCore

extension PreviewViewController {
var isDarkMode: Bool {
switch NSApp.effectiveAppearance.name {
case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark:
return true
default:
return false
}
}

var isRightToLeft: Bool {
view.userInterfaceLayoutDirection == .rightToLeft
}

var effectiveTheme: String {
isDarkMode ? "github-dark" : "github-light"
func updateAppearance() {
if isDarkMode {
view.layer?.backgroundColor = NSColor(red: 13.0 / 255, green: 17.0 / 255, blue: 22.0 / 255, alpha: 1).cgColor
webView.appearance = NSAppearance(named: .darkAqua)
} else {
view.layer?.backgroundColor = NSColor.white.cgColor
webView.appearance = NSAppearance(named: .aqua)
}
}
}

func updateBackgroundColor() {
// To hide the transparent background of the scrolling overflow
view.layer?.backgroundColor = (isDarkMode ? NSColor(red: 13.0 / 255, green: 17.0 / 255, blue: 22.0 / 255, alpha: 1) : NSColor.white).cgColor
}
// MARK: - Private

func updateEditorTheme() {
// To keep the app size smaller, we don't have bridge here,
// construct script literals directly.
webView.evaluateJavaScript("setTheme(`\(effectiveTheme)`)")
private extension PreviewViewController {
var isDarkMode: Bool {
switch UserDefaults.forcedColorScheme {
case .dark:
return true
case .light:
return false
case .system:
break
}

switch NSApp.effectiveAppearance.name {
case .darkAqua, .vibrantDark, .accessibilityHighContrastDarkAqua, .accessibilityHighContrastVibrantDark:
return true
default:
return false
}
}
}
8 changes: 3 additions & 5 deletions PreviewExtension/PreviewViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,16 +71,15 @@ final class PreviewViewController: NSViewController {
view.layer?.cornerRadius = 6

addEventMonitorsForDragging()
updateBackgroundColor()
updateAppearance()

appearanceObservation = NSApp.observe(\.effectiveAppearance) { [weak self] _, _ in
guard let self else {
return
}

Task { @MainActor in
self.updateBackgroundColor()
self.updateEditorTheme()
self.updateAppearance()
}
}
}
Expand All @@ -103,8 +102,7 @@ extension PreviewViewController: QLPreviewingController {
previewDirectoryURL = fileURL.deletingLastPathComponent()

let config = EditorConfig.previewConfig(
fileData: try Data(contentsOf: fileURL),
theme: effectiveTheme
fileData: try Data(contentsOf: fileURL)
)

let html = ([config.toHtml] + userStyles).joined(separator: "\n\n")
Expand Down