diff --git a/MDViewer/ContentView.swift b/MDViewer/ContentView.swift
index b08d2bf..d3b28b7 100644
--- a/MDViewer/ContentView.swift
+++ b/MDViewer/ContentView.swift
@@ -5,18 +5,24 @@ struct ContentView: View {
let fileURL: URL?
let appearanceMode: AppearanceMode
let zoomLevel: Double
+ let theme: Theme
@State private var text: String
- init(document: MarkdownDocument, fileURL: URL?, appearanceMode: AppearanceMode, zoomLevel: Double) {
+ init(document: MarkdownDocument, fileURL: URL?, appearanceMode: AppearanceMode, zoomLevel: Double, theme: Theme) {
self.document = document
self.fileURL = fileURL
self.appearanceMode = appearanceMode
self.zoomLevel = zoomLevel
self._text = State(initialValue: document.text)
+ self.theme = theme
}
var body: some View {
- MarkdownWebView(text: text, appearanceMode: appearanceMode, zoomLevel: zoomLevel)
+ MarkdownWebView(
+ text: text,
+ zoomLevel: zoomLevel,
+ theme: theme
+ )
.onReceive(NotificationCenter.default.publisher(for: .reloadDocument)) { _ in
reload()
}
diff --git a/MDViewer/MDViewerApp.swift b/MDViewer/MDViewerApp.swift
index 63d1e8a..1bc71a2 100644
--- a/MDViewer/MDViewerApp.swift
+++ b/MDViewer/MDViewerApp.swift
@@ -22,6 +22,22 @@ enum AppearanceMode: String, CaseIterable {
struct MDViewerApp: App {
@AppStorage("appearanceMode") private var appearanceMode: String = AppearanceMode.system.rawValue
@AppStorage("zoomLevel") private var zoomLevel: Double = 1.0
+ @AppStorage("lightThemeID") private var lightThemeID: String = "github-light"
+ @AppStorage("darkThemeID") private var darkThemeID: String = "github-dark"
+
+ private func selectTheme() -> Theme {
+ let lightTheme = Theme.theme(for: lightThemeID, in: Theme.themes)
+ let darkTheme = Theme.theme(for: darkThemeID, in: Theme.themes)
+ switch AppearanceMode(rawValue: appearanceMode) {
+ case .light:
+ return lightTheme
+ case .dark:
+ return darkTheme
+ default:
+ let isDark = NSApp.effectiveAppearance.name == .darkAqua
+ return isDark ? darkTheme : lightTheme
+ }
+ }
var body: some Scene {
DocumentGroup(viewing: MarkdownDocument.self) { file in
@@ -29,7 +45,8 @@ struct MDViewerApp: App {
document: file.document,
fileURL: file.fileURL,
appearanceMode: AppearanceMode(rawValue: appearanceMode) ?? .system,
- zoomLevel: zoomLevel
+ zoomLevel: zoomLevel,
+ theme: selectTheme(),
)
}
.commands {
@@ -73,6 +90,14 @@ struct MDViewerApp: App {
}
}
}
+
+ Settings {
+ PreferencesView(
+ lightThemeID: $lightThemeID,
+ darkThemeID: $darkThemeID
+ )
+ }
+ .restorationBehavior(.disabled)
}
private func shortcut(for mode: AppearanceMode) -> KeyboardShortcut {
diff --git a/MDViewer/MarkdownWebView.swift b/MDViewer/MarkdownWebView.swift
index 69b7150..f993bef 100644
--- a/MDViewer/MarkdownWebView.swift
+++ b/MDViewer/MarkdownWebView.swift
@@ -3,38 +3,24 @@ import WebKit
struct MarkdownWebView: NSViewRepresentable {
let text: String
- let appearanceMode: AppearanceMode
let zoomLevel: Double
+ let theme: Theme
func makeNSView(context: Context) -> WKWebView {
let config = WKWebViewConfiguration()
config.preferences.setValue(true, forKey: "developerExtrasEnabled")
let webView = WKWebView(frame: .zero, configuration: config)
- webView.setValue(false, forKey: "drawsBackground")
- applyAppearance(to: webView)
webView.pageZoom = zoomLevel
loadContent(into: webView)
return webView
}
func updateNSView(_ webView: WKWebView, context: Context) {
- applyAppearance(to: webView)
webView.pageZoom = zoomLevel
loadContent(into: webView)
}
- private func applyAppearance(to webView: WKWebView) {
- switch appearanceMode {
- case .system:
- webView.appearance = nil
- case .light:
- webView.appearance = NSAppearance(named: .aqua)
- case .dark:
- webView.appearance = NSAppearance(named: .darkAqua)
- }
- }
-
private func loadContent(into webView: WKWebView) {
guard let templateURL = Bundle.main.url(forResource: "template", withExtension: "html"),
let markedURL = Bundle.main.url(forResource: "marked.min", withExtension: "js"),
@@ -48,9 +34,14 @@ struct MarkdownWebView: NSViewRepresentable {
.replacingOccurrences(of: "$", with: "\\$")
html = html
+ .replacingOccurrences(of: "{{THEME_CSS}}", with: theme.colors.cssVariables())
.replacingOccurrences(of: "{{MARKED_JS}}", with: markedJS)
.replacingOccurrences(of: "{{MARKDOWN_CONTENT}}", with: escaped)
webView.loadHTMLString(html, baseURL: templateURL.deletingLastPathComponent())
}
}
+
+#Preview {
+ MarkdownWebView(text: "# This is a test\n1. Test\n1. Test\n1. Test", zoomLevel: 1.0, theme: Theme.theme(for: "github-light", in: Theme.themes))
+}
diff --git a/MDViewer/PreferencesView.swift b/MDViewer/PreferencesView.swift
new file mode 100644
index 0000000..71d4334
--- /dev/null
+++ b/MDViewer/PreferencesView.swift
@@ -0,0 +1,84 @@
+import SwiftUI
+
+struct PreferencesView: View {
+ @AppStorage("appearanceMode") private var appearanceMode: String = AppearanceMode.system.rawValue
+ @AppStorage("zoomLevel") private var zoomLevel: Double = 1.0
+ @Binding var lightThemeID: String
+ @Binding var darkThemeID: String
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 20) {
+ Section {
+ Picker("Appearance", selection: $appearanceMode) {
+ ForEach(AppearanceMode.allCases, id: \.rawValue) { mode in
+ Text(mode.label).tag(mode.rawValue)
+ }
+ }
+ .pickerStyle(.segmented)
+ }
+
+ Section {
+ Picker("Light Theme", selection: $lightThemeID) {
+ ForEach(Theme.themes) { theme in
+ Text(theme.name).tag(theme.id)
+ }
+ }
+ }
+
+ Section {
+ Picker("Dark Theme", selection: $darkThemeID) {
+ ForEach(Theme.themes) { theme in
+ Text(theme.name).tag(theme.id)
+ }
+ }
+ }
+
+ Section {
+ HStack(alignment: .center, spacing: 10) {
+ Text("Zoom")
+ Slider(
+ value: $zoomLevel,
+ in: 0.25...1.75,
+ step: 0.15
+ )
+ Text(String(format: "%.0f", zoomLevel * 100) + "%")
+ .font(.caption)
+ .foregroundColor(.secondary)
+ }
+ }
+
+ Divider()
+
+ HStack {
+ Spacer()
+ Button("Reset to Defaults") {
+ appearanceMode = AppearanceMode.system.rawValue
+ lightThemeID = "github-light"
+ darkThemeID = "github-dark"
+ zoomLevel = 1.0
+ }
+ }
+ }
+ .onChange(of: appearanceMode) { _, newValue in
+ updateAppAppearance(newValue)
+ }
+ .padding(20)
+ .frame(width: 300)
+ }
+
+ private func updateAppAppearance(_ mode: String) {
+ let appMode = AppearanceMode(rawValue: mode) ?? .system
+ switch appMode {
+ case .system:
+ NSApp.appearance = nil
+ case .light:
+ NSApp.appearance = NSAppearance(named: .aqua)
+ case .dark:
+ NSApp.appearance = NSAppearance(named: .darkAqua)
+ }
+ }
+}
+
+#Preview {
+ PreferencesView(lightThemeID: .constant("github-light"), darkThemeID: .constant("github-dark"))
+}
diff --git a/MDViewer/Resources/template.html b/MDViewer/Resources/template.html
index 854faca..ce58e5a 100644
--- a/MDViewer/Resources/template.html
+++ b/MDViewer/Resources/template.html
@@ -5,27 +5,7 @@