Skip to content

Commit

Permalink
Highlights
Browse files Browse the repository at this point in the history
This patch adds highlights (NIP-84) to Damus.

Kind 9802 are handled by all the necessary models.
We show highlighted events, longform events, and url references.
Url references also leverage text fragments to take the user to the highlighted text.

Testing
——
iPhone 15 Pro Max (17.0) Dark Mode:
https://v.nostr.build/oM6DW.mp4

iPhone 15 Pro Max (17.0) Light Mode:
https://v.nostr.build/BRrmP.mp4

iPhone SE (3rd generation) (16.4) Light Mode:
https://v.nostr.build/6GzKa.mp4
——

Closes: #2172
Closes: #1772
Closes: #1773
Closes: #2173
Closes: #2175
Changelog-Added: Highlights (NIP-84)

PATCH CHANGELOG:
V1 -> V2: addressed review comments highlights are now truncated and highlight label shown in Thread view
V2 -> V3: handle case where highlight context is smaller than the highlight content

Signed-off-by: ericholguin <[email protected]>
  • Loading branch information
ericholguin authored and danieldaquino committed Jun 21, 2024
1 parent 23c3130 commit 6376c61
Show file tree
Hide file tree
Showing 18 changed files with 573 additions and 10 deletions.
29 changes: 29 additions & 0 deletions damus.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,11 @@
5C7389B12B6EFA7100781E0A /* ProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B02B6EFA7100781E0A /* ProxyView.swift */; };
5C7389B72B9E692E00781E0A /* MutinyButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B62B9E692E00781E0A /* MutinyButton.swift */; };
5C7389B92B9E69ED00781E0A /* MutinyGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */; };
5CC8529D2BD741CD0039FFC5 /* HighlightEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */; };
5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC8529E2BD744F60039FFC5 /* HighlightView.swift */; };
5CC852A22BDED9B90039FFC5 /* HighlightDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */; };
5CC852A42BDF3CA10039FFC5 /* HighlightLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A32BDF3CA10039FFC5 /* HighlightLink.swift */; };
5CC852A62BE00F180039FFC5 /* HighlightEventRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC852A52BE00F180039FFC5 /* HighlightEventRef.swift */; };
5CC868DD2AA29B3200FB22BA /* NeutralButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */; };
5CF2DCCC2AA3AF0B00984B8D /* RelayPicView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */; };
5CF2DCCE2AABE1A500984B8D /* DamusLightGradient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */; };
Expand Down Expand Up @@ -1341,6 +1346,11 @@
5C7389B02B6EFA7100781E0A /* ProxyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProxyView.swift; sourceTree = "<group>"; };
5C7389B62B9E692E00781E0A /* MutinyButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyButton.swift; sourceTree = "<group>"; };
5C7389B82B9E69ED00781E0A /* MutinyGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MutinyGradient.swift; sourceTree = "<group>"; };
5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightEvent.swift; sourceTree = "<group>"; };
5CC8529E2BD744F60039FFC5 /* HighlightView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightView.swift; sourceTree = "<group>"; };
5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDescription.swift; sourceTree = "<group>"; };
5CC852A32BDF3CA10039FFC5 /* HighlightLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightLink.swift; sourceTree = "<group>"; };
5CC852A52BE00F180039FFC5 /* HighlightEventRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightEventRef.swift; sourceTree = "<group>"; };
5CC868DC2AA29B3200FB22BA /* NeutralButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NeutralButtonStyle.swift; sourceTree = "<group>"; };
5CF2DCCB2AA3AF0B00984B8D /* RelayPicView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayPicView.swift; sourceTree = "<group>"; };
5CF2DCCD2AABE1A500984B8D /* DamusLightGradient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DamusLightGradient.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1668,6 +1678,8 @@
B5C60C1F2B530D5100C5ECA7 /* MuteItem.swift */,
B533694D2B66D791008A805E /* MutelistManager.swift */,
D7D2A3802BF815D000E4B42B /* PushNotificationClient.swift */,
D7C28E3A2BBB4D0000EE459F /* VideoCache.swift */,
5CC8529C2BD741CD0039FFC5 /* HighlightEvent.swift */,
);
path = Models;
sourceTree = "<group>";
Expand Down Expand Up @@ -2402,6 +2414,7 @@
4CC7AAEE297F11B300430951 /* Events */ = {
isa = PBXGroup;
children = (
5CC852A02BDED9970039FFC5 /* Highlight */,
4CA927682A290F8F0098A105 /* Components */,
4CC7AAEF297F11C700430951 /* SelectedEventView.swift */,
4CC7AAF5297F1A6A00430951 /* EventBody.swift */,
Expand Down Expand Up @@ -2699,6 +2712,17 @@
path = Images;
sourceTree = "<group>";
};
5CC852A02BDED9970039FFC5 /* Highlight */ = {
isa = PBXGroup;
children = (
5CC8529E2BD744F60039FFC5 /* HighlightView.swift */,
5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */,
5CC852A32BDF3CA10039FFC5 /* HighlightLink.swift */,
5CC852A52BE00F180039FFC5 /* HighlightEventRef.swift */,
);
path = Highlight;
sourceTree = "<group>";
};
7C0F392D29B57C8F0039859C /* Extensions */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -3183,6 +3207,7 @@
4C32B94D2A9AD44700DC3548 /* Offset.swift in Sources */,
4C633350283D40E500B1C9C3 /* HomeModel.swift in Sources */,
4C987B57283FD07F0042CE38 /* FollowersModel.swift in Sources */,
5CC852A42BDF3CA10039FFC5 /* HighlightLink.swift in Sources */,
4C32B9552A9AD44700DC3548 /* ByteBuffer.swift in Sources */,
4C32B95B2A9AD44700DC3548 /* NativeObject.swift in Sources */,
3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */,
Expand Down Expand Up @@ -3322,6 +3347,7 @@
4C3AC7A12835A81400E1F516 /* SetupView.swift in Sources */,
4C06670128FC7C5900038D2A /* RelayView.swift in Sources */,
4C285C8C28398BC7008A31F1 /* Keys.swift in Sources */,
5CC852A22BDED9B90039FFC5 /* HighlightDescription.swift in Sources */,
4C94D6432BA5AEFE00C26EFF /* QuoteRepostsView.swift in Sources */,
D7EDED332B12ACAE0018B19C /* DamusUserDefaults.swift in Sources */,
4CA352AE2A76C1AC003BB08B /* FollowedNotify.swift in Sources */,
Expand Down Expand Up @@ -3365,7 +3391,9 @@
4C2859602A12A2BE004746F7 /* SupporterBadge.swift in Sources */,
4C1A9A2A29DDF54400516EAC /* DamusVideoPlayer.swift in Sources */,
4CA352A22A76AEC5003BB08B /* LikedNotify.swift in Sources */,
5CC8529F2BD744F60039FFC5 /* HighlightView.swift in Sources */,
BA37598D2ABCCE500018D73B /* PhotoCaptureProcessor.swift in Sources */,
5CC8529D2BD741CD0039FFC5 /* HighlightEvent.swift in Sources */,
4C9146FD2A2A87C200DDEA40 /* wasm.c in Sources */,
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
4C3EA64C28FF59AC00C48A62 /* bech32_util.c in Sources */,
Expand Down Expand Up @@ -3477,6 +3505,7 @@
B51C1CEB2B55A60A00E312A9 /* MuteDurationMenu.swift in Sources */,
4CB88389296AF99A00DC99E7 /* EventDetailBar.swift in Sources */,
4C32B9512A9AD44700DC3548 /* FlatbuffersErrors.swift in Sources */,
5CC852A62BE00F180039FFC5 /* HighlightEventRef.swift in Sources */,
4CE8794E2996B16A00F758CC /* RelayToggle.swift in Sources */,
4C3AC79B28306D7B00E1F516 /* Contacts.swift in Sources */,
4C3EA63D28FF52D600C48A62 /* bolt11.c in Sources */,
Expand Down
38 changes: 38 additions & 0 deletions damus/Assets.xcassets/Colors/DamusHighlight.colorset/Contents.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
{
"colors" : [
{
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0xF2",
"green" : "0xD8",
"red" : "0xF4"
}
},
"idiom" : "universal"
},
{
"appearances" : [
{
"appearance" : "luminosity",
"value" : "dark"
}
],
"color" : {
"color-space" : "srgb",
"components" : {
"alpha" : "1.000",
"blue" : "0x45",
"green" : "0x17",
"red" : "0x47"
}
},
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}
1 change: 1 addition & 0 deletions damus/Components/DamusColors.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ class DamusColors {
static let green = Color("DamusGreen")
static let purple = Color("DamusPurple")
static let deepPurple = Color("DamusDeepPurple")
static let highlight = Color("DamusHighlight")
static let blue = Color("DamusBlue")
static let bitcoin = Color("Bitcoin")
static let success = Color("DamusSuccessPrimary")
Expand Down
34 changes: 34 additions & 0 deletions damus/Models/HighlightEvent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// HighlightEvent.swift
// damus
//
// Created by eric on 4/22/24.
//

import Foundation

struct HighlightEvent {
let event: NostrEvent

var event_ref: String? = nil
var url_ref: URL? = nil
var context: String? = nil

static func parse(from ev: NostrEvent) -> HighlightEvent {
var highlight = HighlightEvent(event: ev)

for tag in ev.tags {
guard tag.count >= 2 else { continue }
switch tag[0].string() {
case "e": highlight.event_ref = tag[1].string()
case "a": highlight.event_ref = tag[1].string()
case "r": highlight.url_ref = URL(string: tag[1].string())
case "context": highlight.context = tag[1].string()
default:
break
}
}

return highlight
}
}
4 changes: 2 additions & 2 deletions damus/Models/HomeModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -184,7 +184,7 @@ class HomeModel: ContactsDelegate {
}

switch kind {
case .chat, .longform, .text:
case .chat, .longform, .text, .highlight:
handle_text_event(sub_id: sub_id, ev)
case .contacts:
handle_contact_event(sub_id: sub_id, relay_id: relay_id, ev: ev)
Expand Down Expand Up @@ -586,7 +586,7 @@ class HomeModel: ContactsDelegate {
func subscribe_to_home_filters(friends fs: [Pubkey]? = nil, relay_id: RelayURL? = nil) {
// TODO: separate likes?
var home_filter_kinds: [NostrKind] = [
.text, .longform, .boost
.text, .longform, .boost, .highlight
]
if !damus_state.settings.onlyzaps_mode {
home_filter_kinds.append(.like)
Expand Down
6 changes: 3 additions & 3 deletions damus/Models/ProfileModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,11 @@ class ProfileModel: ObservableObject, Equatable {
damus.pool.unsubscribe(sub_id: sub_id)
damus.pool.unsubscribe(sub_id: prof_subid)
}

func subscribe() {
var text_filter = NostrFilter(kinds: [.text, .longform])
var text_filter = NostrFilter(kinds: [.text, .longform, .highlight])
var profile_filter = NostrFilter(kinds: [.contacts, .metadata, .boost])

profile_filter.authors = [pubkey]

text_filter.authors = [pubkey]
Expand Down
2 changes: 1 addition & 1 deletion damus/Models/SearchModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ class SearchModel: ObservableObject {
func subscribe() {
// since 1 month
search.limit = self.limit
search.kinds = [.text, .like, .longform]
search.kinds = [.text, .like, .longform, .highlight]

//likes_filter.ids = ref_events.referenced_ids!

Expand Down
1 change: 1 addition & 0 deletions damus/Nostr/NostrKind.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ enum NostrKind: UInt32, Codable {
case longform = 30023
case zap = 9735
case zap_request = 9734
case highlight = 9802
case nwc_request = 23194
case nwc_response = 23195
case http_auth = 27235
Expand Down
2 changes: 2 additions & 0 deletions damus/Views/EventView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ struct EventView: View {
}
} else if event.known_kind == .longform {
LongformPreview(state: damus, ev: event, options: options)
} else if event.known_kind == .highlight {
HighlightView(state: damus, event: event, options: options)
} else {
TextEvent(damus: damus, event: event, pubkey: pubkey, options: options)
//.padding([.top], 6)
Expand Down
10 changes: 9 additions & 1 deletion damus/Views/Events/Components/ReplyPart.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,15 @@ struct ReplyPart: View {
var body: some View {
Group {
if let reply_ref = event.thread_reply()?.reply {
ReplyDescription(event: event, replying_to: events.lookup(reply_ref.note_id), ndb: ndb)
let replying_to = events.lookup(reply_ref.note_id)
if event.known_kind != .highlight {
ReplyDescription(event: event, replying_to: replying_to, ndb: ndb)
} else if event.known_kind == .highlight {
HighlightDescription(event: event, highlighted_event: replying_to, ndb: ndb)
}
else {
EmptyView()
}
} else {
EmptyView()
}
Expand Down
2 changes: 2 additions & 0 deletions damus/Views/Events/EventBody.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ struct EventBody: View {
if !options.contains(.truncate_content) {
note_content
}
} else if event.known_kind == .highlight {
HighlightBodyView(state: damus_state, ev: event, options: options)
} else {
note_content
}
Expand Down
54 changes: 54 additions & 0 deletions damus/Views/Events/Highlight/HighlightDescription.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
//
// HighlightDescription.swift
// damus
//
// Created by eric on 4/28/24.
//

import SwiftUI

// Modified from Reply Description
struct HighlightDescription: View {
let event: NostrEvent
let highlighted_event: NostrEvent?
let ndb: Ndb

var body: some View {
(Text(Image(systemName: "highlighter")) + Text(verbatim: " \(highlight_desc(ndb: ndb, event: event, highlighted_event: highlighted_event))"))
.font(.footnote)
.foregroundColor(.gray)
.frame(maxWidth: .infinity, alignment: .leading)

}
}

struct HighlightDescription_Previews: PreviewProvider {
static var previews: some View {
HighlightDescription(event: test_note, highlighted_event: test_note, ndb: test_damus_state.ndb)
}
}

func highlight_desc(ndb: Ndb, event: NostrEvent, highlighted_event: NostrEvent?, locale: Locale = Locale.current) -> String {
let desc = make_reply_description(event, replying_to: highlighted_event)
let pubkeys = desc.pubkeys

let bundle = bundleForLocale(locale: locale)

if desc.pubkeys.count == 0 {
return NSLocalizedString("Highlighted", bundle: bundle, comment: "Label to indicate that the user is highlighting their own post.")
}

guard let profile_txn = NdbTxn(ndb: ndb) else {
return ""
}

let names: [String] = pubkeys.map { pk in
let prof = ndb.lookup_profile_with_txn(pk, txn: profile_txn)

return Profile.displayName(profile: prof?.profile, pubkey: pk).username.truncate(maxLength: 50)
}

let uniqueNames = NSOrderedSet(array: names).array as! [String]

return String(format: NSLocalizedString("Highlighted %@", bundle: bundle, comment: "Label to indicate that the user is highlighting 1 user."), locale: locale, uniqueNames[0])
}
Loading

0 comments on commit 6376c61

Please sign in to comment.