Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 63 additions & 9 deletions ios/KeepMac/MacNoteDetail.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,22 @@ import SwiftUI
/// The editor pane. Autosaves the body (debounced ~0.6s, like the web), and a
/// new note (`note == nil`) is created on first non-empty save then patched —
/// mirroring the web/iOS create→edit bridge. Pin / archive / trash live in the
/// toolbar.
/// toolbar; the title is renamed inline, committing on Enter or focus loss
/// like the web editor's header field.
struct MacNoteDetail: View {
@Environment(NotesStore.self) private var store

let note: Note?
var onCreated: (String) -> Void = { _ in }

@State private var text = ""
@State private var title = ""
@FocusState private var titleFocused: Bool
@State private var createdId: String?
@State private var saveTask: Task<Void, Never>?

private let titleCharLimit = 36

/// The live note (existing, or the one we just created), pulled fresh from
/// the store so toolbar state (pin/archive) reflects the latest.
private var current: Note? {
Expand All @@ -23,14 +28,25 @@ struct MacNoteDetail: View {
}

var body: some View {
TextEditor(text: $text)
.font(.body)
.textEditorStyle(.plain)
.scrollContentBackground(.hidden)
.padding(12)
.navigationTitle(current?.displayTitle ?? "New Note")
.onAppear { text = note?.body ?? "" }
.onChange(of: text) { _, value in scheduleSave(value) }
VStack(alignment: .leading, spacing: 0) {
if let n = current {
titleField(for: n)
}
TextEditor(text: $text)
.font(.body)
.textEditorStyle(.plain)
.scrollContentBackground(.hidden)
}
.padding(12)
.navigationTitle(current?.displayTitle ?? "New Note")
.onAppear {
text = note?.body ?? ""
title = current?.displayTitle ?? ""
}
.onChange(of: current?.id) { _, _ in
if !titleFocused { title = current?.displayTitle ?? "" }
}
.onChange(of: text) { _, value in scheduleSave(value) }
.toolbar {
if let n = current {
ToolbarItemGroup {
Expand Down Expand Up @@ -61,6 +77,44 @@ struct MacNoteDetail: View {
}
}

@ViewBuilder
private func titleField(for n: Note) -> some View {
if n.trashed {
Text(n.displayTitle)
.font(.title3.weight(.semibold))
.foregroundStyle(.secondary)
.padding(.bottom, 8)
} else {
TextField("Title", text: $title)
.font(.title3.weight(.semibold))
.textFieldStyle(.plain)
.focused($titleFocused)
.onSubmit { titleFocused = false }
.onExitCommand {
title = n.displayTitle
titleFocused = false
}
.onChange(of: title) { _, value in
if value.count > titleCharLimit {
title = String(value.prefix(titleCharLimit))
}
}
.onChange(of: titleFocused) { _, focused in
if !focused { commitTitle(for: n) }
}
.padding(.bottom, 8)
}
}

private func commitTitle(for n: Note) {
let trimmed = title.trimmingCharacters(in: .whitespaces)
guard !trimmed.isEmpty, trimmed != n.displayTitle else {
title = n.displayTitle
return
}
Task { await store.update(n.id, patch: ["title": trimmed]) }
}

private func scheduleSave(_ value: String) {
saveTask?.cancel()
saveTask = Task {
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading