move some stuff around, reply desc
Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
@@ -24,6 +24,8 @@
|
|||||||
4C363A94282704FA006E126D /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A93282704FA006E126D /* Post.swift */; };
|
4C363A94282704FA006E126D /* Post.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A93282704FA006E126D /* Post.swift */; };
|
||||||
4C363A962827096D006E126D /* PostBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A952827096D006E126D /* PostBlock.swift */; };
|
4C363A962827096D006E126D /* PostBlock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A952827096D006E126D /* PostBlock.swift */; };
|
||||||
4C363A9828283441006E126D /* TestingPrivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9728283441006E126D /* TestingPrivate.swift */; };
|
4C363A9828283441006E126D /* TestingPrivate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9728283441006E126D /* TestingPrivate.swift */; };
|
||||||
|
4C363A9A28283854006E126D /* Reply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9928283854006E126D /* Reply.swift */; };
|
||||||
|
4C363A9C282838B9006E126D /* EventRef.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C363A9B282838B9006E126D /* EventRef.swift */; };
|
||||||
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */; };
|
4C3BEFD22819DB9B00B3DE84 /* ProfileModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */; };
|
||||||
4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
4C3BEFD42819DE8F00B3DE84 /* NostrKind.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */; };
|
||||||
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD5281D995700B3DE84 /* ActionBarModel.swift */; };
|
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4C3BEFD5281D995700B3DE84 /* ActionBarModel.swift */; };
|
||||||
@@ -101,6 +103,8 @@
|
|||||||
4C363A93282704FA006E126D /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
|
4C363A93282704FA006E126D /* Post.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Post.swift; sourceTree = "<group>"; };
|
||||||
4C363A952827096D006E126D /* PostBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostBlock.swift; sourceTree = "<group>"; };
|
4C363A952827096D006E126D /* PostBlock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PostBlock.swift; sourceTree = "<group>"; };
|
||||||
4C363A9728283441006E126D /* TestingPrivate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingPrivate.swift; sourceTree = "<group>"; };
|
4C363A9728283441006E126D /* TestingPrivate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestingPrivate.swift; sourceTree = "<group>"; };
|
||||||
|
4C363A9928283854006E126D /* Reply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Reply.swift; sourceTree = "<group>"; };
|
||||||
|
4C363A9B282838B9006E126D /* EventRef.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventRef.swift; sourceTree = "<group>"; };
|
||||||
4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModel.swift; sourceTree = "<group>"; };
|
4C3BEFD12819DB9B00B3DE84 /* ProfileModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProfileModel.swift; sourceTree = "<group>"; };
|
||||||
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrKind.swift; sourceTree = "<group>"; };
|
4C3BEFD32819DE8F00B3DE84 /* NostrKind.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NostrKind.swift; sourceTree = "<group>"; };
|
||||||
4C3BEFD5281D995700B3DE84 /* ActionBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBarModel.swift; sourceTree = "<group>"; };
|
4C3BEFD5281D995700B3DE84 /* ActionBarModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionBarModel.swift; sourceTree = "<group>"; };
|
||||||
@@ -188,6 +192,8 @@
|
|||||||
4C363A912825FCF2006E126D /* ProfileUpdate.swift */,
|
4C363A912825FCF2006E126D /* ProfileUpdate.swift */,
|
||||||
4C363A93282704FA006E126D /* Post.swift */,
|
4C363A93282704FA006E126D /* Post.swift */,
|
||||||
4C363A952827096D006E126D /* PostBlock.swift */,
|
4C363A952827096D006E126D /* PostBlock.swift */,
|
||||||
|
4C363A9928283854006E126D /* Reply.swift */,
|
||||||
|
4C363A9B282838B9006E126D /* EventRef.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -475,6 +481,7 @@
|
|||||||
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
|
4C75EFB328049D640006080F /* NostrEvent.swift in Sources */,
|
||||||
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
|
4CA2EFA0280E37AC0044ACD8 /* TimelineView.swift in Sources */,
|
||||||
4C363A8428233689006E126D /* Parser.swift in Sources */,
|
4C363A8428233689006E126D /* Parser.swift in Sources */,
|
||||||
|
4C363A9A28283854006E126D /* Reply.swift in Sources */,
|
||||||
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
4C3BEFDC281DCE6100B3DE84 /* Liked.swift in Sources */,
|
||||||
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
4C75EFB128049D510006080F /* NostrResponse.swift in Sources */,
|
||||||
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
4CEE2AF7280B2DEA00AB5EEF /* ProfileName.swift in Sources */,
|
||||||
@@ -488,6 +495,7 @@
|
|||||||
4C363A94282704FA006E126D /* Post.swift in Sources */,
|
4C363A94282704FA006E126D /* Post.swift in Sources */,
|
||||||
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
4C363A8828236948006E126D /* BlocksView.swift in Sources */,
|
||||||
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
4C75EFAF28049D350006080F /* NostrFilter.swift in Sources */,
|
||||||
|
4C363A9C282838B9006E126D /* EventRef.swift in Sources */,
|
||||||
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
|
4C8682872814DE470026224F /* ProfileView.swift in Sources */,
|
||||||
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
4CE6DF1627F8DEBF00C66700 /* RelayConnection.swift in Sources */,
|
||||||
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
4C3BEFD6281D995700B3DE84 /* ActionBarModel.swift in Sources */,
|
||||||
|
|||||||
153
damus/Models/EventRef.swift
Normal file
153
damus/Models/EventRef.swift
Normal file
@@ -0,0 +1,153 @@
|
|||||||
|
//
|
||||||
|
// EventRef.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-05-08.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum EventRef {
|
||||||
|
case mention(Mention)
|
||||||
|
case thread_id(ReferencedId)
|
||||||
|
case reply(ReferencedId)
|
||||||
|
case reply_to_root(ReferencedId)
|
||||||
|
|
||||||
|
var is_mention: Mention? {
|
||||||
|
if case .mention(let m) = self {
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_direct_reply: ReferencedId? {
|
||||||
|
switch self {
|
||||||
|
case .mention:
|
||||||
|
return nil
|
||||||
|
case .thread_id:
|
||||||
|
return nil
|
||||||
|
case .reply(let refid):
|
||||||
|
return refid
|
||||||
|
case .reply_to_root(let refid):
|
||||||
|
return refid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_thread_id: ReferencedId? {
|
||||||
|
switch self {
|
||||||
|
case .mention(let mention):
|
||||||
|
return nil
|
||||||
|
case .thread_id(let referencedId):
|
||||||
|
return referencedId
|
||||||
|
case .reply(let referencedId):
|
||||||
|
return nil
|
||||||
|
case .reply_to_root(let referencedId):
|
||||||
|
return referencedId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var is_reply: ReferencedId? {
|
||||||
|
switch self {
|
||||||
|
case .mention:
|
||||||
|
return nil
|
||||||
|
case .thread_id:
|
||||||
|
return nil
|
||||||
|
case .reply(let refid):
|
||||||
|
return refid
|
||||||
|
case .reply_to_root(let refid):
|
||||||
|
return refid
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func has_any_e_refs(_ tags: [[String]]) -> Bool {
|
||||||
|
for tag in tags {
|
||||||
|
if tag.count >= 2 && tag[0] == "e" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> {
|
||||||
|
return blocks.reduce(into: []) { acc, block in
|
||||||
|
switch block {
|
||||||
|
case .mention(let m):
|
||||||
|
if m.type == type {
|
||||||
|
acc.insert(m.index)
|
||||||
|
}
|
||||||
|
case .text:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func interp_event_refs_without_mentions(_ refs: [ReferencedId]) -> [EventRef] {
|
||||||
|
if refs.count == 0 {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
if refs.count == 1 {
|
||||||
|
return [.reply_to_root(refs[0])]
|
||||||
|
}
|
||||||
|
|
||||||
|
var evrefs: [EventRef] = []
|
||||||
|
var first: Bool = true
|
||||||
|
for ref in refs {
|
||||||
|
if first {
|
||||||
|
evrefs.append(.thread_id(ref))
|
||||||
|
first = false
|
||||||
|
} else {
|
||||||
|
evrefs.append(.reply(ref))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return evrefs
|
||||||
|
}
|
||||||
|
|
||||||
|
func interp_event_refs_with_mentions(tags: [[String]], mention_indices: Set<Int>) -> [EventRef] {
|
||||||
|
var mentions: [EventRef] = []
|
||||||
|
var ev_refs: [ReferencedId] = []
|
||||||
|
var i: Int = 0
|
||||||
|
|
||||||
|
for tag in tags {
|
||||||
|
if tag.count >= 2 && tag[0] == "e" {
|
||||||
|
let ref = tag_to_refid(tag)!
|
||||||
|
if mention_indices.contains(i) {
|
||||||
|
let mention = Mention(index: i, type: .event, ref: ref)
|
||||||
|
mentions.append(.mention(mention))
|
||||||
|
} else {
|
||||||
|
ev_refs.append(ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
var replies = interp_event_refs_without_mentions(ev_refs)
|
||||||
|
replies.append(contentsOf: mentions)
|
||||||
|
return replies
|
||||||
|
}
|
||||||
|
|
||||||
|
func interpret_event_refs(blocks: [Block], tags: [[String]]) -> [EventRef] {
|
||||||
|
if tags.count == 0 {
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
/// build a set of indices for each event mention
|
||||||
|
let mention_indices = build_mention_indices(blocks, type: .event)
|
||||||
|
|
||||||
|
/// simpler case with no mentions
|
||||||
|
if mention_indices.count == 0 {
|
||||||
|
let ev_refs = get_referenced_ids(tags: tags, key: "e")
|
||||||
|
return interp_event_refs_without_mentions(ev_refs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func event_is_reply(_ ev: NostrEvent) -> Bool {
|
||||||
|
return ev.event_refs.contains { evref in
|
||||||
|
return evref.is_reply != nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
32
damus/Models/Reply.swift
Normal file
32
damus/Models/Reply.swift
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// Reply.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by William Casarin on 2022-05-08.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
struct ReplyDesc {
|
||||||
|
let pubkeys: [String]
|
||||||
|
let others: Int
|
||||||
|
}
|
||||||
|
|
||||||
|
func make_reply_description(_ tags: [[String]]) -> ReplyDesc {
|
||||||
|
var c = 0
|
||||||
|
var ns: [String] = []
|
||||||
|
var i = tags.count - 1
|
||||||
|
|
||||||
|
while i >= 0 {
|
||||||
|
let tag = tags[i]
|
||||||
|
if tag.count >= 2 && tag[0] == "p" {
|
||||||
|
c += 1
|
||||||
|
if ns.count < 2 {
|
||||||
|
ns.append(tag[1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
i -= 1
|
||||||
|
}
|
||||||
|
|
||||||
|
return ReplyDesc(pubkeys: ns, others: c)
|
||||||
|
}
|
||||||
@@ -128,25 +128,6 @@ class NostrEvent: Codable, Identifiable, CustomStringConvertible {
|
|||||||
return get_referenced_ids(key: "e")
|
return get_referenced_ids(key: "e")
|
||||||
}
|
}
|
||||||
|
|
||||||
public var reply_description: ([String], Int) {
|
|
||||||
var c = 0
|
|
||||||
var ns: [String] = []
|
|
||||||
var i = tags.count - 1
|
|
||||||
|
|
||||||
while i >= 0 {
|
|
||||||
let tag = tags[i]
|
|
||||||
if tag.count >= 2 && tag[0] == "p" {
|
|
||||||
c += 1
|
|
||||||
if ns.count < 2 {
|
|
||||||
ns.append(tag[1])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i -= 1
|
|
||||||
}
|
|
||||||
|
|
||||||
return (ns, c)
|
|
||||||
}
|
|
||||||
|
|
||||||
public func count_ids() -> Int {
|
public func count_ids() -> Int {
|
||||||
return count_refs("e")
|
return count_refs("e")
|
||||||
}
|
}
|
||||||
@@ -396,147 +377,3 @@ func gather_reply_ids(our_pubkey: String, from: NostrEvent) -> [ReferencedId] {
|
|||||||
}
|
}
|
||||||
return ids
|
return ids
|
||||||
}
|
}
|
||||||
|
|
||||||
enum EventRef {
|
|
||||||
case mention(Mention)
|
|
||||||
case thread_id(ReferencedId)
|
|
||||||
case reply(ReferencedId)
|
|
||||||
case reply_to_root(ReferencedId)
|
|
||||||
|
|
||||||
var is_mention: Mention? {
|
|
||||||
if case .mention(let m) = self {
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_direct_reply: ReferencedId? {
|
|
||||||
switch self {
|
|
||||||
case .mention:
|
|
||||||
return nil
|
|
||||||
case .thread_id:
|
|
||||||
return nil
|
|
||||||
case .reply(let refid):
|
|
||||||
return refid
|
|
||||||
case .reply_to_root(let refid):
|
|
||||||
return refid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_thread_id: ReferencedId? {
|
|
||||||
switch self {
|
|
||||||
case .mention(let mention):
|
|
||||||
return nil
|
|
||||||
case .thread_id(let referencedId):
|
|
||||||
return referencedId
|
|
||||||
case .reply(let referencedId):
|
|
||||||
return nil
|
|
||||||
case .reply_to_root(let referencedId):
|
|
||||||
return referencedId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var is_reply: ReferencedId? {
|
|
||||||
switch self {
|
|
||||||
case .mention:
|
|
||||||
return nil
|
|
||||||
case .thread_id:
|
|
||||||
return nil
|
|
||||||
case .reply(let refid):
|
|
||||||
return refid
|
|
||||||
case .reply_to_root(let refid):
|
|
||||||
return refid
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func has_any_e_refs(_ tags: [[String]]) -> Bool {
|
|
||||||
for tag in tags {
|
|
||||||
if tag.count >= 2 && tag[0] == "e" {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func build_mention_indices(_ blocks: [Block], type: MentionType) -> Set<Int> {
|
|
||||||
return blocks.reduce(into: []) { acc, block in
|
|
||||||
switch block {
|
|
||||||
case .mention(let m):
|
|
||||||
if m.type == type {
|
|
||||||
acc.insert(m.index)
|
|
||||||
}
|
|
||||||
case .text:
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func interp_event_refs_without_mentions(_ refs: [ReferencedId]) -> [EventRef] {
|
|
||||||
if refs.count == 0 {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
if refs.count == 1 {
|
|
||||||
return [.reply_to_root(refs[0])]
|
|
||||||
}
|
|
||||||
|
|
||||||
var evrefs: [EventRef] = []
|
|
||||||
var first: Bool = true
|
|
||||||
for ref in refs {
|
|
||||||
if first {
|
|
||||||
evrefs.append(.thread_id(ref))
|
|
||||||
first = false
|
|
||||||
} else {
|
|
||||||
evrefs.append(.reply(ref))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return evrefs
|
|
||||||
}
|
|
||||||
|
|
||||||
func interp_event_refs_with_mentions(tags: [[String]], mention_indices: Set<Int>) -> [EventRef] {
|
|
||||||
var mentions: [EventRef] = []
|
|
||||||
var ev_refs: [ReferencedId] = []
|
|
||||||
var i: Int = 0
|
|
||||||
|
|
||||||
for tag in tags {
|
|
||||||
if tag.count >= 2 && tag[0] == "e" {
|
|
||||||
let ref = tag_to_refid(tag)!
|
|
||||||
if mention_indices.contains(i) {
|
|
||||||
let mention = Mention(index: i, type: .event, ref: ref)
|
|
||||||
mentions.append(.mention(mention))
|
|
||||||
} else {
|
|
||||||
ev_refs.append(ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
i += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
var replies = interp_event_refs_without_mentions(ev_refs)
|
|
||||||
replies.append(contentsOf: mentions)
|
|
||||||
return replies
|
|
||||||
}
|
|
||||||
|
|
||||||
func interpret_event_refs(blocks: [Block], tags: [[String]]) -> [EventRef] {
|
|
||||||
if tags.count == 0 {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
/// build a set of indices for each event mention
|
|
||||||
let mention_indices = build_mention_indices(blocks, type: .event)
|
|
||||||
|
|
||||||
/// simpler case with no mentions
|
|
||||||
if mention_indices.count == 0 {
|
|
||||||
let ev_refs = get_referenced_ids(tags: tags, key: "e")
|
|
||||||
return interp_event_refs_without_mentions(ev_refs)
|
|
||||||
}
|
|
||||||
|
|
||||||
return interp_event_refs_with_mentions(tags: tags, mention_indices: mention_indices)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
func event_is_reply(_ ev: NostrEvent) -> Bool {
|
|
||||||
return ev.event_refs.contains { evref in
|
|
||||||
return evref.is_reply != nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -123,8 +123,11 @@ func format_relative_time(_ created_at: Int64) -> String
|
|||||||
}
|
}
|
||||||
|
|
||||||
func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
|
func reply_desc(profiles: Profiles, event: NostrEvent) -> String {
|
||||||
let (pubkeys, n) = event.reply_description
|
let desc = make_reply_description(event.tags)
|
||||||
if pubkeys.count == 0 {
|
let pubkeys = desc.pubkeys
|
||||||
|
let n = desc.others
|
||||||
|
|
||||||
|
if desc.pubkeys.count == 0 {
|
||||||
return "Reply to self"
|
return "Reply to self"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user