Huge refactor to add better structure to the project. Separating features with their associated view and model structure. This should be better organization and will allow us to improve the overall architecture in the future. I forsee many more improvements that can follow this change. e.g. MVVM Arch As well as cleaning up duplicate, unused, functionality. Many files have global functions that can also be moved or be renamed. damus/ ├── Features/ │ ├── <Feature>/ │ │ ├── Views/ │ │ └── Models/ ├── Shared/ │ ├── Components/ │ ├── Media/ │ ├── Buttons/ │ ├── Extensions/ │ ├── Empty Views/ │ ├── ErrorHandling/ │ ├── Modifiers/ │ └── Utilities/ ├── Core/ │ ├── Nostr/ │ ├── NIPs/ │ ├── DIPs/ │ ├── Types/ │ ├── Networking/ │ └── Storage/ Signed-off-by: ericholguin <ericholguin@apache.org>
216 lines
5.7 KiB
Swift
216 lines
5.7 KiB
Swift
//
|
|
// ReferencedId.swift
|
|
// damus
|
|
//
|
|
// Created by William Casarin on 2023-07-22.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
func tagref_should_be_id(_ tag: NdbTagElem) -> Bool {
|
|
return !(tag.matches_char("t") || tag.matches_char("d"))
|
|
}
|
|
|
|
|
|
struct References<T: TagConvertible>: Sequence, IteratorProtocol {
|
|
let tags: TagsSequence
|
|
var tags_iter: TagsIterator
|
|
|
|
init(tags: TagsSequence) {
|
|
self.tags = tags
|
|
self.tags_iter = tags.makeIterator()
|
|
}
|
|
|
|
mutating func next() -> T? {
|
|
while let tag = tags_iter.next() {
|
|
guard let evref = T.from_tag(tag: tag) else { continue }
|
|
return evref
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
extension References {
|
|
var first: T? {
|
|
self.first(where: { _ in true })
|
|
}
|
|
|
|
var last: T? {
|
|
var last: T? = nil
|
|
for t in self {
|
|
last = t
|
|
}
|
|
return last
|
|
}
|
|
}
|
|
|
|
|
|
// NdbTagElem transition helpers
|
|
extension String {
|
|
func string() -> String {
|
|
return self
|
|
}
|
|
|
|
func first_char() -> AsciiCharacter? {
|
|
self.first.flatMap { chr in AsciiCharacter(chr) }
|
|
}
|
|
|
|
func matches_char(_ c: AsciiCharacter) -> Bool {
|
|
return self.first == c.character
|
|
}
|
|
|
|
func matches_str(_ str: String) -> Bool {
|
|
return self == str
|
|
}
|
|
}
|
|
|
|
enum FollowRef: TagKeys, Hashable, TagConvertible, Equatable {
|
|
|
|
// NOTE: When adding cases make sure to update key and from_tag
|
|
case pubkey(Pubkey)
|
|
case hashtag(String)
|
|
|
|
var key: FollowKeys {
|
|
switch self {
|
|
case .hashtag: return .t
|
|
case .pubkey: return .p
|
|
}
|
|
}
|
|
|
|
enum FollowKeys: AsciiCharacter, TagKey, CustomStringConvertible {
|
|
case p, t
|
|
|
|
var keychar: AsciiCharacter { self.rawValue }
|
|
var description: String { self.rawValue.description }
|
|
}
|
|
|
|
static func from_tag(tag: TagSequence) -> FollowRef? {
|
|
guard tag.count >= 2 else { return nil }
|
|
|
|
var i = tag.makeIterator()
|
|
|
|
guard let t0 = i.next(),
|
|
let c = t0.single_char,
|
|
let fkey = FollowKeys(rawValue: c),
|
|
let t1 = i.next()
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
switch fkey {
|
|
case .p: return t1.id().map({ .pubkey(Pubkey($0)) })
|
|
case .t: return .hashtag(t1.string())
|
|
}
|
|
}
|
|
|
|
var tag: [String] {
|
|
[key.description, self.description]
|
|
}
|
|
|
|
var description: String {
|
|
switch self {
|
|
case .pubkey(let pubkey): return pubkey.description
|
|
case .hashtag(let string): return string
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Models common tag references defined by the Nostr protocol, and their associated values.
|
|
///
|
|
/// For example, this raw JSON tag sequence:
|
|
/// ```json
|
|
/// ["p", "8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6"]
|
|
/// ```
|
|
///
|
|
/// would be parsed into something equivalent to `.pubkey(Pubkey(hex: "8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6"))`
|
|
///
|
|
/// ## Notes
|
|
///
|
|
/// - Not all tag information from all NIPs can be modelled using this alone, as some NIPs may define extra associated values for specific event types. You may need to use a specialized type for some event kinds
|
|
///
|
|
enum RefId: TagConvertible, TagKeys, Equatable, Hashable {
|
|
case event(NoteId)
|
|
case pubkey(Pubkey)
|
|
case quote(QuoteId)
|
|
case hashtag(Hashtag)
|
|
case param(TagElem)
|
|
case naddr(NAddr)
|
|
case reference(String)
|
|
|
|
/// The key that defines the type of reference being made
|
|
var key: RefKey {
|
|
switch self {
|
|
case .event: return .e
|
|
case .pubkey: return .p
|
|
case .quote: return .q
|
|
case .hashtag: return .t
|
|
case .param: return .d
|
|
case .naddr: return .a
|
|
case .reference: return .r
|
|
}
|
|
}
|
|
|
|
/// Defines the type of reference being made on a Nostr event tag
|
|
///
|
|
/// Example:
|
|
/// ```json
|
|
/// ["p", "8b2be0a0ad34805d76679272c28a77dbede9adcbfdca48c681ec8b624a1208a6"]
|
|
/// ```
|
|
///
|
|
/// The `RefKey` is "p"
|
|
enum RefKey: AsciiCharacter, TagKey, CustomStringConvertible {
|
|
case e, p, t, d, q, a, r
|
|
|
|
var keychar: AsciiCharacter {
|
|
self.rawValue
|
|
}
|
|
|
|
var description: String {
|
|
self.keychar.description
|
|
}
|
|
}
|
|
|
|
/// A raw nostr-style tag sequence representation of this object
|
|
var tag: [String] {
|
|
[self.key.description, self.description]
|
|
}
|
|
|
|
/// Describes what is being referenced, as a `String`
|
|
var description: String {
|
|
switch self {
|
|
case .event(let noteId): return noteId.hex()
|
|
case .pubkey(let pubkey): return pubkey.hex()
|
|
case .quote(let quote): return quote.hex()
|
|
case .hashtag(let string): return string.hashtag
|
|
case .param(let string): return string.string()
|
|
case .naddr(let naddr):
|
|
return naddr.kind.description + ":" + naddr.author.hex() + ":" + naddr.identifier
|
|
case .reference(let string):
|
|
return string
|
|
}
|
|
}
|
|
|
|
/// Parses a raw tag sequence
|
|
static func from_tag(tag: TagSequence) -> RefId? {
|
|
var i = tag.makeIterator()
|
|
|
|
guard tag.count >= 2,
|
|
let t0 = i.next(),
|
|
let key = t0.single_char,
|
|
let rkey = RefKey(rawValue: key),
|
|
let t1 = i.next()
|
|
else { return nil }
|
|
|
|
switch rkey {
|
|
case .e: return t1.id().map({ .event(NoteId($0)) })
|
|
case .p: return t1.id().map({ .pubkey(Pubkey($0)) })
|
|
case .q: return t1.id().map({ .quote(QuoteId($0)) })
|
|
case .t: return .hashtag(Hashtag(hashtag: t1.string()))
|
|
case .d: return .param(t1)
|
|
case .a: return .naddr(NAddr(identifier: "", author: Pubkey(Data()), relays: [], kind: 0))
|
|
case .r: return .reference(t1.string())
|
|
}
|
|
}
|
|
}
|
|
|