mirror of
https://github.com/Qortal/qortal-ui.git
synced 2025-04-23 19:37:52 +00:00
Merge branch 'feature/implement-logic-edit-reply-messages' of https://github.com/PhillipLangMartinez/qortal-ui into feature/implement-UI-edit-reply-messages
This commit is contained in:
commit
5c292292ea
@ -485,7 +485,7 @@
|
|||||||
"cchange23": "Cannot Decrypt Message!",
|
"cchange23": "Cannot Decrypt Message!",
|
||||||
"cchange24": "Maximum Characters per message is 255",
|
"cchange24": "Maximum Characters per message is 255",
|
||||||
"cchange25": "Edit Message",
|
"cchange25": "Edit Message",
|
||||||
"cchange26": "File size exceeds 5 MB",
|
"cchange26": "File size exceeds 0.5 MB",
|
||||||
"cchange27": "A registered name is required to send images",
|
"cchange27": "A registered name is required to send images",
|
||||||
"cchange28": "This file is not an image",
|
"cchange28": "This file is not an image",
|
||||||
"cchange29": "Maximum message size is 1000 bytes",
|
"cchange29": "Maximum message size is 1000 bytes",
|
||||||
|
@ -24,6 +24,7 @@ import { replaceMessagesEdited } from '../../utils/replace-messages-edited.js';
|
|||||||
import { publishData } from '../../utils/publish-image.js';
|
import { publishData } from '../../utils/publish-image.js';
|
||||||
import WebWorker from 'web-worker:./computePowWorker.js';
|
import WebWorker from 'web-worker:./computePowWorker.js';
|
||||||
import WebWorkerImage from 'web-worker:./computePowWorkerImage.js';
|
import WebWorkerImage from 'web-worker:./computePowWorkerImage.js';
|
||||||
|
import { EmojiPicker } from 'emoji-picker-js';
|
||||||
|
|
||||||
|
|
||||||
// const messagesCache = localForage.createInstance({
|
// const messagesCache = localForage.createInstance({
|
||||||
@ -66,6 +67,7 @@ class ChatPage extends LitElement {
|
|||||||
chatEditorNewChat: { type: Object },
|
chatEditorNewChat: { type: Object },
|
||||||
userLanguage: { type: String },
|
userLanguage: { type: String },
|
||||||
lastMessageRefVisible: { type: Boolean },
|
lastMessageRefVisible: { type: Boolean },
|
||||||
|
isLoadingOldMessages: {type: Boolean}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -550,6 +552,15 @@ class ChatPage extends LitElement {
|
|||||||
this.uid = new ShortUniqueId()
|
this.uid = new ShortUniqueId()
|
||||||
this.userLanguage = ""
|
this.userLanguage = ""
|
||||||
this.lastMessageRefVisible = false
|
this.lastMessageRefVisible = false
|
||||||
|
this.emojiPicker = new EmojiPicker({
|
||||||
|
style: "twemoji",
|
||||||
|
twemojiBaseUrl: '/emoji/',
|
||||||
|
showPreview: false,
|
||||||
|
showVariants: false,
|
||||||
|
showAnimation: false,
|
||||||
|
position: 'top-start',
|
||||||
|
boxShadow: 'rgba(4, 4, 5, 0.15) 0px 0px 0px 1px, rgba(0, 0, 0, 0.24) 0px 8px 16px 0px'
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -853,11 +864,16 @@ class ChatPage extends LitElement {
|
|||||||
.focusChatEditor=${() => this.focusChatEditor()}
|
.focusChatEditor=${() => this.focusChatEditor()}
|
||||||
.sendMessage=${(val) => this._sendMessage(val)}
|
.sendMessage=${(val) => this._sendMessage(val)}
|
||||||
.showLastMessageRefScroller=${(val) => this.showLastMessageRefScroller(val)}
|
.showLastMessageRefScroller=${(val) => this.showLastMessageRefScroller(val)}
|
||||||
|
.emojiPicker=${this.emojiPicker}
|
||||||
|
?isLoadingMessages=${this.isLoadingOldMessages}
|
||||||
|
.setIsLoadingMessages=${(val) => this.setIsLoadingMessages(val)}
|
||||||
>
|
>
|
||||||
</chat-scroller>
|
</chat-scroller>
|
||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
setIsLoadingMessages(val){
|
||||||
|
this.isLoadingOldMessages = val
|
||||||
|
}
|
||||||
async getUpdateComplete() {
|
async getUpdateComplete() {
|
||||||
await super.getUpdateComplete();
|
await super.getUpdateComplete();
|
||||||
const marginElements = Array.from(this.shadowRoot.querySelectorAll('chat-scroller'));
|
const marginElements = Array.from(this.shadowRoot.querySelectorAll('chat-scroller'));
|
||||||
@ -889,9 +905,16 @@ class ChatPage extends LitElement {
|
|||||||
return a.timestamp
|
return a.timestamp
|
||||||
- b.timestamp
|
- b.timestamp
|
||||||
})
|
})
|
||||||
|
this.isLoadingOldMessages = false
|
||||||
await this.getUpdateComplete();
|
await this.getUpdateComplete();
|
||||||
|
const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template'));
|
||||||
|
|
||||||
|
const findElement = marginElements.find((item)=> item.messageObj.reference === scrollElement.messageObj.reference)
|
||||||
|
|
||||||
|
if(findElement){
|
||||||
|
findElement.scrollIntoView({ behavior: 'auto', block: 'center' });
|
||||||
|
}
|
||||||
|
|
||||||
scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
const getInitialMessages = await parentEpml.request('apiCall', {
|
const getInitialMessages = await parentEpml.request('apiCall', {
|
||||||
@ -916,9 +939,15 @@ class ChatPage extends LitElement {
|
|||||||
return a.timestamp
|
return a.timestamp
|
||||||
- b.timestamp
|
- b.timestamp
|
||||||
})
|
})
|
||||||
|
this.isLoadingOldMessages = false
|
||||||
await this.getUpdateComplete();
|
await this.getUpdateComplete();
|
||||||
|
const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template'));
|
||||||
|
const findElement = marginElements.find((item)=> item.messageObj.reference === scrollElement.messageObj.reference)
|
||||||
|
|
||||||
|
if(findElement){
|
||||||
|
findElement.scrollIntoView({ behavior: 'auto', block: 'center' });
|
||||||
|
}
|
||||||
|
|
||||||
scrollElement.scrollIntoView({ behavior: 'auto', block: 'center' });
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1359,6 +1388,7 @@ class ChatPage extends LitElement {
|
|||||||
const file = new File([result], "name", {
|
const file = new File([result], "name", {
|
||||||
type: 'image/png'
|
type: 'image/png'
|
||||||
});
|
});
|
||||||
|
|
||||||
compressedFile = file;
|
compressedFile = file;
|
||||||
resolve();
|
resolve();
|
||||||
},
|
},
|
||||||
@ -1371,7 +1401,7 @@ class ChatPage extends LitElement {
|
|||||||
await publishData({
|
await publishData({
|
||||||
registeredName: userName,
|
registeredName: userName,
|
||||||
file : compressedFile,
|
file : compressedFile,
|
||||||
service: 'IMAGE',
|
service: 'QCHAT_IMAGE',
|
||||||
identifier: identifier,
|
identifier: identifier,
|
||||||
parentEpml,
|
parentEpml,
|
||||||
metaData: undefined,
|
metaData: undefined,
|
||||||
@ -1442,7 +1472,7 @@ class ChatPage extends LitElement {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
const fileSize = compressedFile.size;
|
const fileSize = compressedFile.size;
|
||||||
if (fileSize > 5000000) {
|
if (fileSize > 500000) {
|
||||||
parentEpml.request('showSnackBar', get("chatpage.cchange26"));
|
parentEpml.request('showSnackBar', get("chatpage.cchange26"));
|
||||||
this.isLoading = false;
|
this.isLoading = false;
|
||||||
this.chatEditor.enable();
|
this.chatEditor.enable();
|
||||||
@ -1454,7 +1484,7 @@ class ChatPage extends LitElement {
|
|||||||
await publishData({
|
await publishData({
|
||||||
registeredName: userName,
|
registeredName: userName,
|
||||||
file : compressedFile,
|
file : compressedFile,
|
||||||
service: 'IMAGE',
|
service: 'QCHAT_IMAGE',
|
||||||
identifier : identifier,
|
identifier : identifier,
|
||||||
parentEpml,
|
parentEpml,
|
||||||
metaData: undefined,
|
metaData: undefined,
|
||||||
@ -1478,7 +1508,7 @@ class ChatPage extends LitElement {
|
|||||||
const messageObject = {
|
const messageObject = {
|
||||||
messageText: trimmedMessageWithImage,
|
messageText: trimmedMessageWithImage,
|
||||||
images: [{
|
images: [{
|
||||||
service: "IMAGE",
|
service: "QCHAT_IMAGE",
|
||||||
name: userName,
|
name: userName,
|
||||||
identifier: identifier
|
identifier: identifier
|
||||||
}],
|
}],
|
||||||
|
@ -435,4 +435,9 @@ export const chatStyles = css`
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
}
|
}
|
||||||
|
.spinnerContainer {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
justify-content: center
|
||||||
|
}
|
||||||
`
|
`
|
||||||
|
@ -17,6 +17,7 @@ import { EmojiPicker } from 'emoji-picker-js';
|
|||||||
import { cropAddress } from "../../utils/cropAddress";
|
import { cropAddress } from "../../utils/cropAddress";
|
||||||
|
|
||||||
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
|
||||||
|
let toggledMessage = {}
|
||||||
class ChatScroller extends LitElement {
|
class ChatScroller extends LitElement {
|
||||||
static get properties() {
|
static get properties() {
|
||||||
return {
|
return {
|
||||||
@ -30,6 +31,9 @@ class ChatScroller extends LitElement {
|
|||||||
focusChatEditor: {attribute: false},
|
focusChatEditor: {attribute: false},
|
||||||
sendMessage: {attribute: false},
|
sendMessage: {attribute: false},
|
||||||
showLastMessageRefScroller: { type: Function },
|
showLastMessageRefScroller: { type: Function },
|
||||||
|
emojiPicker: { attribute: false },
|
||||||
|
isLoadingMessages: { type: Boolean},
|
||||||
|
setIsLoadingMessages: {attribute: false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,15 +46,6 @@ class ChatScroller extends LitElement {
|
|||||||
this._downObserverHandler = this._downObserverHandler.bind(this)
|
this._downObserverHandler = this._downObserverHandler.bind(this)
|
||||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
||||||
this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]")
|
this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]")
|
||||||
this.emojiPicker = new EmojiPicker({
|
|
||||||
style: "twemoji",
|
|
||||||
twemojiBaseUrl: '/emoji/',
|
|
||||||
showPreview: false,
|
|
||||||
showVariants: false,
|
|
||||||
showAnimation: false,
|
|
||||||
position: 'top-start',
|
|
||||||
boxShadow: 'rgba(4, 4, 5, 0.15) 0px 0px 0px 1px, rgba(0, 0, 0, 0.24) 0px 8px 16px 0px'
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -78,8 +73,12 @@ class ChatScroller extends LitElement {
|
|||||||
return messageArray;
|
return messageArray;
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
|
${this.isLoadingMessages ? html`
|
||||||
|
<div class="spinnerContainer">
|
||||||
|
<paper-spinner-lite active></paper-spinner-lite>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
<ul id="viewElement" class="chat-list clearfix">
|
<ul id="viewElement" class="chat-list clearfix">
|
||||||
<div id="upObserver"></div>
|
<div id="upObserver"></div>
|
||||||
${formattedMessages.map((formattedMessage) => {
|
${formattedMessages.map((formattedMessage) => {
|
||||||
@ -99,6 +98,7 @@ class ChatScroller extends LitElement {
|
|||||||
?isFirstMessage=${indexMessage === 0}
|
?isFirstMessage=${indexMessage === 0}
|
||||||
?isSingleMessageInGroup=${formattedMessage.messages.length > 1}
|
?isSingleMessageInGroup=${formattedMessage.messages.length > 1}
|
||||||
?isLastMessageInGroup=${indexMessage === formattedMessage.messages.length - 1}
|
?isLastMessageInGroup=${indexMessage === formattedMessage.messages.length - 1}
|
||||||
|
.setToggledMessage=${this.setToggledMessage}
|
||||||
>
|
>
|
||||||
</message-template>`
|
</message-template>`
|
||||||
)
|
)
|
||||||
@ -109,6 +109,9 @@ class ChatScroller extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
shouldUpdate(changedProperties) {
|
shouldUpdate(changedProperties) {
|
||||||
|
if(changedProperties.has('isLoadingMessages')){
|
||||||
|
return true
|
||||||
|
}
|
||||||
// Only update element if prop1 changed.
|
// Only update element if prop1 changed.
|
||||||
return changedProperties.has('messages');
|
return changedProperties.has('messages');
|
||||||
}
|
}
|
||||||
@ -120,7 +123,22 @@ class ChatScroller extends LitElement {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setToggledMessage(message){
|
||||||
|
toggledMessage = message
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
async firstUpdated() {
|
async firstUpdated() {
|
||||||
|
this.emojiPicker.on('emoji', selection => {
|
||||||
|
|
||||||
|
this.sendMessage({
|
||||||
|
type: 'reaction',
|
||||||
|
editedMessageObj: toggledMessage,
|
||||||
|
reaction: selection.emoji,
|
||||||
|
|
||||||
|
|
||||||
|
})
|
||||||
|
});
|
||||||
this.viewElement = this.shadowRoot.getElementById('viewElement');
|
this.viewElement = this.shadowRoot.getElementById('viewElement');
|
||||||
this.upObserverElement = this.shadowRoot.getElementById('upObserver');
|
this.upObserverElement = this.shadowRoot.getElementById('upObserver');
|
||||||
this.downObserverElement = this.shadowRoot.getElementById('downObserver');
|
this.downObserverElement = this.shadowRoot.getElementById('downObserver');
|
||||||
@ -137,6 +155,7 @@ class ChatScroller extends LitElement {
|
|||||||
|
|
||||||
_upObserverhandler(entries) {
|
_upObserverhandler(entries) {
|
||||||
if (entries[0].isIntersecting) {
|
if (entries[0].isIntersecting) {
|
||||||
|
this.setIsLoadingMessages(true);
|
||||||
let _scrollElement = entries[0].target.nextElementSibling;
|
let _scrollElement = entries[0].target.nextElementSibling;
|
||||||
this._getOldMessage(_scrollElement);
|
this._getOldMessage(_scrollElement);
|
||||||
}
|
}
|
||||||
@ -198,6 +217,7 @@ class MessageTemplate extends LitElement {
|
|||||||
isFirstMessage: { type: Boolean },
|
isFirstMessage: { type: Boolean },
|
||||||
isSingleMessageInGroup: { type: Boolean },
|
isSingleMessageInGroup: { type: Boolean },
|
||||||
isLastMessageInGroup: { type: Boolean },
|
isLastMessageInGroup: { type: Boolean },
|
||||||
|
setToggledMessage: {attribute: false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -237,7 +257,13 @@ class MessageTemplate extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
showBlockIconFunc(bool) {
|
showBlockIconFunc(bool) {
|
||||||
this.shadowRoot.querySelector(".chat-hover").focus({ preventScroll: true })
|
const chatHover = this.shadowRoot.querySelector(".chat-hover")
|
||||||
|
|
||||||
|
if(chatHover){
|
||||||
|
chatHover.querySelector(".chat-hover").focus({ preventScroll: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if(bool) {
|
if(bool) {
|
||||||
this.showBlockAddressIcon = true;
|
this.showBlockAddressIcon = true;
|
||||||
} else {
|
} else {
|
||||||
@ -454,6 +480,8 @@ class MessageTemplate extends LitElement {
|
|||||||
@blur=${() => this.showBlockIconFunc(false)}
|
@blur=${() => this.showBlockIconFunc(false)}
|
||||||
.sendMessage=${this.sendMessage}
|
.sendMessage=${this.sendMessage}
|
||||||
version=${version}
|
version=${version}
|
||||||
|
.emojiPicker=${this.emojiPicker}
|
||||||
|
.setToggledMessage=${this.setToggledMessage}
|
||||||
>
|
>
|
||||||
</chat-menu>
|
</chat-menu>
|
||||||
</div>
|
</div>
|
||||||
@ -529,7 +557,8 @@ class ChatMenu extends LitElement {
|
|||||||
myAddress: { type: Object },
|
myAddress: { type: Object },
|
||||||
emojiPicker: { attribute: false },
|
emojiPicker: { attribute: false },
|
||||||
sendMessage: {attribute: false},
|
sendMessage: {attribute: false},
|
||||||
version: {type: String}
|
version: {type: String},
|
||||||
|
setToggledMessage: {attribute: false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -559,26 +588,6 @@ class ChatMenu extends LitElement {
|
|||||||
parentEpml.request('showSnackBar', `${errorMsg}`)
|
parentEpml.request('showSnackBar', `${errorMsg}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
firstUpdated () {
|
|
||||||
this.emojiPicker = new EmojiPicker({
|
|
||||||
style: "twemoji",
|
|
||||||
twemojiBaseUrl: '/emoji/',
|
|
||||||
showPreview: false,
|
|
||||||
showVariants: false,
|
|
||||||
showAnimation: false,
|
|
||||||
position: 'top-start',
|
|
||||||
boxShadow: 'rgba(4, 4, 5, 0.15) 0px 0px 0px 1px, rgba(0, 0, 0, 0.24) 0px 8px 16px 0px'
|
|
||||||
});
|
|
||||||
|
|
||||||
this.emojiPicker.on('emoji', selection => {
|
|
||||||
this.sendMessage({
|
|
||||||
type: 'reaction',
|
|
||||||
editedMessageObj: this.originalMessage,
|
|
||||||
reaction: selection.emoji,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
return html`
|
return html`
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -590,7 +599,13 @@ class ChatMenu extends LitElement {
|
|||||||
this.versionErrorSnack()
|
this.versionErrorSnack()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.emojiPicker.togglePicker(e.target)
|
try {
|
||||||
|
this.setToggledMessage(this.originalMessage)
|
||||||
|
this.emojiPicker.togglePicker(e.target)
|
||||||
|
} catch (error) {
|
||||||
|
console.log({error})
|
||||||
|
}
|
||||||
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<vaadin-icon icon="vaadin:smiley-o" slot="icon"></vaadin-icon>
|
<vaadin-icon icon="vaadin:smiley-o" slot="icon"></vaadin-icon>
|
||||||
|
@ -266,7 +266,6 @@ class ChatTextEditor extends LitElement {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async firstUpdated() {
|
async firstUpdated() {
|
||||||
console.log(this.placeholder, "here500");
|
|
||||||
if (this.hasGlobalEvents) {
|
if (this.hasGlobalEvents) {
|
||||||
this.addGlobalEventListener();
|
this.addGlobalEventListener();
|
||||||
}
|
}
|
||||||
|
@ -17,13 +17,9 @@ export const replaceMessagesEdited = async ({
|
|||||||
url: `/chat/messages?chatreference=${msg.reference}&reverse=true${msgQuery}`,
|
url: `/chat/messages?chatreference=${msg.reference}&reverse=true${msgQuery}`,
|
||||||
})
|
})
|
||||||
|
|
||||||
console.log({response})
|
|
||||||
|
|
||||||
if (response && Array.isArray(response) && response.length !== 0) {
|
if (response && Array.isArray(response) && response.length !== 0) {
|
||||||
let responseItem = { ...response[0] }
|
let responseItem = { ...response[0] }
|
||||||
console.log('right before')
|
|
||||||
const decodeResponseItem = decodeMessageFunc(responseItem, isReceipient, _publicKey)
|
const decodeResponseItem = decodeMessageFunc(responseItem, isReceipient, _publicKey)
|
||||||
console.log({decodeResponseItem})
|
|
||||||
delete decodeResponseItem.timestamp
|
delete decodeResponseItem.timestamp
|
||||||
msgItem = {
|
msgItem = {
|
||||||
...msg,
|
...msg,
|
||||||
@ -43,24 +39,20 @@ export const replaceMessagesEdited = async ({
|
|||||||
try {
|
try {
|
||||||
parsedMessageObj = JSON.parse(msg.decodedMessage)
|
parsedMessageObj = JSON.parse(msg.decodedMessage)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('error', {parsedMessageObj})
|
console.log('error')
|
||||||
return msg
|
return msg
|
||||||
}
|
}
|
||||||
console.log('noerror')
|
|
||||||
let msgItem = msg
|
let msgItem = msg
|
||||||
try {
|
try {
|
||||||
let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}`
|
let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}`
|
||||||
if (!isReceipient) {
|
if (!isReceipient) {
|
||||||
msgQuery = `&txGroupId=${msg.txGroupId}`
|
msgQuery = `&txGroupId=${msg.txGroupId}`
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log({parsedMessageObj})
|
|
||||||
if (parsedMessageObj.repliedTo) {
|
if (parsedMessageObj.repliedTo) {
|
||||||
const response = await parentEpml.request("apiCall", {
|
const response = await parentEpml.request("apiCall", {
|
||||||
type: "api",
|
type: "api",
|
||||||
url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}`,
|
url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}`,
|
||||||
})
|
})
|
||||||
console.log({response2: response})
|
|
||||||
if (
|
if (
|
||||||
response &&
|
response &&
|
||||||
Array.isArray(response) &&
|
Array.isArray(response) &&
|
||||||
|
Loading…
x
Reference in New Issue
Block a user