mirror of
git://jb55.com/damus
synced 2024-09-19 19:46:51 +00:00
Add saved drafts to posts, replies, and DMs
Changelog-Added: Save drafts to posts, replies and DMs Closes: #582
This commit is contained in:
parent
47a6f7ff38
commit
bc638f79f6
@ -15,6 +15,7 @@
|
|||||||
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */; };
|
3AA247FD297E3CFF0090C62D /* RepostsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FC297E3CFF0090C62D /* RepostsModel.swift */; };
|
||||||
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
3AA247FF297E3D900090C62D /* RepostsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA247FE297E3D900090C62D /* RepostsView.swift */; };
|
||||||
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
3AA24802297E3DC20090C62D /* RepostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA24801297E3DC20090C62D /* RepostView.swift */; };
|
||||||
|
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AA59D1C2999B0400061C48E /* DraftsModel.swift */; };
|
||||||
3AAA95CA298DF87B00F3D526 /* TranslationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */; };
|
3AAA95CA298DF87B00F3D526 /* TranslationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95C9298DF87B00F3D526 /* TranslationService.swift */; };
|
||||||
3AAA95CC298E07E900F3D526 /* DeepLPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */; };
|
3AAA95CC298E07E900F3D526 /* DeepLPlan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */; };
|
||||||
3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB72AB8298ECF30004BB58C /* Translator.swift */; };
|
3AB72AB9298ECF30004BB58C /* Translator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3AB72AB8298ECF30004BB58C /* Translator.swift */; };
|
||||||
@ -270,6 +271,7 @@
|
|||||||
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepostsModel.swift; sourceTree = "<group>"; };
|
3AA247FC297E3CFF0090C62D /* RepostsModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RepostsModel.swift; sourceTree = "<group>"; };
|
||||||
3AA247FE297E3D900090C62D /* RepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostsView.swift; sourceTree = "<group>"; };
|
3AA247FE297E3D900090C62D /* RepostsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostsView.swift; sourceTree = "<group>"; };
|
||||||
3AA24801297E3DC20090C62D /* RepostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostView.swift; sourceTree = "<group>"; };
|
3AA24801297E3DC20090C62D /* RepostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RepostView.swift; sourceTree = "<group>"; };
|
||||||
|
3AA59D1C2999B0400061C48E /* DraftsModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DraftsModel.swift; sourceTree = "<group>"; };
|
||||||
3AAA95C9298DF87B00F3D526 /* TranslationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationService.swift; sourceTree = "<group>"; };
|
3AAA95C9298DF87B00F3D526 /* TranslationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TranslationService.swift; sourceTree = "<group>"; };
|
||||||
3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLPlan.swift; sourceTree = "<group>"; };
|
3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLPlan.swift; sourceTree = "<group>"; };
|
||||||
3AB5B86A2986D8A3006599D2 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
3AB5B86A2986D8A3006599D2 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/InfoPlist.strings; sourceTree = "<group>"; };
|
||||||
@ -660,6 +662,7 @@
|
|||||||
3AAA95C9298DF87B00F3D526 /* TranslationService.swift */,
|
3AAA95C9298DF87B00F3D526 /* TranslationService.swift */,
|
||||||
3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */,
|
3AAA95CB298E07E900F3D526 /* DeepLPlan.swift */,
|
||||||
4CE8795A2996C47A00F758CC /* ZapsModel.swift */,
|
4CE8795A2996C47A00F758CC /* ZapsModel.swift */,
|
||||||
|
3AA59D1C2999B0400061C48E /* DraftsModel.swift */,
|
||||||
);
|
);
|
||||||
path = Models;
|
path = Models;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@ -1352,6 +1355,7 @@
|
|||||||
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
|
4CC7AAFA297F64AC00430951 /* EventMenu.swift in Sources */,
|
||||||
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */,
|
4C75EFBB2804A34C0006080F /* ProofOfWork.swift in Sources */,
|
||||||
4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */,
|
4C3AC7A52836987600E1F516 /* MainTabView.swift in Sources */,
|
||||||
|
3AA59D1D2999B0400061C48E /* DraftsModel.swift in Sources */,
|
||||||
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */,
|
3169CAED294FCCFC00EE4006 /* Constants.swift in Sources */,
|
||||||
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
|
4CB9D4A72992D02B00A9A7E4 /* ProfileNameView.swift in Sources */,
|
||||||
);
|
);
|
||||||
|
@ -606,19 +606,21 @@ struct ContentView: View {
|
|||||||
|
|
||||||
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
pool.register_handler(sub_id: sub_id, handler: home.handle_event)
|
||||||
|
|
||||||
self.damus_state = DamusState(pool: pool, keypair: keypair,
|
self.damus_state = DamusState(pool: pool,
|
||||||
likes: EventCounter(our_pubkey: pubkey),
|
keypair: keypair,
|
||||||
boosts: EventCounter(our_pubkey: pubkey),
|
likes: EventCounter(our_pubkey: pubkey),
|
||||||
contacts: Contacts(our_pubkey: pubkey),
|
boosts: EventCounter(our_pubkey: pubkey),
|
||||||
tips: TipCounter(our_pubkey: pubkey),
|
contacts: Contacts(our_pubkey: pubkey),
|
||||||
profiles: Profiles(),
|
tips: TipCounter(our_pubkey: pubkey),
|
||||||
dms: home.dms,
|
profiles: Profiles(),
|
||||||
previews: PreviewCache(),
|
dms: home.dms,
|
||||||
zaps: Zaps(our_pubkey: pubkey),
|
previews: PreviewCache(),
|
||||||
lnurls: LNUrls(),
|
zaps: Zaps(our_pubkey: pubkey),
|
||||||
settings: UserSettingsStore(),
|
lnurls: LNUrls(),
|
||||||
relay_filters: relay_filters,
|
settings: UserSettingsStore(),
|
||||||
relay_metadata: metadatas
|
relay_filters: relay_filters,
|
||||||
|
relay_metadata: metadatas,
|
||||||
|
drafts_model: home.drafts_model
|
||||||
)
|
)
|
||||||
home.damus_state = self.damus_state!
|
home.damus_state = self.damus_state!
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ struct DamusState {
|
|||||||
let settings: UserSettingsStore
|
let settings: UserSettingsStore
|
||||||
let relay_filters: RelayFilters
|
let relay_filters: RelayFilters
|
||||||
let relay_metadata: RelayMetadatas
|
let relay_metadata: RelayMetadatas
|
||||||
|
let drafts_model: DraftsModel
|
||||||
|
|
||||||
var pubkey: String {
|
var pubkey: String {
|
||||||
return keypair.pubkey
|
return keypair.pubkey
|
||||||
@ -34,6 +35,6 @@ struct DamusState {
|
|||||||
|
|
||||||
|
|
||||||
static var empty: DamusState {
|
static var empty: DamusState {
|
||||||
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas())
|
return DamusState.init(pool: RelayPool(), keypair: Keypair(pubkey: "", privkey: ""), likes: EventCounter(our_pubkey: ""), boosts: EventCounter(our_pubkey: ""), contacts: Contacts(our_pubkey: ""), tips: TipCounter(our_pubkey: ""), profiles: Profiles(), dms: DirectMessagesModel(our_pubkey: ""), previews: PreviewCache(), zaps: Zaps(our_pubkey: ""), lnurls: LNUrls(), settings: UserSettingsStore(), relay_filters: RelayFilters(our_pubkey: ""), relay_metadata: RelayMetadatas(), drafts_model: DraftsModel())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,8 @@ class DirectMessageModel: ObservableObject {
|
|||||||
is_request = determine_is_request()
|
is_request = determine_is_request()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Published var draft: String
|
||||||
|
|
||||||
var is_request: Bool
|
var is_request: Bool
|
||||||
var our_pubkey: String
|
var our_pubkey: String
|
||||||
@ -31,11 +33,13 @@ class DirectMessageModel: ObservableObject {
|
|||||||
self.events = events
|
self.events = events
|
||||||
self.is_request = false
|
self.is_request = false
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
|
self.draft = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
init(our_pubkey: String) {
|
init(our_pubkey: String) {
|
||||||
self.events = []
|
self.events = []
|
||||||
self.is_request = false
|
self.is_request = false
|
||||||
self.our_pubkey = our_pubkey
|
self.our_pubkey = our_pubkey
|
||||||
|
self.draft = ""
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
damus/Models/DraftsModel.swift
Normal file
13
damus/Models/DraftsModel.swift
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// DraftsModel.swift
|
||||||
|
// damus
|
||||||
|
//
|
||||||
|
// Created by Terry Yiu on 2/12/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class DraftsModel: ObservableObject {
|
||||||
|
@Published var post: String = ""
|
||||||
|
@Published var replies = Dictionary<NostrEvent, String>()
|
||||||
|
}
|
@ -52,15 +52,18 @@ class HomeModel: ObservableObject {
|
|||||||
@Published var events: [NostrEvent] = []
|
@Published var events: [NostrEvent] = []
|
||||||
@Published var loading: Bool = false
|
@Published var loading: Bool = false
|
||||||
@Published var signal: SignalModel = SignalModel()
|
@Published var signal: SignalModel = SignalModel()
|
||||||
|
@Published var drafts_model: DraftsModel
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
self.damus_state = DamusState.empty
|
self.damus_state = DamusState.empty
|
||||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
||||||
|
self.drafts_model = DraftsModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(damus_state: DamusState) {
|
init(damus_state: DamusState) {
|
||||||
self.damus_state = damus_state
|
self.damus_state = damus_state
|
||||||
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
self.dms = DirectMessagesModel(our_pubkey: damus_state.pubkey)
|
||||||
|
self.drafts_model = DraftsModel()
|
||||||
}
|
}
|
||||||
|
|
||||||
var pool: RelayPool {
|
var pool: RelayPool {
|
||||||
|
@ -11,7 +11,6 @@ struct DMChatView: View {
|
|||||||
let damus_state: DamusState
|
let damus_state: DamusState
|
||||||
let pubkey: String
|
let pubkey: String
|
||||||
@EnvironmentObject var dms: DirectMessageModel
|
@EnvironmentObject var dms: DirectMessageModel
|
||||||
@State var message: String = ""
|
|
||||||
@State var showPrivateKeyWarning: Bool = false
|
@State var showPrivateKeyWarning: Bool = false
|
||||||
|
|
||||||
var Messages: some View {
|
var Messages: some View {
|
||||||
@ -52,7 +51,7 @@ struct DMChatView: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var InputField: some View {
|
var InputField: some View {
|
||||||
TextEditor(text: $message)
|
TextEditor(text: $dms.draft)
|
||||||
.textEditorBackground {
|
.textEditorBackground {
|
||||||
InputBackground()
|
InputBackground()
|
||||||
}
|
}
|
||||||
@ -93,11 +92,11 @@ struct DMChatView: View {
|
|||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
InputField
|
InputField
|
||||||
|
|
||||||
if !message.isEmpty {
|
if !dms.draft.isEmpty {
|
||||||
Button(
|
Button(
|
||||||
role: .none,
|
role: .none,
|
||||||
action: {
|
action: {
|
||||||
showPrivateKeyWarning = contentContainsPrivateKey(message)
|
showPrivateKeyWarning = contentContainsPrivateKey(dms.draft)
|
||||||
|
|
||||||
if !showPrivateKeyWarning {
|
if !showPrivateKeyWarning {
|
||||||
send_message()
|
send_message()
|
||||||
@ -112,7 +111,7 @@ struct DMChatView: View {
|
|||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.frame(minHeight: 70, maxHeight: 150, alignment: .bottom)
|
.frame(minHeight: 70, maxHeight: 150, alignment: .bottom)
|
||||||
|
|
||||||
Text(message).opacity(0).padding(.all, 8)
|
Text(dms.draft).opacity(0).padding(.all, 8)
|
||||||
.fixedSize(horizontal: false, vertical: true)
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
.frame(minHeight: 70, maxHeight: 150, alignment: .bottom)
|
.frame(minHeight: 70, maxHeight: 150, alignment: .bottom)
|
||||||
}
|
}
|
||||||
@ -122,7 +121,7 @@ struct DMChatView: View {
|
|||||||
|
|
||||||
func send_message() {
|
func send_message() {
|
||||||
let tags = [["p", pubkey]]
|
let tags = [["p", pubkey]]
|
||||||
let post_blocks = parse_post_blocks(content: message)
|
let post_blocks = parse_post_blocks(content: dms.draft)
|
||||||
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
|
let post_tags = make_post_tags(post_blocks: post_blocks, tags: tags)
|
||||||
let content = render_blocks(blocks: post_tags.blocks)
|
let content = render_blocks(blocks: post_tags.blocks)
|
||||||
|
|
||||||
@ -131,7 +130,7 @@ struct DMChatView: View {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
message = ""
|
dms.draft = ""
|
||||||
|
|
||||||
damus_state.pool.send(.event(dm))
|
damus_state.pool.send(.event(dm))
|
||||||
end_editing()
|
end_editing()
|
||||||
@ -157,6 +156,11 @@ struct DMChatView: View {
|
|||||||
}
|
}
|
||||||
.navigationTitle(NSLocalizedString("DMs", comment: "Navigation title for DMs view, where DM is the English abbreviation for Direct Message."))
|
.navigationTitle(NSLocalizedString("DMs", comment: "Navigation title for DMs view, where DM is the English abbreviation for Direct Message."))
|
||||||
.toolbar { Header }
|
.toolbar { Header }
|
||||||
|
.onDisappear {
|
||||||
|
if dms.draft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
dms.draft = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
|
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
|
||||||
Button(NSLocalizedString("No", comment: "Button to cancel out of posting a note after being alerted that it looks like they might be posting a private key."), role: .cancel) {
|
Button(NSLocalizedString("No", comment: "Button to cancel out of posting a note after being alerted that it looks like they might be posting a private key."), role: .cancel) {
|
||||||
showPrivateKeyWarning = false
|
showPrivateKeyWarning = false
|
||||||
|
@ -16,6 +16,7 @@ let POST_PLACEHOLDER = NSLocalizedString("Type your post here...", comment: "Tex
|
|||||||
|
|
||||||
struct PostView: View {
|
struct PostView: View {
|
||||||
@State var post: String = ""
|
@State var post: String = ""
|
||||||
|
|
||||||
@FocusState var focus: Bool
|
@FocusState var focus: Bool
|
||||||
@State var showPrivateKeyWarning: Bool = false
|
@State var showPrivateKeyWarning: Bool = false
|
||||||
|
|
||||||
@ -47,6 +48,13 @@ struct PostView: View {
|
|||||||
let new_post = NostrPost(content: content, references: references, kind: kind)
|
let new_post = NostrPost(content: content, references: references, kind: kind)
|
||||||
|
|
||||||
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
NotificationCenter.default.post(name: .post, object: NostrPostResult.post(new_post))
|
||||||
|
|
||||||
|
if replying_to == nil {
|
||||||
|
damus_state.drafts_model.post = ""
|
||||||
|
} else {
|
||||||
|
damus_state.drafts_model.replies.removeValue(forKey: replying_to!)
|
||||||
|
}
|
||||||
|
|
||||||
dismiss()
|
dismiss()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +88,13 @@ struct PostView: View {
|
|||||||
TextEditor(text: $post)
|
TextEditor(text: $post)
|
||||||
.focused($focus)
|
.focused($focus)
|
||||||
.textInputAutocapitalization(.sentences)
|
.textInputAutocapitalization(.sentences)
|
||||||
|
.onChange(of: post) { _ in
|
||||||
|
if replying_to == nil {
|
||||||
|
damus_state.drafts_model.post = post
|
||||||
|
} else {
|
||||||
|
damus_state.drafts_model.replies[replying_to!] = post
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if post.isEmpty {
|
if post.isEmpty {
|
||||||
Text(POST_PLACEHOLDER)
|
Text(POST_PLACEHOLDER)
|
||||||
@ -99,10 +114,26 @@ struct PostView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.onAppear() {
|
.onAppear() {
|
||||||
|
if replying_to == nil {
|
||||||
|
post = damus_state.drafts_model.post
|
||||||
|
} else {
|
||||||
|
if damus_state.drafts_model.replies[replying_to!] == nil {
|
||||||
|
damus_state.drafts_model.replies[replying_to!] = ""
|
||||||
|
}
|
||||||
|
post = damus_state.drafts_model.replies[replying_to!]!
|
||||||
|
}
|
||||||
|
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
self.focus = true
|
self.focus = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.onDisappear {
|
||||||
|
if replying_to == nil && damus_state.drafts_model.post.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
|
||||||
|
damus_state.drafts_model.post = ""
|
||||||
|
} else if replying_to != nil && damus_state.drafts_model.replies[replying_to!]?.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty == true {
|
||||||
|
damus_state.drafts_model.replies.removeValue(forKey: replying_to!)
|
||||||
|
}
|
||||||
|
}
|
||||||
.padding()
|
.padding()
|
||||||
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
|
.alert(NSLocalizedString("Note contains \"nsec1\" private key. Are you sure?", comment: "Alert user that they might be attempting to paste a private key and ask them to confirm."), isPresented: $showPrivateKeyWarning, actions: {
|
||||||
Button(NSLocalizedString("No", comment: "Button to cancel out of posting a note after being alerted that it looks like they might be posting a private key."), role: .cancel) {
|
Button(NSLocalizedString("No", comment: "Button to cancel out of posting a note after being alerted that it looks like they might be posting a private key."), role: .cancel) {
|
||||||
|
Loading…
Reference in New Issue
Block a user