一键导入
md-preview-app-macos
Native macOS Markdown viewer app with Quick Look extension, Mermaid diagrams, KaTeX math, document outline, and editor integration
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Native macOS Markdown viewer app with Quick Look extension, Mermaid diagrams, KaTeX math, document outline, and editor integration
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | md-preview-app-macos |
| description | Native macOS Markdown viewer app with Quick Look extension, Mermaid diagrams, KaTeX math, document outline, and editor integration |
| triggers | ["add markdown preview to my mac","build md-preview app from source","contribute to markdown preview macos","add a feature to md-preview","how does the quick look extension work","render mermaid diagrams in markdown viewer","open markdown files natively on macos","integrate md-preview with my editor"] |
Skill by ara.so — Daily 2026 Skills collection.
A fast, native macOS app (AppKit + WKWebView) for reading .md files. No Electron, no browser tab. Features include a document outline sidebar, Mermaid diagram rendering, KaTeX math, Quick Look extension, in-document search, and one-click "Open With" for popular editors.
Download the signed and notarized DMG from the Releases page and drag to /Applications.
git clone git@github.com:pluk-inc/md-preview.app.git
cd md-preview.app
open md-preview.xcodeproj
# Build and run the `md-preview` scheme
# SPM resolves Sparkle + swift-markdown on first build
Requirements: macOS 15+, Xcode with Swift 6.0, Apple Silicon or Intel.
md-preview/ # Main app target (AppKit, WKWebView)
quick-look/ # Quick Look extension (.appex)
scripts/ # Release & rollback automation
Version.xcconfig # MARKETING_VERSION + CURRENT_PROJECT_VERSION
appcast.xml # Sparkle update feed
| Package | Purpose |
|---|---|
swift-markdown (Apple) | Markdown parsing via cmark-gfm |
Sparkle | Auto-update framework |
| Mermaid (bundled JS) | Fenced mermaid block rendering |
| KaTeX (bundled JS) | LaTeX math rendering |
import Markdown
// Parse a markdown string into a Document
let source = try String(contentsOf: fileURL, encoding: .utf8)
let document = Document(parsing: source)
// Walk the document tree to build HTML + extract headings for TOC
struct HTMLRenderer: MarkupVisitor {
typealias Result = String
mutating func visitDocument(_ document: Document) -> String {
document.children.map { visit($0) }.joined()
}
mutating func visitHeading(_ heading: Heading) -> String {
let text = heading.plainText
let anchor = text.lowercased()
.replacingOccurrences(of: " ", with: "-")
.filter { $0.isLetter || $0.isNumber || $0 == "-" }
let level = heading.level
return "<h\(level) id=\"\(anchor)\">\(text)</h\(level)>\n"
}
mutating func visitParagraph(_ paragraph: Paragraph) -> String {
"<p>\(paragraph.children.map { visit($0) }.joined())</p>\n"
}
mutating func visitCodeBlock(_ codeBlock: CodeBlock) -> String {
let lang = codeBlock.language ?? ""
if lang == "mermaid" {
// Render as Mermaid diagram via bundled mermaid.min.js
return "<div class=\"mermaid\">\(codeBlock.code)</div>\n"
}
if lang == "math" {
// Render as KaTeX display math
return "<div class=\"math-display\">$$\(codeBlock.code)$$</div>\n"
}
return "<pre><code class=\"language-\(lang)\">\(codeBlock.code)</code></pre>\n"
}
}
import WebKit
class PreviewViewController: NSViewController, WKNavigationDelegate {
let webView = WKWebView()
func loadMarkdown(from url: URL) {
let source = try! String(contentsOf: url, encoding: .utf8)
var renderer = HTMLRenderer()
let body = renderer.visit(Document(parsing: source))
let html = wrapInTemplate(body)
// Load with base URL so bundled assets (mermaid, KaTeX) resolve
let resourceURL = Bundle.main.resourceURL!
webView.loadHTMLString(html, baseURL: resourceURL)
}
// Handle navigation: open external links in default browser
func webView(_ webView: WKWebView,
decidePolicyFor action: WKNavigationAction,
decisionHandler: @escaping (WKNavigationActionPolicy) -> Void) {
if action.navigationType == .linkActivated,
let url = action.request.url,
url.scheme == "https" || url.scheme == "http" {
NSWorkspace.shared.open(url)
decisionHandler(.cancel)
} else {
decisionHandler(.allow)
}
}
}
struct HeadingItem: Identifiable {
let id = UUID()
let level: Int
let text: String
let anchor: String
}
// Extract headings while parsing
func extractHeadings(from document: Document) -> [HeadingItem] {
var headings: [HeadingItem] = []
for child in document.children {
if let heading = child as? Heading {
let text = heading.plainText
let anchor = text.lowercased()
.replacingOccurrences(of: " ", with: "-")
headings.append(HeadingItem(level: heading.level,
text: text,
anchor: anchor))
}
}
return headings
}
// Jump to heading via JavaScript
func scrollTo(anchor: String) {
let js = "document.getElementById('\(anchor)')?.scrollIntoView({behavior:'smooth'})"
webView.evaluateJavaScript(js, completionHandler: nil)
}
The extension lives in quick-look/ and is a separate .appex target. It reuses the same HTML rendering pipeline so Mermaid diagrams and math work offline in Finder spacebar previews.
// quick-look/PreviewViewController.swift (skeleton)
import Quartz
class PreviewViewController: NSViewController, QLPreviewingController {
func preparePreviewOfFile(at url: URL,
completionHandler: @escaping (Error?) -> Void) {
let source = try! String(contentsOf: url, encoding: .utf8)
var renderer = HTMLRenderer()
let html = wrapInTemplate(renderer.visit(Document(parsing: source)))
let base = Bundle.main.resourceURL!
webView.loadHTMLString(html, baseURL: base)
completionHandler(nil)
}
}
Fenced mermaid blocks are detected during parsing and emitted as <div class="mermaid"> elements. The bundled mermaid.min.js initializes on page load — no CDN required.
Markdown input:
```mermaid
flowchart TD
A[Input .md] --> B[swift-markdown]
B --> C[WKWebView]
```
HTML template snippet (how it's wired):
<script src="mermaid.min.js"></script>
<script>mermaid.initialize({ startOnLoad: true, theme: 'default' });</script>
| Syntax | Usage |
|---|---|
$x^2 + y^2$ | Inline math |
$$\int_0^1 f(x)\,dx$$ | Display math |
```math block | Fenced display math |
Copying a rendered formula pastes the original LaTeX source (via the bundled copy-tex KaTeX extension).
The app queries Launch Services for apps that declare an editor role for Markdown UTIs, filters to known editors, and remembers your pick.
let mdUTI = UTType("net.daringfireball.markdown")!
let editors = NSWorkspace.shared.urlsForApplications(
toOpen: fileURL // or query by UTI
).filter { url in
let knownEditors = ["com.microsoft.VSCode",
"com.todesktop.230313mzl4w4u92", // Cursor
"dev.zed.zed", "com.sublimetext.4",
"com.barebones.bbedit", "com.panic.Nova",
"com.coteditor.CotEditor", "com.macromates.TextMate",
"org.vim.MacVim", "com.apple.dt.Xcode",
"com.apple.TextEdit"]
let bundleID = Bundle(url: url)?.bundleIdentifier ?? ""
return knownEditors.contains(bundleID)
}
// Open the file in chosen editor
NSWorkspace.shared.open([fileURL],
withApplicationAt: editorURL,
configuration: .init(),
completionHandler: nil)
The Share toolbar item feeds the Markdown text (not a file URL) to NSSharingServicePicker, so Copy writes raw Markdown to the clipboard — ideal for pasting into ChatGPT or Claude.
// Wire up the share button
@objc func shareDocument(_ sender: NSToolbarItem) {
let source = try! String(contentsOf: currentFileURL, encoding: .utf8)
let picker = NSSharingServicePicker(items: [source])
picker.show(relativeTo: .zero, of: sender.view!, preferredEdge: .minY)
}
Standard WKWebView find interaction — no custom implementation needed:
// Enable find bar (macOS 12+)
webView.configuration.preferences.setValue(true,
forKey: "developerExtrasEnabled")
// Trigger search (connect to ⌘F)
@objc func performFindPanelAction(_ sender: Any?) {
webView.performFindPanelAction(sender)
}
// ⌘G / ⌘⇧G handled automatically by WKWebView
| Extension | UTI |
|---|---|
.md, .markdown, .mdown | net.daringfireball.markdown |
.txt | public.plain-text |
Register in Info.plist under CFBundleDocumentTypes and UTImportedTypeDeclarations.
Releases use Amore for signing, notarization, DMG, S3 upload, and Sparkle appcast.
# 1. Bump versions (single source of truth)
# Edit Version.xcconfig:
# MARKETING_VERSION = 1.2.0
# CURRENT_PROJECT_VERSION = 42
# 2. Run release script
./scripts/release.sh
# 3. Roll back a bad release
./scripts/rollback-release.sh
# Fork, then:
git clone https://github.com/YOUR_FORK/md-preview.app.git
cd md-preview.app
# Create a feature branch
git checkout -b feature/my-change
# Open in Xcode, build the `md-preview` scheme
open md-preview.xcodeproj
# Manual smoke test before PR:
# - Drop a .md with headings, mermaid blocks, and math onto the app
# - Verify TOC sidebar, diagram render, math render
# - Test Quick Look (spacebar in Finder)
# - Test "Open With" menu
git push origin feature/my-change
# Open PR against main
PR guidelines:
| Problem | Fix |
|---|---|
| Mermaid diagrams blank | Check baseURL points to app bundle resources; mermaid.min.js must be in the Copy Bundle Resources phase |
| KaTeX not rendering | Same — verify katex.min.js, katex.min.css, and copy-tex.min.js are in bundle resources |
| Quick Look shows plain text | Re-run qlmanage -r to reset Quick Look daemon: qlmanage -r && qlmanage -r cache |
| SPM not resolving | File → Packages → Reset Package Caches in Xcode |
| Notarization issues | Use Amore or check xcrun notarytool — requires APPLE_ID, TEAM_ID, and an app-specific password |
| App not set as default handler | Delete ~/Library/Preferences/com.apple.LaunchServices.QuarantineEventsV2 and re-register via LSSetDefaultHandlerForURLScheme or the app's first-launch prompt |
# Reset Quick Look plugin cache after building
qlmanage -r
qlmanage -r cache
# Test Quick Look directly
qlmanage -p /path/to/file.md
AI agent skill for using deepsec, the agent-powered security vulnerability scanner for large codebases
Open-source Luau/Lua script IDE and executor built as a Windows Forms C# application with Monaco editor integration
Use Claude Code's autonomous agent loop with DeepSeek V4 Pro, OpenRouter, or any Anthropic-compatible backend at up to 17x lower cost.
Minimalist batteries-included repository for training, evaluating, and deploying diffusion-forcing video world models for robot manipulation, gaming, and MPC planning.
WARNING - This project is malicious software disguised as an FL Studio crack/patcher
SysWatch is a single-host system diagnostics TUI for macOS and Linux with twelve tabs, plain-English insights, and session scrubbing.