18 Commits
Author SHA1 Message Date
Darin Kelkhoff be5995bb55 release 1.1.5 (adding dmg) 2026-06-08 11:54:51 -05:00
Darin KelkhoffandClaude Sonnet 4.6 3a3884155f release: derive CURRENT_PROJECT_VERSION from MARKETING_VERSION, fix DMG staging
Build number is computed as major*10000+minor*100+patch so Sparkle always
sees a strictly-increasing CFBundleVersion without a separate manual bump.

DMG is now staged from a clean temp directory containing only the .app,
so xcodebuild export artifacts (DistributionSummary.plist, etc.) no longer
appear in the mounted volume.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:54:27 -05:00
Darin Kelkhoff 97f398276a release: update appcast for v1.1.4 2026-06-08 11:38:07 -05:00
Darin Kelkhoff f60bb0aa53 release 1.1.4 2026-06-08 11:29:14 -05:00
Darin Kelkhoff 654a134753 widen hero and features max-width to 1200px 2026-06-08 11:28:28 -05:00
Darin Kelkhoff e8ad868277 add .nojekyll to prevent Jekyll from interfering with static assets 2026-06-08 11:27:12 -05:00
Darin KelkhoffandClaude Sonnet 4.6 3fbbe57c6f add app icon and screenshot to landing page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:25:41 -05:00
Darin KelkhoffandClaude Sonnet 4.6 6e71826787 add DMG creation to release recipe and GitHub Pages landing page
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 11:23:47 -05:00
Darin KelkhoffandClaude Sonnet 4.6 e2a6aeafb2 guard release recipe against re-releasing an already-published version
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 10:10:39 -05:00
Darin Kelkhoff 2ed08c1e86 release: update appcast for v1.1.3 2026-06-08 10:07:18 -05:00
Darin KelkhoffandClaude Sonnet 4.6 e09b6713cc bump to 1.1.3; set CURRENT_PROJECT_VERSION=3 so Sparkle detects updates
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 09:54:00 -05:00
Darin Kelkhoff a793a26977 release: update appcast for v1.1.2 2026-06-08 09:50:34 -05:00
Darin KelkhoffandClaude Sonnet 4.6 9efc6b1518 bump to 1.1.2; fix CFBundleShortVersionString in project.yml to survive xcodegen
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 09:48:53 -05:00
Darin KelkhoffandClaude Sonnet 4.6 d57ca37e86 fix CFBundleShortVersionString to use MARKETING_VERSION build setting
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 09:47:41 -05:00
Darin KelkhoffandClaude Sonnet 4.6 b451f36d71 add LSApplicationCategoryType to suppress archive warning
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 09:46:27 -05:00
Darin Kelkhoff d0e74cc99a release: update appcast for v1.1.1 2026-06-08 09:39:31 -05:00
Darin KelkhoffandClaude Sonnet 4.6 3c5cd61d20 add Help window and About credits; bump to 1.1.1
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-08 09:37:30 -05:00
Darin Kelkhoff d2b8c02ca5 release: update appcast for v1.1.0 2026-06-08 08:48:19 -05:00
13 changed files with 1505 additions and 9 deletions

No files matched your search

+10 -2
View File
@@ -5,13 +5,21 @@
<link>https://github.com/darinkelkhoff/markdownViewr</link>
<description>markdownViewr releases</description>
<language>en</language>
<item>
<title>1.1.3</title>
<pubDate>Mon, 08 Jun 2026 10:07:17 -0500</pubDate>
<sparkle:version>3</sparkle:version>
<sparkle:shortVersionString>1.1.3</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>13.0</sparkle:minimumSystemVersion>
<enclosure url="https://github.com/darinkelkhoff/markdownViewr/releases/download/v1.1.4/markdownViewr-1.1.4.zip" length="4202362" type="application/octet-stream" sparkle:edSignature="R5jaamgXCpnoMHHRU4H+WsT+VNfEhRZEMIdXchNKXgzPZyCEMV4lRbEr7TJN/TvxxmiSEJorOXeJjhjH5tMzCA=="/>
</item>
<item>
<title>1.0</title>
<pubDate>Mon, 08 Jun 2026 08:18:43 -0500</pubDate>
<sparkle:version>1</sparkle:version>
<sparkle:shortVersionString>1.0</sparkle:shortVersionString>
<sparkle:shortVersionString>1.1.2</sparkle:shortVersionString>
<sparkle:minimumSystemVersion>13.0</sparkle:minimumSystemVersion>
<enclosure url="https://github.com/darinkelkhoff/markdownViewr/releases/download/v1.1.0/markdownViewr-1.1.0.zip" length="4188027" type="application/octet-stream" sparkle:edSignature="c1j2HlKXVEBF6bsfjG4DUNNhwOwOGIb+24OJoA7AKKSoBsZW1XnVqIuOlHWoKSiv8pL5lpwysOvaMK8hBc0DDg=="/>
<enclosure url="https://github.com/darinkelkhoff/markdownViewr/releases/download/v1.1.2/markdownViewr-1.1.2.zip" length="4202333" type="application/octet-stream" sparkle:edSignature="FZ19CRV6Ib9Dbpt7Nr5PK4tCcaLJ8rHPlBOr2eGLjV1GnJxBETSnCp0FCEiOsvgWqpgsOlJc/eEQHDbIK/8UBw=="/>
</item>
</channel>
</rss>
View File
Whitespace-only changes.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1010 KiB

