Add support for adding comments when creating a highlight
Changelog-Added: Add support for adding comments when creating a highlight Signed-off-by: Daniel D’Aquino <daniel@daquino.me>
This commit is contained in:
@@ -399,7 +399,7 @@
|
||||
5C14C29D2BBBA40B00079FD2 /* RelayAdminDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */; };
|
||||
5C14C29F2BBBA5C600079FD2 /* RelayNipList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */; };
|
||||
5C42E78C29DB76D90086AAC1 /* EmptyUserSearchView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */; };
|
||||
5C4D9EA72C042FA5005EA0F7 /* HighlightPostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightPostView.swift */; };
|
||||
5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */; };
|
||||
5C513FBA297F72980072348F /* CustomPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FB9297F72980072348F /* CustomPicker.swift */; };
|
||||
5C513FCC2984ACA60072348F /* QRCodeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C513FCB2984ACA60072348F /* QRCodeView.swift */; };
|
||||
5C6E1DAD2A193EC2008FC15A /* GradientButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */; };
|
||||
@@ -1343,7 +1343,7 @@
|
||||
5C14C29C2BBBA40B00079FD2 /* RelayAdminDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayAdminDetail.swift; sourceTree = "<group>"; };
|
||||
5C14C29E2BBBA5C600079FD2 /* RelayNipList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelayNipList.swift; sourceTree = "<group>"; };
|
||||
5C42E78B29DB76D90086AAC1 /* EmptyUserSearchView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyUserSearchView.swift; sourceTree = "<group>"; };
|
||||
5C4D9EA62C042FA5005EA0F7 /* HighlightPostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightPostView.swift; sourceTree = "<group>"; };
|
||||
5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighlightDraftContentView.swift; sourceTree = "<group>"; };
|
||||
5C513FB9297F72980072348F /* CustomPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPicker.swift; sourceTree = "<group>"; };
|
||||
5C513FCB2984ACA60072348F /* QRCodeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QRCodeView.swift; sourceTree = "<group>"; };
|
||||
5C6E1DAC2A193EC2008FC15A /* GradientButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GradientButtonStyle.swift; sourceTree = "<group>"; };
|
||||
@@ -2725,7 +2725,7 @@
|
||||
5CC852A12BDED9B90039FFC5 /* HighlightDescription.swift */,
|
||||
5CC852A32BDF3CA10039FFC5 /* HighlightLink.swift */,
|
||||
5CC852A52BE00F180039FFC5 /* HighlightEventRef.swift */,
|
||||
5C4D9EA62C042FA5005EA0F7 /* HighlightPostView.swift */,
|
||||
5C4D9EA62C042FA5005EA0F7 /* HighlightDraftContentView.swift */,
|
||||
);
|
||||
path = Highlight;
|
||||
sourceTree = "<group>";
|
||||
@@ -3184,7 +3184,7 @@
|
||||
4C75EFAD28049CFB0006080F /* PostButton.swift in Sources */,
|
||||
D7EDED1E2B11797D0018B19C /* LongformEvent.swift in Sources */,
|
||||
504323A92A3495B6006AE6DC /* RelayModelCache.swift in Sources */,
|
||||
5C4D9EA72C042FA5005EA0F7 /* HighlightPostView.swift in Sources */,
|
||||
5C4D9EA72C042FA5005EA0F7 /* HighlightDraftContentView.swift in Sources */,
|
||||
3A8CC6CC2A2CFEF900940F5F /* StringUtil.swift in Sources */,
|
||||
D7FD12262BD345A700CF195B /* FirstAidSettingsView.swift in Sources */,
|
||||
D7870BC12AC4750B0080BA88 /* MentionView.swift in Sources */,
|
||||
|
||||
@@ -59,9 +59,12 @@ struct SelectableText: View {
|
||||
self.highlightPostingState = newValue ? .show_post_view(highlighted_text: self.highlightPostingState.highlighted_text() ?? "") : .hide
|
||||
})) {
|
||||
if let event, case .show_post_view(let highlighted_text) = self.highlightPostingState {
|
||||
HighlightPostView(damus_state: damus_state, event: event, selectedText: .constant(highlighted_text))
|
||||
.presentationDragIndicator(.visible)
|
||||
.presentationDetents([.height(selectedTextHeight + 150), .medium, .large])
|
||||
PostView(
|
||||
action: .highlighting(.init(selected_text: highlighted_text, source: .event(event))),
|
||||
damus_state: damus_state
|
||||
)
|
||||
.presentationDragIndicator(.visible)
|
||||
.presentationDetents([.height(selectedTextHeight + 450), .medium, .large])
|
||||
}
|
||||
}
|
||||
.frame(height: selectedTextHeight)
|
||||
|
||||
@@ -1109,7 +1109,7 @@ func handle_post_notification(keypair: FullKeypair, postbox: PostBox, events: Ev
|
||||
//let post = tup.0
|
||||
//let to_relays = tup.1
|
||||
print("post \(post.content)")
|
||||
guard let new_ev = post_to_event(post: post, keypair: keypair) else {
|
||||
guard let new_ev = post.to_event(keypair: keypair) else {
|
||||
return false
|
||||
}
|
||||
postbox.send(new_ev)
|
||||
|
||||
@@ -28,4 +28,5 @@ class Drafts: ObservableObject {
|
||||
@Published var post: DraftArtifacts? = nil
|
||||
@Published var replies: [NostrEvent: DraftArtifacts] = [:]
|
||||
@Published var quotes: [NostrEvent: DraftArtifacts] = [:]
|
||||
@Published var highlights: [HighlightSource: DraftArtifacts] = [:]
|
||||
}
|
||||
|
||||
@@ -32,3 +32,31 @@ struct HighlightEvent {
|
||||
return highlight
|
||||
}
|
||||
}
|
||||
|
||||
struct HighlightContentDraft: Hashable {
|
||||
let selected_text: String
|
||||
let source: HighlightSource
|
||||
}
|
||||
|
||||
enum HighlightSource: Hashable {
|
||||
case event(NostrEvent)
|
||||
case external_url(URL)
|
||||
|
||||
func tags() -> [[String]] {
|
||||
switch self {
|
||||
case .event(let event):
|
||||
return [ ["e", "\(event.id)"] ]
|
||||
case .external_url(let url):
|
||||
return [ ["r", "\(url)"] ]
|
||||
}
|
||||
}
|
||||
|
||||
func ref() -> RefId {
|
||||
switch self {
|
||||
case .event(let event):
|
||||
return .event(event.id)
|
||||
case .external_url(let url):
|
||||
return .reference(url.absoluteString)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,12 +290,3 @@ func make_post_tags(post_blocks: [Block], tags: [[String]]) -> PostTags {
|
||||
|
||||
return PostTags(blocks: post_blocks, tags: new_tags)
|
||||
}
|
||||
|
||||
func post_to_event(post: NostrPost, keypair: FullKeypair) -> NostrEvent? {
|
||||
let post_blocks = parse_post_blocks(content: post.content)
|
||||
let post_tags = make_post_tags(post_blocks: post_blocks, tags: post.tags)
|
||||
let content = post_tags.blocks
|
||||
.map(\.asString)
|
||||
.joined(separator: "")
|
||||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: post.kind.rawValue, tags: post_tags.tags)
|
||||
}
|
||||
|
||||
@@ -17,10 +17,40 @@ struct NostrPost {
|
||||
self.kind = kind
|
||||
self.tags = tags
|
||||
}
|
||||
|
||||
func to_event(keypair: FullKeypair) -> NostrEvent? {
|
||||
let post_blocks = self.parse_blocks()
|
||||
let post_tags = make_post_tags(post_blocks: post_blocks, tags: self.tags)
|
||||
let content = post_tags.blocks
|
||||
.map(\.asString)
|
||||
.joined(separator: "")
|
||||
|
||||
if self.kind == .highlight {
|
||||
var new_tags = post_tags.tags.filter({ $0[safe: 0] != "comment" })
|
||||
if content.count > 0 {
|
||||
new_tags.append(["comment", content])
|
||||
}
|
||||
return NostrEvent(content: self.content, keypair: keypair.to_keypair(), kind: self.kind.rawValue, tags: new_tags)
|
||||
}
|
||||
|
||||
return NostrEvent(content: content, keypair: keypair.to_keypair(), kind: self.kind.rawValue, tags: post_tags.tags)
|
||||
}
|
||||
|
||||
func parse_blocks() -> [Block] {
|
||||
guard let content_for_parsing = self.default_content_for_block_parsing() else { return [] }
|
||||
return parse_post_blocks(content: content_for_parsing)
|
||||
}
|
||||
|
||||
private func default_content_for_block_parsing() -> String? {
|
||||
switch kind {
|
||||
case .highlight:
|
||||
return tags.filter({ $0[safe: 0] == "comment" }).first?[safe: 1]
|
||||
default:
|
||||
return self.content
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Return a list of tags
|
||||
func parse_post_blocks(content: String) -> [Block] {
|
||||
return parse_note_content(content: .content(content, nil)).blocks
|
||||
}
|
||||
|
||||
42
damus/Views/Events/Highlight/HighlightDraftContentView.swift
Normal file
42
damus/Views/Events/Highlight/HighlightDraftContentView.swift
Normal file
@@ -0,0 +1,42 @@
|
||||
//
|
||||
// HighlightDraftContentView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by eric on 5/26/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct HighlightDraftContentView: View {
|
||||
let draft: HighlightContentDraft
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 10) {
|
||||
HStack {
|
||||
var attributedString: AttributedString {
|
||||
var attributedString = AttributedString(draft.selected_text)
|
||||
|
||||
if let range = attributedString.range(of: draft.selected_text) {
|
||||
attributedString[range].backgroundColor = DamusColors.highlight
|
||||
}
|
||||
|
||||
return attributedString
|
||||
}
|
||||
|
||||
Text(attributedString)
|
||||
.lineSpacing(5)
|
||||
.padding(10)
|
||||
}
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 25).fill(DamusColors.highlight).frame(width: 4),
|
||||
alignment: .leading
|
||||
)
|
||||
|
||||
if case .external_url(let url) = draft.source {
|
||||
LinkViewRepresentable(meta: .url(url))
|
||||
.frame(height: 50)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
//
|
||||
// HighlightPostView.swift
|
||||
// damus
|
||||
//
|
||||
// Created by eric on 5/26/24.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct HighlightPostView: View {
|
||||
let damus_state: DamusState
|
||||
let event: NostrEvent
|
||||
@Binding var selectedText: String
|
||||
|
||||
@Environment(\.dismiss) var dismiss
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
VStack {
|
||||
HStack(spacing: 5.0) {
|
||||
Button(action: {
|
||||
dismiss()
|
||||
}, label: {
|
||||
Text("Cancel", comment: "Button to cancel out of highlighting a note.")
|
||||
.padding(10)
|
||||
})
|
||||
.buttonStyle(NeutralButtonStyle())
|
||||
|
||||
Spacer()
|
||||
|
||||
Button(NSLocalizedString("Post", comment: "Button to post a highlight.")) {
|
||||
let tags: [[String]] = [ ["e", "\(self.event.id)"] ]
|
||||
|
||||
let kind = NostrKind.highlight.rawValue
|
||||
guard let ev = NostrEvent(content: selectedText, keypair: damus_state.keypair, kind: kind, tags: tags) else {
|
||||
return
|
||||
}
|
||||
damus_state.postbox.send(ev)
|
||||
dismiss()
|
||||
}
|
||||
.bold()
|
||||
.buttonStyle(GradientButtonStyle(padding: 10))
|
||||
}
|
||||
|
||||
Divider()
|
||||
.foregroundColor(DamusColors.neutral3)
|
||||
.padding(.top, 5)
|
||||
}
|
||||
.frame(height: 30)
|
||||
.padding()
|
||||
.padding(.top, 15)
|
||||
|
||||
HStack {
|
||||
var attributedString: AttributedString {
|
||||
var attributedString = AttributedString(selectedText)
|
||||
|
||||
if let range = attributedString.range(of: selectedText) {
|
||||
attributedString[range].backgroundColor = DamusColors.highlight
|
||||
}
|
||||
|
||||
return attributedString
|
||||
}
|
||||
|
||||
Text(attributedString)
|
||||
.lineSpacing(5)
|
||||
.padding(10)
|
||||
}
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 25).fill(DamusColors.highlight).frame(width: 4),
|
||||
alignment: .leading
|
||||
)
|
||||
.padding()
|
||||
|
||||
Spacer()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -30,15 +30,18 @@ enum PostAction {
|
||||
case replying_to(NostrEvent)
|
||||
case quoting(NostrEvent)
|
||||
case posting(PostTarget)
|
||||
case highlighting(HighlightContentDraft)
|
||||
|
||||
var ev: NostrEvent? {
|
||||
switch self {
|
||||
case .replying_to(let ev):
|
||||
return ev
|
||||
case .quoting(let ev):
|
||||
return ev
|
||||
case .posting:
|
||||
return nil
|
||||
case .replying_to(let ev):
|
||||
return ev
|
||||
case .quoting(let ev):
|
||||
return ev
|
||||
case .posting:
|
||||
return nil
|
||||
case .highlighting:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -128,7 +131,12 @@ struct PostView: View {
|
||||
}
|
||||
|
||||
var posting_disabled: Bool {
|
||||
return is_post_empty || uploading_disabled
|
||||
switch action {
|
||||
case .highlighting(_):
|
||||
return false
|
||||
default:
|
||||
return is_post_empty || uploading_disabled
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a valid height for the text box, even when textHeight is not a number
|
||||
@@ -204,6 +212,8 @@ struct PostView: View {
|
||||
damus_state.drafts.quotes.removeValue(forKey: quoting)
|
||||
case .posting:
|
||||
damus_state.drafts.post = nil
|
||||
case .highlighting(let draft):
|
||||
damus_state.drafts.highlights.removeValue(forKey: draft.source)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -371,6 +381,9 @@ struct PostView: View {
|
||||
if case .quoting(let ev) = action {
|
||||
BuilderEventView(damus: damus_state, event: ev)
|
||||
}
|
||||
else if case .highlighting(let draft) = action {
|
||||
HighlightDraftContentView(draft: draft)
|
||||
}
|
||||
}
|
||||
.padding(.horizontal)
|
||||
}
|
||||
@@ -454,14 +467,15 @@ struct PostView: View {
|
||||
let loaded_draft = load_draft()
|
||||
|
||||
switch action {
|
||||
case .replying_to(let replying_to):
|
||||
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
|
||||
case .quoting(let quoting):
|
||||
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
|
||||
case .posting(let target):
|
||||
guard !loaded_draft else { break }
|
||||
|
||||
fill_target_content(target: target)
|
||||
case .replying_to(let replying_to):
|
||||
references = gather_reply_ids(our_pubkey: damus_state.pubkey, from: replying_to)
|
||||
case .quoting(let quoting):
|
||||
references = gather_quote_ids(our_pubkey: damus_state.pubkey, from: quoting)
|
||||
case .posting(let target):
|
||||
guard !loaded_draft else { break }
|
||||
fill_target_content(target: target)
|
||||
case .highlighting(let draft):
|
||||
references = [draft.source.ref()]
|
||||
}
|
||||
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
@@ -597,6 +611,8 @@ func set_draft_for_post(drafts: Drafts, action: PostAction, artifacts: DraftArti
|
||||
drafts.quotes[ev] = artifacts
|
||||
case .posting:
|
||||
drafts.post = artifacts
|
||||
case .highlighting(let draft):
|
||||
drafts.highlights[draft.source] = artifacts
|
||||
}
|
||||
}
|
||||
|
||||
@@ -608,6 +624,8 @@ func load_draft_for_post(drafts: Drafts, action: PostAction) -> DraftArtifacts?
|
||||
return drafts.quotes[ev]
|
||||
case .posting:
|
||||
return drafts.post
|
||||
case .highlighting(let draft):
|
||||
return drafts.highlights[draft.source]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -669,18 +687,20 @@ func build_post(state: DamusState, post: NSMutableAttributedString, action: Post
|
||||
var tags: [[String]] = []
|
||||
|
||||
switch action {
|
||||
case .replying_to(let replying_to):
|
||||
// start off with the reply tags
|
||||
tags = nip10_reply_tags(replying_to: replying_to, keypair: state.keypair)
|
||||
case .replying_to(let replying_to):
|
||||
// start off with the reply tags
|
||||
tags = nip10_reply_tags(replying_to: replying_to, keypair: state.keypair)
|
||||
|
||||
case .quoting(let ev):
|
||||
content.append(" nostr:" + bech32_note_id(ev.id))
|
||||
case .quoting(let ev):
|
||||
content.append(" nostr:" + bech32_note_id(ev.id))
|
||||
|
||||
if let quoted_ev = state.events.lookup(ev.id) {
|
||||
tags.append(["p", quoted_ev.pubkey.hex()])
|
||||
}
|
||||
case .posting(let postTarget):
|
||||
break
|
||||
if let quoted_ev = state.events.lookup(ev.id) {
|
||||
tags.append(["p", quoted_ev.pubkey.hex()])
|
||||
}
|
||||
case .posting(let postTarget):
|
||||
break
|
||||
case .highlighting(let draft):
|
||||
break
|
||||
}
|
||||
|
||||
// include pubkeys
|
||||
@@ -690,6 +710,17 @@ func build_post(state: DamusState, post: NSMutableAttributedString, action: Post
|
||||
|
||||
// append additional tags
|
||||
tags += uploadedMedias.compactMap { $0.metadata?.to_tag() }
|
||||
|
||||
switch action {
|
||||
case .highlighting(let draft):
|
||||
tags.append(contentsOf: draft.source.tags())
|
||||
if !(content.isEmpty || content.allSatisfy { $0.isWhitespace }) {
|
||||
tags.append(["comment", content])
|
||||
}
|
||||
return NostrPost(content: draft.selected_text, kind: .highlight, tags: tags)
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
return NostrPost(content: content, kind: .text, tags: tags)
|
||||
}
|
||||
|
||||
@@ -240,7 +240,7 @@ class ReplyTests: XCTestCase {
|
||||
let content = "this is a @\(pk.npub) mention"
|
||||
let blocks = parse_post_blocks(content: content)
|
||||
let post = NostrPost(content: content, tags: [["e", evid.hex()]])
|
||||
let ev = post_to_event(post: post, keypair: test_keypair_full)!
|
||||
let ev = post.to_event(keypair: test_keypair_full)!
|
||||
|
||||
XCTAssertEqual(ev.tags.count, 2)
|
||||
XCTAssertEqual(blocks.count, 3)
|
||||
@@ -255,7 +255,7 @@ class ReplyTests: XCTestCase {
|
||||
let content = "this is a @\(nsec) mention"
|
||||
let blocks = parse_post_blocks(content: content)
|
||||
let post = NostrPost(content: content, tags: [["e", evid.hex()]])
|
||||
let ev = post_to_event(post: post, keypair: test_keypair_full)!
|
||||
let ev = post.to_event(keypair: test_keypair_full)!
|
||||
|
||||
XCTAssertEqual(ev.tags.count, 2)
|
||||
XCTAssertEqual(blocks.count, 3)
|
||||
@@ -275,7 +275,7 @@ class ReplyTests: XCTestCase {
|
||||
]
|
||||
|
||||
let post = NostrPost(content: "this is a (@\(pubkey.npub)) mention", tags: tags)
|
||||
let ev = post_to_event(post: post, keypair: test_keypair_full)!
|
||||
let ev = post.to_event(keypair: test_keypair_full)!
|
||||
|
||||
XCTAssertEqual(ev.content, "this is a (nostr:\(pubkey.npub)) mention")
|
||||
XCTAssertEqual(ev.tags[2][1].string(), pubkey.description)
|
||||
|
||||
@@ -193,7 +193,7 @@ class damusTests: XCTestCase {
|
||||
|
||||
func testMakeHashtagPost() {
|
||||
let post = NostrPost(content: "#damus some content #bitcoin derp #かっこいい wow", tags: [])
|
||||
let ev = post_to_event(post: post, keypair: test_keypair_full)!
|
||||
let ev = post.to_event(keypair: test_keypair_full)!
|
||||
|
||||
XCTAssertEqual(ev.tags.count, 3)
|
||||
XCTAssertEqual(ev.content, "#damus some content #bitcoin derp #かっこいい wow")
|
||||
@@ -270,7 +270,7 @@ class damusTests: XCTestCase {
|
||||
|
||||
private func createEventFromContentString(_ content: String) -> NostrEvent {
|
||||
let post = NostrPost(content: content, tags: [])
|
||||
guard let ev = post_to_event(post: post, keypair: test_keypair_full) else {
|
||||
guard let ev = post.to_event(keypair: test_keypair_full) else {
|
||||
XCTFail("Could not create event")
|
||||
return test_note
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user