Files
andros 2aaa8eab7b Initial commit: iOS Denote client
SwiftUI app for iPhone that connects to a WebDAV server and lists,
searches, reads and edits Denote-format notes (.org). Credentials
stored in the iOS Keychain. Server configured via a setup screen on
first launch.
2026-05-22 15:42:19 +02:00

88 lines
2.9 KiB
Swift

import Foundation
struct DenoteNote: Identifiable, Hashable {
let id: String
let filename: String
let path: String
let title: String
let keywords: [String]
let fileExtension: String
let modifiedDate: Date?
var content: String?
var displayTitle: String {
title.isEmpty ? filename : title
}
var formattedDate: String {
guard let date = modifiedDate else { return "" }
let f = DateFormatter()
f.dateStyle = .medium
f.timeStyle = .none
return f.string(from: date)
}
// Parses a Denote filename from a WebDAV href.
// Expected format: /notes/YYYYMMDDTHHMMSS(==SIGNATURE)?(--TITLE)?(__KW1_KW2)?.EXT
static func parse(href: String, modifiedDate: Date?) -> DenoteNote? {
let filename = href.split(separator: "/").last.map(String.init) ?? href
guard filename.range(of: #"^\d{8}T\d{6}"#, options: .regularExpression) != nil else { return nil }
guard !filename.hasSuffix(".organice-bak"),
!filename.hasSuffix(".bak"),
filename != "DavLock" else { return nil }
let nameWithoutExt: String
let ext: String
if let dotIndex = filename.lastIndex(of: ".") {
nameWithoutExt = String(filename[..<dotIndex])
ext = String(filename[filename.index(after: dotIndex)...])
} else {
nameWithoutExt = filename
ext = ""
}
var remaining = nameWithoutExt
let identifier: String
if let range = remaining.range(of: "--") {
let rawId = String(remaining[..<range.lowerBound])
identifier = rawId.components(separatedBy: "==").first ?? rawId
remaining = String(remaining[range.upperBound...])
} else {
identifier = remaining.components(separatedBy: "==").first ?? remaining
remaining = ""
}
let title: String
let keywords: [String]
if let kwRange = remaining.range(of: "__") {
title = slugToTitle(String(remaining[..<kwRange.lowerBound]))
keywords = String(remaining[kwRange.upperBound...]).split(separator: "_").map(String.init)
} else {
title = slugToTitle(remaining)
keywords = []
}
return DenoteNote(
id: identifier,
filename: filename,
path: href,
title: title,
keywords: keywords,
fileExtension: ext,
modifiedDate: modifiedDate,
content: nil
)
}
private static func slugToTitle(_ slug: String) -> String {
slug.replacingOccurrences(of: "-", with: " ")
.split(separator: " ")
.map { $0.prefix(1).uppercased() + $0.dropFirst() }
.joined(separator: " ")
}
func hash(into hasher: inout Hasher) { hasher.combine(id) }
static func == (lhs: DenoteNote, rhs: DenoteNote) -> Bool { lhs.id == rhs.id }
}