+167
View File
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>markdownViewr</title>
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
background: #f5f5f7;
color: #1d1d1f;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 48px 24px;
}
.hero {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
max-width: 1200px;
}
.icon {
width: 128px;
height: 128px;
margin-bottom: 24px;
border-radius: 22%;
box-shadow: 0 8px 30px rgba(0,0,0,0.15);
}
.screenshot-wrap {
margin-top: 48px;
width: 100%;
max-width: 780px;
}
.screenshot-wrap img {
width: 100%;
border-radius: 12px;
box-shadow: 0 8px 40px rgba(0,0,0,0.18);
}
h1 {
font-size: 2.4rem;
font-weight: 700;
letter-spacing: -0.02em;
margin-bottom: 12px;
}
.tagline {
font-size: 1.15rem;
color: #555;
line-height: 1.5;
margin-bottom: 32px;
}
.download-btn {
display: inline-flex;
align-items: center;
gap: 8px;
background: #0071e3;
color: #fff;
text-decoration: none;
font-size: 1rem;
font-weight: 600;
padding: 14px 28px;
border-radius: 980px;
transition: background 0.15s;
}
.download-btn:hover { background: #0077ed; }
.download-btn svg {
width: 18px;
height: 18px;
fill: currentColor;
}
.req {
margin-top: 12px;
font-size: 0.82rem;
color: #888;
}
.features {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 10px;
margin-top: 40px;
max-width: 1200px;
}
.feature {
background: #fff;
border-radius: 12px;
padding: 10px 16px;
font-size: 0.88rem;
color: #444;
box-shadow: 0 1px 3px rgba(0,0,0,0.08);
}
footer {
margin-top: 56px;
font-size: 0.8rem;
color: #aaa;
}
footer a { color: #aaa; }
@media (prefers-color-scheme: dark) {
body { background: #1c1c1e; color: #f5f5f7; }
.tagline { color: #aaa; }
.feature { background: #2c2c2e; color: #ccc; box-shadow: none; }
.req { color: #666; }
footer, footer a { color: #555; }
}
</style>
</head>
<body>
<div class="hero">
<img class="icon" src="icon.png" alt="markdownViewr icon">
<h1>markdownViewr</h1>
<p class="tagline">
A fast, beautiful markdown viewer for macOS.<br>
Open any markdown file. Live-reloads as you edit.
</p>
<a class="download-btn"
href="https://github.com/darinkelkhoff/markdownViewr/releases/latest/download/markdownViewr.dmg">
<svg viewBox="0 0 20 20"><path d="M10 2a1 1 0 011 1v9.586l2.293-2.293a1 1 0 111.414 1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 111.414-1.414L9 12.586V3a1 1 0 011-1zM3 16a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1z"/></svg>
Download for Mac
</a>
<p class="req">macOS 13 Ventura or later &nbsp;·&nbsp; Free</p>
<div class="features">
<span class="feature">GitHub Flavored Markdown</span>
<span class="feature">Syntax-highlighted code blocks</span>
<span class="feature">Fully customizable themes</span>
<span class="feature">Live file watching</span>
<span class="feature">Vim navigation keys</span>
<span class="feature">Find in document</span>
<span class="feature">YAML frontmatter support</span>
<span class="feature">Open in external editor</span>
<span class="feature">Automatic updates</span>
</div>
</div>
<div class="screenshot-wrap">
<img src="screenshot.png" alt="markdownViewr showing a markdown document">
</div>
<footer>
<a href="https://github.com/darinkelkhoff/markdownViewr">View on GitHub</a>
</footer>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 521 KiB

@@ -0,0 +1,732 @@
# Markdown Extensions Implementation Plan
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** Add four toggleable inline markdown extensions (highlight, superscript, subscript, underline) with a Markdown settings tab, a theme-aware highlight color, and a post-processing HTML pass.
**Architecture:** A new `MarkdownExtensions` struct holds four booleans stored in UserDefaults via `ThemeManager`. `MarkdownDocument.convertToHTML` runs a post-processing pass (skipping `<pre>` blocks) after the swift-markdown AST walk. `ThemeColors` gains `highlightBackground`/`highlightText` fields (with a custom decoder for backward compatibility) that feed CSS variables used by `mark {}` in the HTML template.
**Tech Stack:** SwiftUI, AppKit (NSWindow), swift-markdown 0.7.3, NSRegularExpression, UserDefaults, XcodeGen (sources glob — new Swift files auto-included, no regeneration needed).
---
### Task 1: MarkdownExtensions struct + ThemeColors highlight fields
**Files:**
- Create: `markdownViewr/MarkdownExtensions.swift`
- Modify: `markdownViewr/Theme.swift`
- [ ] **Step 1: Create `markdownViewr/MarkdownExtensions.swift`**
```swift
import Foundation
struct MarkdownExtensions: Codable {
var highlight: Bool = true
var superscript: Bool = true
var subscript_: Bool = true // trailing underscore: `subscript` is a Swift keyword
var underline: Bool = true
}
```
- [ ] **Step 2: Add `highlightBackground` and `highlightText` to `ThemeColors` in `Theme.swift`**
Add the two new properties after `blockquoteBackground`:
```swift
var blockquoteBackground: String
var highlightBackground: String
var highlightText: String
```
Update the `init(...)` signature and body to include them after `blockquoteBackground`:
```swift
init(
background: String = "#1e1e2e",
text: String = "#cdd6f4",
heading1: String = "#cba6f7",
heading2: String = "#89b4fa",
heading3: String = "#89b4fa",
heading4: String = "#74c7ec",
heading5: String = "#74c7ec",
heading6: String = "#74c7ec",
link: String = "#89dceb",
codeBackground: String = "#181825",
codeText: String = "#a6e3a1",
blockquoteBorder: String = "#cba6f7",
blockquoteBackground: String = "#252535",
highlightBackground: String = "#ffd700",
highlightText: String = "#1a1a1a"
) {
self.background = background
self.text = text
self.heading1 = heading1
self.heading2 = heading2
self.heading3 = heading3
self.heading4 = heading4
self.heading5 = heading5
self.heading6 = heading6
self.link = link
self.codeBackground = codeBackground
self.codeText = codeText
self.blockquoteBorder = blockquoteBorder
self.blockquoteBackground = blockquoteBackground
self.highlightBackground = highlightBackground
self.highlightText = highlightText
}
```
- [ ] **Step 3: Add explicit `CodingKeys` and custom `init(from:)` to `ThemeColors` for backward compatibility**
Existing user theme JSONs don't have these fields. Swift's auto-synthesized decoder throws on missing keys, so add the following inside `struct ThemeColors`:
```swift
enum CodingKeys: String, CodingKey {
case background, text
case heading1, heading2, heading3, heading4, heading5, heading6
case link, codeBackground, codeText
case blockquoteBorder, blockquoteBackground
case highlightBackground, highlightText
}
init(from decoder: Decoder) throws {
let c = try decoder.container(keyedBy: CodingKeys.self)
background = try c.decode(String.self, forKey: .background)
text = try c.decode(String.self, forKey: .text)
heading1 = try c.decode(String.self, forKey: .heading1)
heading2 = try c.decode(String.self, forKey: .heading2)
heading3 = try c.decode(String.self, forKey: .heading3)
heading4 = try c.decode(String.self, forKey: .heading4)
heading5 = try c.decode(String.self, forKey: .heading5)
heading6 = try c.decode(String.self, forKey: .heading6)
link = try c.decode(String.self, forKey: .link)
codeBackground = try c.decode(String.self, forKey: .codeBackground)
codeText = try c.decode(String.self, forKey: .codeText)
blockquoteBorder = try c.decode(String.self, forKey: .blockquoteBorder)
blockquoteBackground = try c.decode(String.self, forKey: .blockquoteBackground)
highlightBackground = try c.decodeIfPresent(String.self, forKey: .highlightBackground) ?? "#ffd700"
highlightText = try c.decodeIfPresent(String.self, forKey: .highlightText) ?? "#1a1a1a"
}
```
- [ ] **Step 4: Build to verify**
```bash
just build
```
Expected: build succeeds with no errors.
- [ ] **Step 5: Commit**
```bash
git add markdownViewr/MarkdownExtensions.swift markdownViewr/Theme.swift
git commit -m "add MarkdownExtensions struct and highlight color fields to ThemeColors"
```
---
### Task 2: ThemeManager — extensions property and CSS variables
**Files:**
- Modify: `markdownViewr/ThemeManager.swift`
- [ ] **Step 1: Add `markdownExtensions` as the last `@Published` property in `ThemeManager`**
Add after the `frontmatterMode` property block (around line 21–26):
```swift
@Published var markdownExtensions: MarkdownExtensions {
didSet {
if let data = try? JSONEncoder().encode(markdownExtensions) {
UserDefaults.standard.set(data, forKey: "markdownExtensions")
}
}
}
```
- [ ] **Step 2: Initialize `markdownExtensions` in `ThemeManager.init()`**
In the `init()` body, after the `frontmatterMode` initialization, add:
```swift
if let data = UserDefaults.standard.data(forKey: "markdownExtensions"),
let ext = try? JSONDecoder().decode(MarkdownExtensions.self, from: data) {
self.markdownExtensions = ext
} else {
self.markdownExtensions = MarkdownExtensions()
}
```
- [ ] **Step 3: Add `--highlight-bg` and `--highlight-text` to `generateCSS`**
In `generateCSS(for:)`, inside the `:root {` block, add these two lines after `--blockquote-bg`:
```swift
--highlight-bg: \(theme.colors.highlightBackground);
--highlight-text: \(theme.colors.highlightText);
```
- [ ] **Step 4: Build to verify**
```bash
just build
```
Expected: build succeeds.
---
### Task 3: HTML template — `mark` CSS rule
**Files:**
- Modify: `markdownViewr/Resources/template.html`
- [ ] **Step 1: Add `mark` rule to `base-css` in `template.html`**
After the `del { opacity: 0.6; }` rule (around line 223–225), add:
```css
mark {
background-color: var(--highlight-bg);
color: var(--highlight-text);
border-radius: 2px;
padding: 0 2px;
}
```
Note: find marks created by the in-page find feature use inline styles that override this rule, so there is no conflict.
- [ ] **Step 2: Build to verify**
```bash
just build
```
Expected: build succeeds.
---
### Task 4: Update built-in theme JSON files
**Files:**
- Modify: all 20 files in `markdownViewr/Resources/themes/`
Each file needs `"highlightBackground"` and `"highlightText"` added inside the `"colors"` object, after `"blockquoteBackground"`.
- [ ] **Step 1: Update `catppuccin-mocha.json`** (dark)
Add after `"blockquoteBackground"`:
```json
"highlightBackground": "#f9e2af",
"highlightText": "#1e1e2e"
```
- [ ] **Step 2: Update `catppuccin-latte.json`** (light)
```json
"highlightBackground": "#f9e2af",
"highlightText": "#4c4f69"
```
- [ ] **Step 3: Update `dracula.json`** (dark)
```json
"highlightBackground": "#f1fa8c",
"highlightText": "#282a36"
```
- [ ] **Step 4: Update `everforest-dark.json`** (dark)
```json
"highlightBackground": "#dbbc7f",
"highlightText": "#2d353b"
```
- [ ] **Step 5: Update `github-dark.json`** (dark)
```json
"highlightBackground": "#e3b341",
"highlightText": "#0d1117"
```
- [ ] **Step 6: Update `github-light.json`** (light)
```json
"highlightBackground": "#fff8c5",
"highlightText": "#1f2328"
```
- [ ] **Step 7: Update `gruvbox-dark.json`** (dark)
```json
"highlightBackground": "#fabd2f",
"highlightText": "#282828"
```
- [ ] **Step 8: Update `gruvbox-light.json`** (light)
```json
"highlightBackground": "#fabd2f",
"highlightText": "#282828"
```
- [ ] **Step 9: Update `kanagawa.json`** (dark)
```json
"highlightBackground": "#ff9e3b",
"highlightText": "#1f1f28"
```
- [ ] **Step 10: Update `monokai.json`** (dark)
```json
"highlightBackground": "#e6db74",
"highlightText": "#272822"
```
- [ ] **Step 11: Update `nord.json`** (dark)
```json
"highlightBackground": "#ebcb8b",
"highlightText": "#2e3440"
```
- [ ] **Step 12: Update `one-dark.json`** (dark)
```json
"highlightBackground": "#e5c07b",
"highlightText": "#282c34"
```
- [ ] **Step 13: Update `one-light.json`** (light)
```json
"highlightBackground": "#ffd700",
"highlightText": "#383a42"
```
- [ ] **Step 14: Update `rose-pine.json`** (dark)
```json
"highlightBackground": "#f6c177",
"highlightText": "#191724"
```
- [ ] **Step 15: Update `rose-pine-dawn.json`** (light)
```json
"highlightBackground": "#f6c177",
"highlightText": "#575279"
```
- [ ] **Step 16: Update `solarized-dark.json`** (dark)
```json
"highlightBackground": "#b58900",
"highlightText": "#002b36"
```
- [ ] **Step 17: Update `solarized-light.json`** (light)
```json
"highlightBackground": "#b58900",
"highlightText": "#fdf6e3"
```
- [ ] **Step 18: Update `tokyo-night.json`** (dark)
```json
"highlightBackground": "#e0af68",
"highlightText": "#1a1b26"
```
- [ ] **Step 19: Update `tokyo-night-light.json`** (light)
```json
"highlightBackground": "#f9c859",
"highlightText": "#343b58"
```
- [ ] **Step 20: Update `zenburn.json`** (dark)
```json
"highlightBackground": "#f0dfaf",
"highlightText": "#3f3f3f"
```
- [ ] **Step 21: Build to verify**
```bash
just build
```
Expected: build succeeds.
- [ ] **Step 22: Commit**
```bash
git add markdownViewr/ThemeManager.swift markdownViewr/Resources/template.html markdownViewr/Resources/themes/
git commit -m "add highlight CSS variable, mark rule, and highlight colors to all built-in themes"
```
---
### Task 5: MarkdownDocument — post-processing pass
**Files:**
- Modify: `markdownViewr/MarkdownDocument.swift`
- [ ] **Step 1: Update `convertToHTML` signature to accept `extensions`**
Change the function signature from:
```swift
static func convertToHTML(_ markdown: String, frontmatterMode: FrontmatterMode = .hide) -> String {
```
to:
```swift
static func convertToHTML(_ markdown: String, frontmatterMode: FrontmatterMode = .hide, extensions: MarkdownExtensions = MarkdownExtensions()) -> String {
```
- [ ] **Step 2: Call `applyExtensions` as the last step in `convertToHTML`**
At the end of `convertToHTML`, change the final two lines from:
```swift
var htmlVisitor = HTMLConverter()
html += htmlVisitor.visit(document)
return html
```
to:
```swift
var htmlVisitor = HTMLConverter()
html += htmlVisitor.visit(document)
return applyExtensions(html, extensions: extensions)
```
- [ ] **Step 3: Add the three private static helpers at the bottom of `MarkdownDocument`** (before the closing brace, after `escapeHTML`)
```swift
private static func applyExtensions(_ html: String, extensions: MarkdownExtensions) -> String {
// Split on <pre>...</pre> blocks so code blocks are never transformed.
var segments = html.components(separatedBy: "<pre>")
for i in 0..<segments.count {
if i == 0 {
segments[i] = applyExtensionRegexes(segments[i], extensions: extensions)
} else {
// Each segment begins with the pre block content up to </pre>,
// followed by regular content.
if let endRange = segments[i].range(of: "</pre>") {
let preContent = String(segments[i][..<endRange.upperBound])
let afterPre = applyExtensionRegexes(
String(segments[i][endRange.upperBound...]),
extensions: extensions
)
segments[i] = preContent + afterPre
}
}
}
return segments.joined(separator: "<pre>")
}
private static func applyExtensionRegexes(_ text: String, extensions: MarkdownExtensions) -> String {
var result = text
if extensions.highlight {
result = applyRegex(result, pattern: "==(.+?)==", template: "<mark>$1</mark>")
}
if extensions.superscript {
result = applyRegex(result, pattern: "\\^(.+?)\\^", template: "<sup>$1</sup>")
}
if extensions.subscript_ {
result = applyRegex(result, pattern: "~(.+?)~", template: "<sub>$1</sub>")
}
if extensions.underline {
result = applyRegex(result, pattern: "\\+\\+(.+?)\\+\\+", template: "<ins>$1</ins>")
}
return result
}
private static func applyRegex(_ text: String, pattern: String, template: String) -> String {
guard let regex = try? NSRegularExpression(pattern: pattern, options: []) else { return text }
let range = NSRange(text.startIndex..., in: text)
return regex.stringByReplacingMatches(in: text, range: range, withTemplate: template)
}
```
- [ ] **Step 4: Build to verify**
```bash
just build
```
Expected: build succeeds.
---
### Task 6: ContentView — wire extensions into rerender
**Files:**
- Modify: `markdownViewr/ContentView.swift`
- [ ] **Step 1: Update `rerender()` to pass `extensions`**
Change:
```swift
private func rerender() {
renderedHTML = MarkdownDocument.convertToHTML(currentMarkdown, frontmatterMode: themeManager.frontmatterMode)
}
```
to:
```swift
private func rerender() {
renderedHTML = MarkdownDocument.convertToHTML(
currentMarkdown,
frontmatterMode: themeManager.frontmatterMode,
extensions: themeManager.markdownExtensions
)
}
```
- [ ] **Step 2: Add `onReceive` for `markdownExtensions`**
After the existing `.onReceive(themeManager.$frontmatterMode)` block, add:
```swift
.onReceive(themeManager.$markdownExtensions) { _ in
DispatchQueue.main.async { rerender() }
}
```
- [ ] **Step 3: Build to verify**
```bash
just build
```
Expected: build succeeds.
- [ ] **Step 4: Commit**
```bash
git add markdownViewr/MarkdownDocument.swift markdownViewr/ContentView.swift
git commit -m "add post-processing pass for markdown extensions (highlight, superscript, subscript, underline)"
```
---
### Task 7: Settings UI — Markdown tab
**Files:**
- Modify: `markdownViewr/SettingsView.swift`
- [ ] **Step 1: Add `MarkdownSettingsView` struct to `SettingsView.swift`**
Add this new struct before the `ThemeEditorItem` struct (before line 265):
```swift
struct MarkdownSettingsView: View {
@EnvironmentObject var themeManager: ThemeManager
var body: some View {
VStack(alignment: .leading, spacing: 0) {
Text("Markdown Extensions")
.font(.headline)
.padding(.bottom, 8)
Text("Enable or disable inline syntax extensions applied during rendering.")
.font(.subheadline)
.foregroundStyle(.secondary)
.padding(.bottom, 16)
GroupBox {
VStack(spacing: 0) {
extensionRow(
name: "Highlight",
example: "==text==",
isOn: Binding(
get: { themeManager.markdownExtensions.highlight },
set: { themeManager.markdownExtensions.highlight = $0 }
)
)
Divider().padding(.vertical, 6)
extensionRow(
name: "Superscript",
example: "^text^",
isOn: Binding(
get: { themeManager.markdownExtensions.superscript },
set: { themeManager.markdownExtensions.superscript = $0 }
)
)
Divider().padding(.vertical, 6)
extensionRow(
name: "Subscript",
example: "~text~",
isOn: Binding(
get: { themeManager.markdownExtensions.subscript_ },
set: { themeManager.markdownExtensions.subscript_ = $0 }
)
)
Divider().padding(.vertical, 6)
extensionRow(
name: "Underline",
example: "++text++",
isOn: Binding(
get: { themeManager.markdownExtensions.underline },
set: { themeManager.markdownExtensions.underline = $0 }
)
)
}
.padding(4)
}
Spacer()
}
.padding(20)
}
private func extensionRow(name: String, example: String, isOn: Binding<Bool>) -> some View {
HStack {
Text(name)
.frame(width: 100, alignment: .leading)
Text(example)
.font(.system(.caption, design: .monospaced))
.foregroundStyle(.secondary)
Spacer()
Toggle("", isOn: isOn)
.toggleStyle(.checkbox)
.labelsHidden()
}
.padding(.vertical, 2)
}
}
```
- [ ] **Step 2: Add the Markdown tab to `SettingsView`**
In `SettingsView.body`, add the new tab after the Themes tab:
```swift
MarkdownSettingsView()
.tabItem {
Label("Markdown", systemImage: "doc.plaintext")
}
```
- [ ] **Step 3: Build to verify**
```bash
just build
```
Expected: build succeeds.
---
### Task 8: Theme Editor — highlight color pickers
**Files:**
- Modify: `markdownViewr/ThemeEditorView.swift`
- Modify: `markdownViewr/SampleMarkdown.swift`
- [ ] **Step 1: Add highlight color rows to `colorsSection` in `ThemeEditorView.swift`**
In the `LazyVGrid` inside `colorsSection`, after `colorRow("Quote BG", $colors.blockquoteBackground)`, add:
```swift
colorRow("Highlight BG", $colors.highlightBackground)
colorRow("Highlight Text", $colors.highlightText)
```
- [ ] **Step 2: Add highlight/extension examples to `sampleMarkdown` in `SampleMarkdown.swift`**
Find the sample markdown string and add a new section so the Theme Editor preview shows the highlight color in action. Add this block to the sample (a sensible place is near the inline formatting examples):
```swift
## Extensions
This sentence uses ==highlighted text==, H~2~O for subscript, E=mc^2^ for superscript, and ++underlined text++.
```
- [ ] **Step 3: Build to verify**
```bash
just build
```
Expected: build succeeds.
- [ ] **Step 4: Commit**
```bash
git add markdownViewr/SettingsView.swift markdownViewr/ThemeEditorView.swift markdownViewr/SampleMarkdown.swift
git commit -m "add Markdown settings tab and highlight color pickers to theme editor"
```
---
### Task 9: Manual test
- [ ] **Step 1: Launch the app**
```bash
just run
```
- [ ] **Step 2: Open a test file with all four extension types**
Create `/tmp/extensions-test.md` with the following content:
```markdown
# Extension Test
Regular text with ==highlighted phrase== inline.
E=mc^2^ uses superscript.
H~2~O uses subscript.
This is ++underlined text++ using plus markers.
Code blocks must NOT be transformed:
==not highlighted==
^not superscript^
~not subscript~
++not underlined++
Fenced code blocks must also be skipped:
```text
==still not highlighted==
```
Open this file in the app.
- [ ] **Step 3: Verify each extension renders correctly**
- `==highlighted phrase==` → yellow/amber background highlight (matching the active theme's highlight color)
- `mc^2^` → `2` as superscript
- `H~2~O` → `2` as subscript
- `++underlined text++` → underline styling
- Content inside code blocks: no transformation, literal syntax visible
- [ ] **Step 4: Verify toggles work**
Open Settings → Markdown tab. Toggle Highlight off. Verify the highlighted phrase no longer has a background and shows `==highlighted phrase==` literally. Toggle it back on and verify it re-renders immediately.
- [ ] **Step 5: Verify highlight color in Theme Editor**
Open Settings → Themes → select a theme → Edit. Verify "Highlight BG" and "Highlight Text" color pickers appear in the Colors section. Change the highlight color and verify the preview updates immediately.
- [ ] **Step 6: Verify theme switching**
Switch between a dark theme and a light theme in the toolbar. Verify the highlight background color changes appropriately with the theme.
@@ -0,0 +1,134 @@
# Markdown Extensions Design
**Date:** 2026-06-04
**Status:** Approved
## Overview
Add a Markdown tab to the Settings window that lets users toggle four inline
syntax extensions. Enabled extensions are applied via a post-processing pass
on the generated HTML.
## Extensions
| Syntax | Name | HTML output | Default |
|---|---|---|---|
| `==text==` | Highlight | `<mark>text</mark>` | on |
| `^text^` | Superscript | `<sup>text</sup>` | on |
| `~text~` | Subscript | `<sub>text</sub>` | on |
| `++text++` | Underline | `<ins>text</ins>` | on |
Strikethrough (`~~text~~`) is already handled at the parser level (swift-markdown
GFM extension) and is not part of this feature.
## Data Model
New `MarkdownExtensions` struct in its own file (`MarkdownExtensions.swift`):
```swift
struct MarkdownExtensions: Codable {
var highlight: Bool = true
var superscript: Bool = true
var subscript_: Bool = true // trailing underscore: `subscript` is a Swift keyword
var underline: Bool = true
}
```
`ThemeManager` gets one new `@Published var markdownExtensions: MarkdownExtensions`
property. On `didSet` it encodes to JSON and writes to UserDefaults key
`markdownExtensions`. On init it reads and decodes from UserDefaults, falling
back to `MarkdownExtensions()` (all on) if the key is absent or malformed.
## Post-Processing
`MarkdownDocument` gains a private static `applyExtensions(_:extensions:) -> String`
helper. It is called as the final step of `convertToHTML`, after `HTMLConverter`
produces its output.
The helper splits the HTML on `<pre>` / `</pre>` boundaries. Segments inside
`<pre>` blocks are passed through unchanged. Segments outside have each enabled
extension's regex applied using `NSRegularExpression`.
Patterns (applied in the order listed):
| Extension | Regex | Replacement |
|---|---|---|
| Highlight | `==(.+?)==` | `<mark>$1</mark>` |
| Superscript | `\^(.+?)\^` | `<sup>$1</sup>` |
| Subscript | `~(.+?)~` | `<sub>$1</sub>` |
| Underline | `\+\+(.+?)\+\+` | `<ins>$1</ins>` |
`convertToHTML` signature gains one new parameter:
```swift
static func convertToHTML(
_ markdown: String,
frontmatterMode: FrontmatterMode = .hide,
extensions: MarkdownExtensions = MarkdownExtensions()
) -> String
```
All existing callers that omit `extensions` get the all-on defaults.
## Settings UI
A fourth tab is added to the `TabView` in `SettingsView`:
- Label: "Markdown", system image: `doc.plaintext`
- New struct `MarkdownSettingsView` in `SettingsView.swift`
- One `GroupBox` containing a `VStack` of four rows
- Each row: extension name (left), example syntax in secondary monospaced text
(center), `Toggle` with `.checkbox` style (right)
- Toggling writes through `themeManager.markdownExtensions` immediately
## Re-render Trigger
`ContentView` already observes `themeManager` via `@EnvironmentObject` and
re-renders on published changes. Adding `onReceive(themeManager.$markdownExtensions)`
(same pattern as `frontmatterMode`) triggers a re-render when any toggle changes.
No restart required.
## Theme Color: Highlight
`<mark>` has a browser-default yellow background that looks wrong on dark themes.
`ThemeColors` gets two new fields:
```swift
var highlightBackground: String // default: "#ffd700" (gold — readable on dark)
var highlightText: String // default: "#1e1e2e" (dark — pairs with gold bg)
```
`ThemeManager.generateCSS` emits `--highlight-bg` and `--highlight-text` CSS
variables. The HTML template adds:
```css
mark {
background-color: var(--highlight-bg);
color: var(--highlight-text);
border-radius: 2px;
padding: 0 2px;
}
```
All 20 built-in theme JSON files get the two new color fields with
theme-appropriate values. The Theme Editor color list gains a "Highlight"
section (background + text), matching the existing "Code" section layout.
User themes that predate this change omit these fields in their JSON. Swift's
auto-synthesized `Decodable` throws on missing keys, so `ThemeColors` needs a
custom `init(from decoder:)` that uses `decodeIfPresent` for the two new fields
and falls back to the `ThemeColors.init` defaults when absent.
## Files Changed
| File | Change |
|---|---|
| `MarkdownExtensions.swift` | New — struct definition |
| `Theme.swift` | Add `highlightBackground`, `highlightText` to `ThemeColors` |
| `ThemeManager.swift` | Add `markdownExtensions` published property; emit `--highlight-bg`/`--highlight-text` in `generateCSS` |
| `MarkdownDocument.swift` | Add `applyExtensions` helper, update `convertToHTML` signature |
| `SettingsView.swift` | Add `MarkdownSettingsView`, add Markdown tab |
| `ContentView.swift` | Add `onReceive` for `markdownExtensions` |
| `ThemeEditorView.swift` | Add Highlight color pickers |
| `Resources/template.html` | Add `mark { … }` CSS rule |
| `Resources/themes/*.json` (×20) | Add `highlightBackground` + `highlightText` per theme |
+29 -3
View File
@@ -30,17 +30,27 @@ release: kill
#!/usr/bin/env bash
set -euo pipefail
VERSION=$(grep 'MARKETING_VERSION' project.yml | head -1 | sed 's/.*"\(.*\)".*/\1/')
MAJOR=$(echo "$VERSION" | cut -d. -f1)
MINOR=$(echo "$VERSION" | cut -d. -f2)
PATCH=$(echo "$VERSION" | cut -d. -f3)
BUILD_NUMBER=$(( MAJOR * 10000 + MINOR * 100 + PATCH ))
LATEST=$(gh release list --repo darinkelkhoff/markdownViewr --limit 1 --json tagName --jq '.[0].tagName' 2>/dev/null || echo "none")
if [[ "$LATEST" == "v$VERSION" ]]; then
echo "Error: v$VERSION is already released. Bump MARKETING_VERSION in project.yml first."
exit 1
fi
ARCHIVE="/tmp/markdownViewr.xcarchive"
EXPORT="/tmp/markdownViewr-export"
ZIP="/tmp/markdownViewr-$VERSION.zip"
echo "==> Archiving v$VERSION..."
echo "==> Archiving v$VERSION (build $BUILD_NUMBER)..."
rm -rf "$ARCHIVE" "$EXPORT" "$ZIP"
xcodebuild archive \
-project markdownViewr.xcodeproj \
-scheme markdownViewr \
-archivePath "$ARCHIVE" \
-destination 'generic/platform=macOS' \
ENABLE_HARDENED_RUNTIME=YES | xcpretty || true
ENABLE_HARDENED_RUNTIME=YES \
CURRENT_PROJECT_VERSION="$BUILD_NUMBER" | xcpretty || true
echo "==> Exporting with Developer ID signing..."
xcodebuild -exportArchive \
-archivePath "$ARCHIVE" \
@@ -58,8 +68,24 @@ release: kill
xcrun stapler staple "$APP"
echo "==> Packaging final release..."
ditto -c -k --keepParent "$APP" "$ZIP"
echo "==> Creating DMG..."
DMG="/tmp/markdownViewr.dmg"
DMG_STAGING="/tmp/markdownViewr-dmg-staging"
rm -f "$DMG"
rm -rf "$DMG_STAGING"
mkdir "$DMG_STAGING"
cp -R "$APP" "$DMG_STAGING/"
create-dmg \
--volname "markdownViewr" \
--window-size 660 400 \
--icon-size 128 \
--icon "markdownViewr.app" 180 185 \
--hide-extension "markdownViewr.app" \
--app-drop-link 480 185 \
"$DMG" \
"$DMG_STAGING/"
echo "==> Creating GitHub release v$VERSION..."
gh release create "v$VERSION" "$ZIP" \
gh release create "v$VERSION" "$DMG" "$ZIP" \
--repo darinkelkhoff/markdownViewr \
--title "markdownViewr v$VERSION" \
--generate-notes
+377
View File
@@ -0,0 +1,377 @@
import SwiftUI
import WebKit
private enum HelpTopic: String, CaseIterable, Identifiable {
case overview = "Overview"
case openingFiles = "Opening Files"
case frontmatter = "Frontmatter"
case extensions = "Markdown Extensions"
case themes = "Themes"
case editors = "External Editors"
case shortcuts = "Keyboard Shortcuts"
var id: String { rawValue }
}
struct HelpView: View {
@State private var selection: HelpTopic = .overview
var body: some View {
NavigationSplitView {
List(HelpTopic.allCases, selection: $selection) { topic in
Text(topic.rawValue).tag(topic)
}
.navigationSplitViewColumnWidth(min: 160, ideal: 180)
} detail: {
HelpWebView(markdown: helpMarkdown(for: selection))
.id(selection)
}
}
private func helpMarkdown(for topic: HelpTopic) -> String {
switch topic {
case .overview:
return """
# Overview
markdownViewr is a fast, view-only markdown file viewer for macOS. It renders \
markdown to HTML and displays it in a styled WebView with full theming support.
Files are watched for external changes and automatically reloaded — open a file \
in your editor and markdownViewr keeps pace as you save.
## Features
- Renders standard Markdown and GitHub Flavored Markdown (GFM): bold, italic, \
strikethrough, tables, code blocks, task lists, and more
- Optional extensions: highlight, superscript, subscript, underline
- YAML frontmatter support: hide it, show it as a metadata table, or render it as Markdown
- Fully customizable themes with a built-in theme editor and custom CSS
- Vim-style keyboard navigation
- Find in document with match count
- One-click open in any configured external editor
- Automatic updates via Sparkle
"""
case .openingFiles:
return """
# Opening Files
markdownViewr opens any plain text file. It registers `.md`, `.markdown`, \
`.mdown`, and `.mkd` with the OS so Finder offers markdownViewr for those \
file types by default.
## Ways to open a file
- **File > Open** (⌘O) — standard open dialog
- Drag a file onto the Dock icon or an open window
- Double-click a file in Finder if markdownViewr is set as the default app
- Right-click a file in Finder > Open With > markdownViewr
## Setting as the default viewer
To make markdownViewr open all `.md` files by default: right-click any `.md` \
file in Finder, choose **Get Info**, expand **Open with**, select markdownViewr, \
then click **Change All...**.
## Live reloading
When an open file is modified on disk by another app (your editor, a script, \
etc.), markdownViewr detects the change and re-renders automatically. Scroll \
position is preserved across reloads.
"""
case .frontmatter:
return """
# Frontmatter
YAML frontmatter is a block of metadata at the very top of a Markdown file, \
delimited by `---` lines:
```
---
title: My Document
author: Jane Smith
date: 2026-01-01
---
# Heading
Body text...
```
The **Frontmatter** setting (Settings > General) controls how this block is handled.
## Hide (default)
The frontmatter block is stripped before rendering. The body begins immediately \
after the closing `---` line.
## Show as Metadata
The frontmatter is rendered as a styled key–value table above the document body. \
Keys appear on the left, values on the right.
## Show as Markdown
The frontmatter is passed to the Markdown parser as-is. The `---` delimiters \
render as horizontal rules and the content is treated as normal Markdown text.
"""
case .extensions:
return """
# Markdown Extensions
Extensions add syntax beyond standard GFM. Each can be toggled independently \
in **Settings > Extensions**.
## Highlight
Wrapping text in `== ==` renders it as highlighted text.
```
==highlighted text==
```
## Superscript
Wrapping a single word in `^ ^` renders it as superscript. No spaces allowed \
inside — this avoids false positives on prose like "It's ^ that simple".
```
E = mc^2^
Footnote reference^1^
```
## Subscript
Wrapping a single word in `~ ~` renders it as subscript. No spaces allowed \
inside — single-word only to avoid conflict with GFM strikethrough (`~~ ~~`).
```
H~2~O
CO~2~
```
## Underline
Wrapping text in `++ ++` renders it as underlined text.
```
++underlined text++
```
## Standard GFM (always on)
- `**bold**` / `__bold__`
- `*italic*` / `_italic_`
- `~~strikethrough~~`
- `` `inline code` ``
- Fenced code blocks
- Tables
- `- [ ]` task lists
"""
case .themes:
return """
# Themes
Themes control the colors, fonts, and overall appearance of rendered documents. \
markdownViewr ships with a set of built-in themes and supports unlimited \
user-created themes.
## Switching themes
Use the **Theme** menu or the keyboard shortcuts **⌘⇧↓** / **⌘⇧↑** to cycle \
through enabled themes. The active theme applies to all open windows.
## Managing themes
**Settings > Themes** lists all themes. You can:
- Enable or disable themes (disabled themes are skipped when cycling)
- Reorder themes by dragging
- Delete user themes
- Restore deleted built-in themes
## Creating and editing themes
Click **New Theme** (or **Edit** on an existing user theme) to open the Theme \
Editor. Changes are previewed live against a sample document.
The editor covers:
- Background and text colors
- Heading, link, inline code, and blockquote colors
- Table border and alternating row colors
- Body, heading, and code font families and sizes
- Custom CSS — injected after all base styles, so any rule can be overridden
## Custom CSS
Custom CSS is the escape hatch for anything the theme editor doesn't expose. \
It targets the same HTML the renderer produces, so standard CSS selectors work:
```css
/* Wider line spacing */
body { line-height: 1.9; }
/* Justify paragraphs */
p { text-align: justify; }
/* Larger code font */
code { font-size: 0.95em; }
```
## Theme storage
User themes are stored as JSON files in \
`~/Library/Application Support/markdownViewr/themes/`. Built-in themes are \
bundled with the app and cannot be edited (but can be duplicated).
"""
case .editors:
return """
# External Editors
markdownViewr can open the current file in an external editor with one click. \
Any app that accepts a file path as a command-line argument can be configured.
## Adding an editor
Go to **Settings > Editors** and click the **+** button. Give the editor a name, \
then click the path field to browse for the executable or app bundle. Most \
editors install a command-line tool (e.g. `/usr/local/bin/code` for VS Code), \
but you can also browse directly to the `.app` in `/Applications`.
Enable **Opens folder instead of file** if the editor works better when opened \
to the containing directory rather than the file itself — useful for \
project-based editors like VS Code or Nova, terminal emulators like Terminal \
(`/System/Applications/Utilities/Terminal.app`), or Finder \
(`/System/Library/CoreServices/Finder.app`).
## Using the toolbar button
When at least one editor is configured, an **Open In** button appears in the \
document toolbar. Click it to open the current file (or its containing folder) \
in your configured editor. If you have multiple editors configured, clicking \
the button shows a menu to choose which one.
"""
case .shortcuts:
return """
# Keyboard Shortcuts
## Navigation
| Key | Action |
|---|---|
| `j` / `k` | Scroll down / up one line |
| `Ctrl+d` / `Ctrl+u` | Scroll half page down / up |
| `Ctrl+f` / `Ctrl+b` | Scroll full page down / up |
| `g g` | Jump to top of document |
| `G` | Jump to bottom of document |
| `]]` / `[[` | Jump to next / previous heading |
## Find
| Key | Action |
|---|---|
| `⌘ F` | Show / hide find bar |
| `⌘ G` | Find next match |
| `⌘ ⇧ G` | Find previous match |
| `/` (in document) | Focus find bar (vim-style) |
| `n` / `N` (in document) | Next / previous match (vim-style) |
## View
| Key | Action |
|---|---|
| `⌘ +` | Zoom in |
| `⌘ −` | Zoom out |
| `⌘ 0` | Actual size |
| `⌘ ⇧ ↓` | Next theme |
| `⌘ ⇧ ↑` | Previous theme |
## General
| Key | Action |
|---|---|
| `⌘ ,` | Open Settings |
| `⌘ ?` | Open Help |
| `⌘ O` | Open file |
| `⌘ W` | Close window |
"""
}
}
}
struct HelpWebView: NSViewRepresentable {
let markdown: String
func makeNSView(context: Context) -> WKWebView {
let config = WKWebViewConfiguration()
let webView = WKWebView(frame: .zero, configuration: config)
webView.setValue(false, forKey: "drawsBackground")
return webView
}
func updateNSView(_ webView: WKWebView, context: Context) {
let body = MarkdownDocument.convertToHTML(markdown)
let html = """
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
* { box-sizing: border-box; }
body {
font-family: -apple-system, sans-serif;
font-size: 13px;
line-height: 1.6;
color: #1a1a1a;
background: transparent;
margin: 0;
padding: 20px 24px;
max-width: 680px;
}
h1 { font-size: 1.5em; margin: 0 0 12px 0; }
h2 { font-size: 1.1em; margin: 20px 0 6px 0; }
p { margin: 0 0 8px 0; }
ul, ol { margin: 0 0 8px 0; padding-left: 1.5em; }
li { margin-bottom: 3px; }
code {
font-family: ui-monospace, monospace;
font-size: 0.9em;
background: rgba(0,0,0,0.06);
padding: 1px 4px;
border-radius: 3px;
}
pre {
background: rgba(0,0,0,0.06);
border-radius: 6px;
padding: 10px 14px;
overflow-x: auto;
margin: 0 0 10px 0;
}
pre code { background: none; padding: 0; }
table { border-collapse: collapse; width: 100%; margin-bottom: 10px; }
th, td { text-align: left; padding: 5px 10px; border-bottom: 1px solid rgba(0,0,0,0.1); }
th { font-weight: 600; border-bottom: 2px solid rgba(0,0,0,0.15); }
@media (prefers-color-scheme: dark) {
body { color: #e8e8e8; }
code { background: rgba(255,255,255,0.1); }
pre { background: rgba(255,255,255,0.08); }
th, td { border-bottom-color: rgba(255,255,255,0.12); }
th { border-bottom-color: rgba(255,255,255,0.2); }
}
</style>
</head>
<body>\(body)</body>
</html>
"""
webView.loadHTMLString(html, baseURL: nil)
}
}
+4 -2
View File
@@ -38,9 +38,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>$(CURRENT_PROJECT_VERSION)</string>
<key>LSApplicationCategoryType</key>
<string>public.app-category.productivity</string>
<key>SUFeedURL</key>
<string>https://raw.githubusercontent.com/darinkelkhoff/markdownViewr/main/appcast.xml</string>
<key>SUPublicEDKey</key>
+42
View File
@@ -57,6 +57,13 @@ struct MarkdownViewrApp: App {
.keyboardShortcut("g", modifiers: [.command, .shift])
}
CommandGroup(replacing: .help) {
Button("markdownViewr Help") {
HelpWindowController.shared.show()
}
.keyboardShortcut("?", modifiers: .command)
}
CommandGroup(replacing: .toolbar) {
Button("Zoom In") {
themeManager.zoomIn()
@@ -107,6 +114,41 @@ struct DocumentWindowConfigurator: NSViewRepresentable {
func updateNSView(_ nsView: NSView, context: Context) {}
}
class HelpWindowController: NSObject, NSWindowDelegate {
static let shared = HelpWindowController()
private var window: NSWindow?
func show() {
if let window {
window.makeKeyAndOrderFront(nil)
return
}
let hostingView = NSHostingView(rootView: HelpView())
hostingView.frame = NSRect(x: 0, y: 0, width: 620, height: 540)
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 620, height: 540),
styleMask: [.titled, .closable, .resizable, .miniaturizable],
backing: .buffered,
defer: false
)
window.title = "markdownViewr Help"
window.contentView = hostingView
window.minSize = NSSize(width: 460, height: 340)
window.isReleasedWhenClosed = false
window.delegate = self
window.center()
window.makeKeyAndOrderFront(nil)
self.window = window
}
func windowWillClose(_ notification: Notification) {
window = nil
}
}
class SettingsWindowController: NSObject, NSWindowDelegate {
static let shared = SettingsWindowController()
private var window: NSWindow?
+5
View File
@@ -0,0 +1,5 @@
{\rtf1\ansi\ansicpg1252\cocoartf2639
\cocoatextscaling0\cocoaplatform0
\pard\pardirnatural\qc\partightenfactor0
Directed by Darin Kelkhoff\
Written by Claude Code}
+5 -2
View File
@@ -31,8 +31,8 @@ targets:
base:
INFOPLIST_FILE: markdownViewr/Info.plist
PRODUCT_BUNDLE_IDENTIFIER: com.dkelkhoff.markdownViewr
MARKETING_VERSION: "1.1.0"
CURRENT_PROJECT_VERSION: "1"
MARKETING_VERSION: "1.1.5"
CURRENT_PROJECT_VERSION: "3"
MACOSX_DEPLOYMENT_TARGET: "13.0"
ENABLE_APP_SANDBOX: "NO"
ASSETCATALOG_COMPILER_APPICON_NAME: AppIcon
@@ -62,5 +62,8 @@ targets:
- markdown
- mdown
- mkd
CFBundleShortVersionString: "$(MARKETING_VERSION)"
CFBundleVersion: "$(CURRENT_PROJECT_VERSION)"
LSApplicationCategoryType: "public.app-category.productivity"
SUFeedURL: "https://raw.githubusercontent.com/darinkelkhoff/markdownViewr/main/appcast.xml"
SUPublicEDKey: "flXZnzfoR5H/JgMTtAGza8SUcUbfK4FWUPEBjxkSZyc="