This commit is contained in:
0x8664b2
2025-06-20 09:06:01 -07:00
commit 07b70455a3
12 changed files with 735 additions and 0 deletions

37
.gitignore vendored Normal file
View File

@@ -0,0 +1,37 @@
# Xcode
build/
*.pbxuser
!default.pbxuser
*.mode1v3
!default.mode1v3
*.mode2v3
!default.mode2v3
*.perspectivev3
!default.perspectivev3
xcuserdata/
*.xccheckout
*.moved-aside
DerivedData/
*.hmap
*.ipa
*.xcuserstate
*.xcworkspace
!default.xcworkspace
# CocoaPods
Pods/
# Carthage
Carthage/Build/
# fastlane
fastlane/report.xml
fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output
# Code Injection
iOSInjectionProject/
# macOS
.DS_Store

36
CLAUDE.md Normal file
View File

@@ -0,0 +1,36 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a new repository (`md.ai`) that appears to be related to AI/ML development based on the repository name and location structure.
## Development Commands
*Note: This repository is currently empty. Update this section as build tools, package managers, and testing frameworks are added.*
Common commands to add:
- Build: `[to be determined based on chosen framework]`
- Test: `[to be determined based on chosen testing framework]`
- Lint: `[to be determined based on chosen linting tools]`
- Dev server: `[to be determined based on chosen development setup]`
## Architecture & Structure
*Note: Project architecture will be documented here as the codebase develops.*
## Getting Started
This is a fresh repository. When beginning development:
1. Choose and set up the appropriate framework/language stack
2. Add package.json (for Node.js) or equivalent dependency management files
3. Set up testing framework and basic project structure
4. Update this CLAUDE.md with specific commands and architecture details
## Notes for Future Development
- Update the "Development Commands" section once build tools are configured
- Document the chosen architecture and key design patterns
- Add any project-specific conventions or guidelines

View File

