2025-06-20 09:06:01 -07:00
|
|
|
import SwiftUI
|
2025-06-20 10:40:43 -07:00
|
|
|
import AppKit
|
2025-06-20 09:06:01 -07:00
|
|
|
|
|
|
|
|
struct ContentView: View {
|
|
|
|
|
var body: some View {
|
2025-06-20 09:58:53 -07:00
|
|
|
VStack {
|
|
|
|
|
Image(systemName: "doc.text")
|
|
|
|
|
.font(.system(size: 80))
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
Text("Markdown Viewer")
|
|
|
|
|
.font(.largeTitle)
|
|
|
|
|
.fontWeight(.medium)
|
|
|
|
|
Text("Open markdown files from the File menu")
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
.padding(.top, 8)
|
|
|
|
|
Text("⌘O")
|
|
|
|
|
.foregroundColor(.secondary)
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.padding(.top, 4)
|
2025-06-20 09:06:01 -07:00
|
|
|
}
|
2025-06-20 09:58:53 -07:00
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
2025-06-20 09:06:01 -07:00
|
|
|
}
|
2025-06-20 09:58:53 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct MarkdownDocumentView: View {
|
|
|
|
|
let content: String
|
|
|
|
|
let fileName: String
|
2025-06-20 10:40:43 -07:00
|
|
|
@State private var isEditMode = false
|
|
|
|
|
@State private var editableContent: String
|
|
|
|
|
|
|
|
|
|
init(content: String, fileName: String) {
|
|
|
|
|
self.content = content
|
|
|
|
|
self.fileName = fileName
|
|
|
|
|
self._editableContent = State(initialValue: content)
|
|
|
|
|
print("MarkdownDocumentView: Initialized with content length: \(content.count)")
|
|
|
|
|
}
|
2025-06-20 09:06:01 -07:00
|
|
|
|
2025-06-20 09:58:53 -07:00
|
|
|
var body: some View {
|
2025-06-20 10:40:43 -07:00
|
|
|
VStack(spacing: 0) {
|
|
|
|
|
// Toolbar
|
|
|
|
|
HStack {
|
|
|
|
|
Button(action: { isEditMode.toggle() }) {
|
|
|
|
|
HStack {
|
|
|
|
|
Image(systemName: isEditMode ? "eye" : "pencil")
|
|
|
|
|
Text(isEditMode ? "Preview" : "Edit")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.buttonStyle(.bordered)
|
|
|
|
|
|
|
|
|
|
Spacer()
|
|
|
|
|
|
|
|
|
|
if isEditMode {
|
|
|
|
|
Button("Save") {
|
|
|
|
|
// TODO: Implement save functionality
|
|
|
|
|
}
|
|
|
|
|
.buttonStyle(.borderedProminent)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.padding()
|
|
|
|
|
.background(Color(NSColor.controlBackgroundColor))
|
|
|
|
|
|
|
|
|
|
// Content area
|
|
|
|
|
if isEditMode {
|
|
|
|
|
HSplitView {
|
|
|
|
|
// Editor side
|
|
|
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
|
|
|
Text("Editor")
|
|
|
|
|
.font(.headline)
|
|
|
|
|
.padding(.horizontal)
|
|
|
|
|
.padding(.top, 8)
|
|
|
|
|
.padding(.bottom, 4)
|
|
|
|
|
|
|
|
|
|
MarkdownEditor(text: $editableContent)
|
|
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Preview side
|
|
|
|
|
VStack(alignment: .leading, spacing: 0) {
|
|
|
|
|
Text("Preview")
|
|
|
|
|
.font(.headline)
|
|
|
|
|
.padding(.horizontal)
|
|
|
|
|
.padding(.top, 8)
|
|
|
|
|
.padding(.bottom, 4)
|
|
|
|
|
|
|
|
|
|
MarkdownRenderer(content: editableContent)
|
|
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
MarkdownRenderer(content: editableContent)
|
|
|
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.navigationTitle(fileName)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct MarkdownEditor: NSViewRepresentable {
|
|
|
|
|
@Binding var text: String
|
|
|
|
|
|
|
|
|
|
func makeNSView(context: Context) -> NSScrollView {
|
|
|
|
|
let scrollView = NSScrollView()
|
|
|
|
|
let textView = NSTextView()
|
|
|
|
|
|
|
|
|
|
// Configure text view
|
|
|
|
|
textView.delegate = context.coordinator
|
|
|
|
|
textView.isEditable = true
|
|
|
|
|
textView.isSelectable = true
|
|
|
|
|
textView.usesRuler = false
|
|
|
|
|
textView.isAutomaticQuoteSubstitutionEnabled = false
|
|
|
|
|
textView.isAutomaticDashSubstitutionEnabled = false
|
|
|
|
|
textView.isAutomaticTextReplacementEnabled = false
|
|
|
|
|
textView.font = NSFont.monospacedSystemFont(ofSize: 14, weight: .regular)
|
|
|
|
|
textView.textColor = NSColor.textColor
|
|
|
|
|
textView.backgroundColor = NSColor.textBackgroundColor
|
|
|
|
|
textView.string = text
|
|
|
|
|
|
|
|
|
|
// Properly set up the scroll view hierarchy first
|
|
|
|
|
scrollView.borderType = .noBorder
|
|
|
|
|
scrollView.hasVerticalScroller = true
|
|
|
|
|
scrollView.hasHorizontalScroller = false
|
|
|
|
|
scrollView.autohidesScrollers = false
|
|
|
|
|
scrollView.documentView = textView
|
|
|
|
|
|
|
|
|
|
// Configure text container for proper wrapping
|
|
|
|
|
if let textContainer = textView.textContainer {
|
|
|
|
|
textContainer.containerSize = NSSize(width: scrollView.contentSize.width, height: CGFloat.greatestFiniteMagnitude)
|
|
|
|
|
textContainer.widthTracksTextView = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Configure text view sizing
|
|
|
|
|
textView.isVerticallyResizable = true
|
|
|
|
|
textView.isHorizontallyResizable = false
|
|
|
|
|
textView.autoresizingMask = [.width]
|
|
|
|
|
textView.minSize = NSSize(width: 0, height: scrollView.contentSize.height)
|
|
|
|
|
textView.maxSize = NSSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
|
|
|
|
|
|
|
|
|
|
print("MarkdownEditor: Created with text length: \(text.count)")
|
|
|
|
|
|
|
|
|
|
return scrollView
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updateNSView(_ scrollView: NSScrollView, context: Context) {
|
|
|
|
|
guard let textView = scrollView.documentView as? NSTextView else { return }
|
|
|
|
|
|
|
|
|
|
if textView.string != text {
|
|
|
|
|
print("MarkdownEditor: Updating text from \(textView.string.count) to \(text.count) characters")
|
|
|
|
|
textView.string = text
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func makeCoordinator() -> Coordinator {
|
|
|
|
|
Coordinator(self)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
class Coordinator: NSObject, NSTextViewDelegate {
|
|
|
|
|
let parent: MarkdownEditor
|
|
|
|
|
|
|
|
|
|
init(_ parent: MarkdownEditor) {
|
|
|
|
|
self.parent = parent
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func textDidChange(_ notification: Notification) {
|
|
|
|
|
guard let textView = notification.object as? NSTextView else { return }
|
|
|
|
|
|
|
|
|
|
DispatchQueue.main.async {
|
|
|
|
|
self.parent.text = textView.string
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-06-20 09:06:01 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#Preview {
|
|
|
|
|
ContentView()
|
|
|
|
|
}
|