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
5 changes: 5 additions & 0 deletions ios/Keep/Models/Note.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,8 @@ struct Note: Identifiable, Codable, Equatable {
struct NotesResponse: Codable { let notes: [Note] }
struct NoteResponse: Codable { let note: Note }
struct OkResponse: Codable { let ok: Bool }
/// POST /api/notes/title — model-generated (or heuristic) note metadata.
struct NoteMeta: Codable {
let title: String
let summary: String?
}
17 changes: 14 additions & 3 deletions ios/Keep/Services/KeepAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,25 @@ actor KeepAPI {
try await request("/api/notes", method: "GET", as: NotesResponse.self).notes
}

func create(body: String) async throws -> Note {
try await request(
func create(body: String, title: String? = nil, summary: String? = nil) async throws -> Note {
var json: [String: Any] = ["body": body]
if let title { json["title"] = title }
if let summary, !summary.isEmpty { json["summary"] = summary }
return try await request(
"/api/notes", method: "POST",
json: ["body": body],
json: json,
as: NoteResponse.self
).note
}

func generateMeta(body: String) async throws -> NoteMeta {
try await request(
"/api/notes/title", method: "POST",
json: ["body": body],
as: NoteMeta.self
)
}

func update(id: String, patch: [String: Any]) async throws -> Note {
try await request(
"/api/notes/\(id)", method: "PATCH",
Expand Down
21 changes: 20 additions & 1 deletion ios/Keep/Services/NotesStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ final class NotesStore {
@discardableResult
func create(body: String) async -> Note? {
do {
let note = try await api.create(body: body)
let meta = try? await api.generateMeta(body: body)
let note = try await api.create(body: body, title: meta?.title, summary: meta?.summary)
notes.insert(note, at: 0)
return note
} catch {
Expand All @@ -50,13 +51,31 @@ final class NotesStore {
}
}

/// Body saves regenerate the title/summary when the lead line changes (or
/// the note has no title yet), folded into the same PATCH — mirroring the
/// web's autosave. Meta failures degrade to a plain body save.
func update(_ id: String, patch: [String: Any]) async {
var patch = patch
if let body = patch["body"] as? String,
patch["title"] == nil,
let current = notes.first(where: { $0.id == id }),
Self.firstLine(body) != Self.firstLine(current.body)
|| current.title.trimmingCharacters(in: .whitespaces).isEmpty,
let meta = try? await api.generateMeta(body: body) {
patch["title"] = meta.title
if let summary = meta.summary, !summary.isEmpty { patch["summary"] = summary }
}
do {
let updated = try await api.update(id: id, patch: patch)
if let i = notes.firstIndex(where: { $0.id == id }) { notes[i] = updated }
} catch { handle(error) }
}

private static func firstLine(_ body: String) -> String {
(body.components(separatedBy: "\n").first ?? "")
.trimmingCharacters(in: .whitespaces)
}

func togglePin(_ note: Note) async {
await update(note.id, patch: ["pinned": !note.pinned])
}
Expand Down
Loading