@@ -0,0 +1,345 @@
// !$*UTF8*$!
{
archiveVersion = 1;
classes = {
};
objectVersion = 56;
objects = {
/* Begin PBXBuildFile section */
1A2345678901234567890001 /* MarkdownViewerApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2345678901234567890002 /* MarkdownViewerApp.swift */; };
1A2345678901234567890003 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2345678901234567890004 /* ContentView.swift */; };
1A2345678901234567890005 /* MarkdownRenderer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1A2345678901234567890006 /* MarkdownRenderer.swift */; };
1A2345678901234567890007 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A2345678901234567890008 /* Assets.xcassets */; };
1A2345678901234567890009 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 1A234567890123456789000A /* Preview Assets.xcassets */; };
/* End PBXBuildFile section */
/* Begin PBXFileReference section */
1A234567890123456789000B /* MarkdownViewer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = MarkdownViewer.app; sourceTree = BUILT_PRODUCTS_DIR; };
1A2345678901234567890002 /* MarkdownViewerApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownViewerApp.swift; sourceTree = "<group>"; };
1A2345678901234567890004 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = "<group>"; };
1A2345678901234567890006 /* MarkdownRenderer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownRenderer.swift; sourceTree = "<group>"; };
1A2345678901234567890008 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
1A234567890123456789000A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
1A234567890123456789000C /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
1A234567890123456789000D = {
isa = PBXGroup;
children = (
1A234567890123456789000E /* MarkdownViewer */,
1A234567890123456789000F /* Products */,
);
sourceTree = "<group>";
};
1A234567890123456789000E /* MarkdownViewer */ = {
isa = PBXGroup;
children = (
1A2345678901234567890002 /* MarkdownViewerApp.swift */,
1A2345678901234567890004 /* ContentView.swift */,
1A2345678901234567890006 /* MarkdownRenderer.swift */,
1A2345678901234567890008 /* Assets.xcassets */,
1A2345678901234567890010 /* Preview Content */,
);
path = MarkdownViewer;
sourceTree = "<group>";
};
1A234567890123456789000F /* Products */ = {
isa = PBXGroup;
children = (
1A234567890123456789000B /* MarkdownViewer.app */,
);
name = Products;
sourceTree = "<group>";
};
1A2345678901234567890010 /* Preview Content */ = {
isa = PBXGroup;
children = (
1A234567890123456789000A /* Preview Assets.xcassets */,
);
path = "Preview Content";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
1A2345678901234567890011 /* MarkdownViewer */ = {
isa = PBXNativeTarget;
buildConfigurationList = 1A2345678901234567890012 /* Build configuration list for PBXNativeTarget "MarkdownViewer" */;
buildPhases = (
1A2345678901234567890013 /* Sources */,
1A234567890123456789000C /* Frameworks */,
1A2345678901234567890014 /* Resources */,
);
buildRules = (
);
dependencies = (
);
name = MarkdownViewer;
productName = MarkdownViewer;
productReference = 1A234567890123456789000B /* MarkdownViewer.app */;
productType = "com.apple.product-type.application";
};
/* End PBXNativeTarget section */
/* Begin PBXProject section */
1A2345678901234567890015 /* Project object */ = {
isa = PBXProject;
attributes = {
BuildIndependentTargetsInParallel = 1;
LastSwiftUpdateCheck = 1500;
LastUpgradeCheck = 1500;
TargetAttributes = {
1A2345678901234567890011 = {
CreatedOnToolsVersion = 15.0;
};
};
};
buildConfigurationList = 1A2345678901234567890016 /* Build configuration list for PBXProject "MarkdownViewer" */;
compatibilityVersion = "Xcode 14.0";
developmentRegion = en;
hasScannedForEncodings = 0;
knownRegions = (
en,
Base,
);
mainGroup = 1A234567890123456789000D;
productRefGroup = 1A234567890123456789000F /* Products */;
projectDirPath = "";
projectRoot = "";
targets = (
1A2345678901234567890011 /* MarkdownViewer */,
);
};
/* End PBXProject section */
/* Begin PBXResourcesBuildPhase section */
1A2345678901234567890014 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1A2345678901234567890009 /* Preview Assets.xcassets in Resources */,
1A2345678901234567890007 /* Assets.xcassets in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
1A2345678901234567890013 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
1A2345678901234567890003 /* ContentView.swift in Sources */,
1A2345678901234567890005 /* MarkdownRenderer.swift in Sources */,
1A2345678901234567890001 /* MarkdownViewerApp.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
/* End PBXSourcesBuildPhase section */
/* Begin XCBuildConfiguration section */
1A2345678901234567890017 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = dwarf;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
"$(inherited)",
);
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
};
name = Debug;
};
1A2345678901234567890018 /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ALWAYS_SEARCH_USER_PATHS = NO;
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
CLANG_ANALYZER_NONNULL = YES;
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
CLANG_ENABLE_MODULES = YES;
CLANG_ENABLE_OBJC_ARC = YES;
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
CLANG_WARN_BOOL_CONVERSION = YES;
CLANG_WARN_COMMA = YES;
CLANG_WARN_CONSTANT_CONVERSION = YES;
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
CLANG_WARN_STRICT_PROTOTYPES = YES;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_USER_SCRIPT_SANDBOXING = YES;
GCC_C_LANGUAGE_STANDARD = gnu17;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
MACOSX_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
VALIDATE_PRODUCT = YES;
};
name = Release;
};
1A2345678901234567890019 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = MarkdownViewer/MarkdownViewer.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"MarkdownViewer/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.MarkdownViewer;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Debug;
};
1A234567890123456789001A /* Release */ = {
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
CODE_SIGN_ENTITLEMENTS = MarkdownViewer/MarkdownViewer.entitlements;
CODE_SIGN_STYLE = Automatic;
COMBINE_HIDPI_IMAGES = YES;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_ASSET_PATHS = "\"MarkdownViewer/Preview Content\"";
ENABLE_PREVIEWS = YES;
GENERATE_INFOPLIST_FILE = YES;
INFOPLIST_KEY_NSHumanReadableCopyright = "";
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/../Frameworks",
);
MARKETING_VERSION = 1.0;
PRODUCT_BUNDLE_IDENTIFIER = com.example.MarkdownViewer;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_VERSION = 5.0;
};
name = Release;
};
/* End XCBuildConfiguration section */
/* Begin XCConfigurationList section */
1A2345678901234567890012 /* Build configuration list for PBXNativeTarget "MarkdownViewer" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1A2345678901234567890019 /* Debug */,
1A234567890123456789001A /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
1A2345678901234567890016 /* Build configuration list for PBXProject "MarkdownViewer" */ = {
isa = XCConfigurationList;
buildConfigurations = (
1A2345678901234567890017 /* Debug */,
1A2345678901234567890018 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
/* End XCConfigurationList section */
};
rootObject = 1A2345678901234567890015 /* Project object */;
}

View File

@@ -0,0 +1,11 @@
{
"colors" : [
{
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,13 @@
{
"images" : [
{
"idiom" : "universal",
"platform" : "ios",
"size" : "1024x1024"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

View File

@@ -0,0 +1,86 @@
import SwiftUI
import UniformTypeIdentifiers
import AppKit
struct ContentView: View {
@State private var selectedFileURL: URL?
@State private var markdownContent: String = ""
var body: some View {
NavigationSplitView {
VStack {
Button("Open Markdown File") {
openFile()
}
.padding()
Button("Test with Sample Content") {
markdownContent = "# Test\n\nThis is **test** content."
print("Set test content: \(markdownContent)")
}
.padding()
if let url = selectedFileURL {
Text("File: \(url.lastPathComponent)")
.foregroundColor(.secondary)
.font(.caption)
.truncationMode(.middle)
.padding(.horizontal)
}
Spacer()
}
.frame(minWidth: 200)
.navigationSplitViewColumnWidth(min: 200, ideal: 250)
} detail: {
if !markdownContent.isEmpty {
MarkdownRenderer(content: markdownContent)
.onAppear {
print("MarkdownRenderer appeared with content length: \(markdownContent.count)")
}
} else {
VStack {
Image(systemName: "doc.text")
.font(.system(size: 60))
.foregroundColor(.secondary)
Text("Select a markdown file to preview")
.foregroundColor(.secondary)
.padding()
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
}
}
.navigationTitle("Markdown Viewer")
}
private func openFile() {
let panel = NSOpenPanel()
panel.allowsMultipleSelection = false
panel.canChooseDirectories = false
panel.canChooseFiles = true
panel.allowedContentTypes = [UTType.plainText, UTType(filenameExtension: "md")!]
if panel.runModal() == .OK {
if let url = panel.url {
selectedFileURL = url
loadMarkdownFile(from: url)
}
}
}
private func loadMarkdownFile(from url: URL) {
do {
let content = try String(contentsOf: url, encoding: .utf8)
print("File loaded successfully, content length: \(content.count)")
print("First 100 characters: \(String(content.prefix(100)))")
markdownContent = content
} catch {
print("Error reading file: \(error)")
markdownContent = "Error loading file: \(error.localizedDescription)"
}
}
}
#Preview {
ContentView()
}

View File

@@ -0,0 +1,153 @@
import SwiftUI
import WebKit
import AppKit
struct MarkdownRenderer: NSViewRepresentable {
let content: String
func makeNSView(context: Context) -> WKWebView {
print("MarkdownRenderer makeNSView called")
let webView = WKWebView()
webView.navigationDelegate = context.coordinator
// Set a background color to see if the view is created
webView.wantsLayer = true
webView.layer?.backgroundColor = NSColor.systemYellow.cgColor
return webView
}
func updateNSView(_ webView: WKWebView, context: Context) {
print("MarkdownRenderer updateNSView called with content length: \(content.count)")
let htmlContent = convertMarkdownToHTML(content)
print("Generated HTML length: \(htmlContent.count)")
webView.loadHTMLString(htmlContent, baseURL: nil)
}
func makeCoordinator() -> Coordinator {
Coordinator()
}
class Coordinator: NSObject, WKNavigationDelegate {
func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
print("WebView finished loading")
}
func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
print("WebView navigation failed: \(error)")
}
func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
print("WebView provisional navigation failed: \(error)")
}
}
private func convertMarkdownToHTML(_ markdown: String) -> String {
guard !markdown.isEmpty else {
return "<p>No content to display</p>"
}
var html = markdown
// Handle headers (more robust approach)
let headerRegex1 = try! NSRegularExpression(pattern: "^# (.+)$", options: [.anchorsMatchLines])
let headerRegex2 = try! NSRegularExpression(pattern: "^## (.+)$", options: [.anchorsMatchLines])
let headerRegex3 = try! NSRegularExpression(pattern: "^### (.+)$", options: [.anchorsMatchLines])
let headerRegex4 = try! NSRegularExpression(pattern: "^#### (.+)$", options: [.anchorsMatchLines])
let headerRegex5 = try! NSRegularExpression(pattern: "^##### (.+)$", options: [.anchorsMatchLines])
let headerRegex6 = try! NSRegularExpression(pattern: "^###### (.+)$", options: [.anchorsMatchLines])
let range = NSRange(location: 0, length: html.count)
html = headerRegex6.stringByReplacingMatches(in: html, options: [], range: range, withTemplate: "<h6>$1</h6>")
html = headerRegex5.stringByReplacingMatches(in: html, options: [], range: NSRange(location: 0, length: html.count), withTemplate: "<h5>$1</h5>")
html = headerRegex4.stringByReplacingMatches(in: html, options: [], range: NSRange(location: 0, length: html.count), withTemplate: "<h4>$1</h4>")
html = headerRegex3.stringByReplacingMatches(in: html, options: [], range: NSRange(location: 0, length: html.count), withTemplate: "<h3>$1</h3>")
html = headerRegex2.stringByReplacingMatches(in: html, options: [], range: NSRange(location: 0, length: html.count), withTemplate: "<h2>$1</h2>")
html = headerRegex1.stringByReplacingMatches(in: html, options: [], range: NSRange(location: 0, length: html.count), withTemplate: "<h1>$1</h1>")
// Handle bold (before italic to avoid conflicts)
let boldRegex = try! NSRegularExpression(pattern: "\\*\\*(.*?)\\*\\*", options: [])
html = boldRegex.stringByReplacingMatches(in: html, options: [], range: NSRange(location: 0, length: html.count), withTemplate: "<strong>$1</strong>")
// Handle italic
let italicRegex = try! NSRegularExpression(pattern: "\\*(.*?)\\*", options: [])
html = italicRegex.stringByReplacingMatches(in: html, options: [], range: NSRange(location: 0, length: html.count), withTemplate: "<em>$1</em>")
// Handle inline code
let codeRegex = try! NSRegularExpression(pattern: "`([^`]+)`", options: [])
html = codeRegex.stringByReplacingMatches(in: html, options: [], range: NSRange(location: 0, length: html.count), withTemplate: "<code>$1</code>")
// Handle links
let linkRegex = try! NSRegularExpression(pattern: "\\[([^\\]]+)\\]\\(([^\\)]+)\\)", options: [])
html = linkRegex.stringByReplacingMatches(in: html, options: [], range: NSRange(location: 0, length: html.count), withTemplate: "<a href=\"$2\">$1</a>")
// Convert line breaks to paragraphs
let paragraphs = html.components(separatedBy: "\n\n")
html = paragraphs.map { paragraph in
let trimmed = paragraph.trimmingCharacters(in: .whitespacesAndNewlines)
if trimmed.isEmpty {
return ""
}
// Don't wrap headers in paragraphs
if trimmed.hasPrefix("<h") {
return trimmed.replacingOccurrences(of: "\n", with: "<br>")
}
return "<p>" + trimmed.replacingOccurrences(of: "\n", with: "<br>") + "</p>"
}.filter { !$0.isEmpty }.joined(separator: "\n")
let fullHTML = """
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #ffffff;
}
h1, h2, h3, h4, h5, h6 {
color: #2c3e50;
margin-top: 24px;
margin-bottom: 16px;
}
h1 { font-size: 2em; border-bottom: 2px solid #eee; padding-bottom: 10px; }
h2 { font-size: 1.5em; border-bottom: 1px solid #eee; padding-bottom: 8px; }
h3 { font-size: 1.25em; }
p { margin-bottom: 16px; }
code {
background-color: #f4f4f4;
padding: 2px 4px;
border-radius: 3px;
font-family: 'SF Mono', Monaco, Menlo, monospace;
font-size: 0.9em;
}
a {
color: #007AFF;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
strong {
font-weight: 600;
}
em {
font-style: italic;
}
</style>
</head>
<body>
\(html)
</body>
</html>
"""
return fullHTML
}
}

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<false/>
<key>com.apple.security.files.user-selected.read-only</key>
<true/>
</dict>
</plist>

View File

@@ -0,0 +1,10 @@
import SwiftUI
@main
struct MarkdownViewerApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}

View File

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}

22
sample.md Normal file
View File

@@ -0,0 +1,22 @@
# Markdown Viewer Test
This is a **sample markdown file** to test the Markdown Viewer app.
## Features Supported
- **Bold text**
- *Italic text*
- `Inline code`
- [Links](https://example.com)
### Code Example
Here's some `inline code` in a paragraph.
## Lists
Basic text formatting works great with this viewer.
### Additional Notes
The app uses WebKit to render the markdown as HTML with custom CSS styling for a clean, readable appearance.