forked from Qortal/qortal-ui
Merge branch 'feature/reusable-editor' into feature/implement-logic-edit-reply-messages
This commit is contained in:
commit
62302f5efb
@ -14,7 +14,7 @@ import './ChatScroller.js'
|
||||
import './LevelFounder.js'
|
||||
import './NameMenu.js'
|
||||
import './TimeAgo.js'
|
||||
import { EmojiPicker } from 'emoji-picker-js';
|
||||
import './ChatTextEditor'
|
||||
import '@polymer/paper-spinner/paper-spinner-lite.js'
|
||||
import '@material/mwc-button'
|
||||
import '@material/mwc-dialog'
|
||||
@ -62,7 +62,8 @@ class ChatPage extends LitElement {
|
||||
imageFile: {type: Object},
|
||||
isUploadingImage: {type: Boolean},
|
||||
caption: { type: String },
|
||||
chatEditor: {type: Object}
|
||||
chatEditor: {type: Object},
|
||||
chatEditorNewChat: {type: Object}
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,6 +73,7 @@ class ChatPage extends LitElement {
|
||||
/* Styling mdc dialog native props */
|
||||
--mdc-dialog-min-width: 300px;
|
||||
--mdc-dialog-box-shadow:rgba(60, 64, 67, 0.3) 0px 1px 2px 0px, rgba(60, 64, 67, 0.15) 0px 2px 6px 2px;
|
||||
--mdc-dialog-z-index: 5
|
||||
}
|
||||
|
||||
html {
|
||||
@ -231,6 +233,9 @@ class ChatPage extends LitElement {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
justify-content: center;
|
||||
background: white;
|
||||
padding: 5px;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
.chatbar-caption {
|
||||
@ -348,47 +353,6 @@ class ChatPage extends LitElement {
|
||||
border-radius: 25%;
|
||||
}
|
||||
|
||||
.paperclip-icon {
|
||||
color: #494949;
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.paperclip-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon {
|
||||
width: 30px;
|
||||
margin-left: 5px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.file-picker-container {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.file-picker-input-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input[type=file]::-webkit-file-upload-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dialogCustom {
|
||||
position: fixed;
|
||||
z-index: 10000;
|
||||
@ -471,6 +435,7 @@ class ChatPage extends LitElement {
|
||||
.dialog-container {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
gap: 10px;
|
||||
@ -490,12 +455,13 @@ class ChatPage extends LitElement {
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.changeMsgInput = this.changeMsgInput.bind(this)
|
||||
this.getOldMessage = this.getOldMessage.bind(this)
|
||||
this._sendMessage = this._sendMessage.bind(this)
|
||||
this.insertImage = this.insertImage.bind(this)
|
||||
this.getMessageSize = this.getMessageSize.bind(this)
|
||||
// this.getMessageSize = this.getMessageSize.bind(this)
|
||||
this._downObserverhandler = this._downObserverhandler.bind(this)
|
||||
this.calculateIFrameHeight = this.calculateIFrameHeight.bind(this)
|
||||
// this.calculateIFrameHeight = this.calculateIFrameHeight.bind(this)
|
||||
this.selectedAddress = {}
|
||||
this.chatId = ''
|
||||
this.myAddress = ''
|
||||
@ -543,12 +509,14 @@ class ChatPage extends LitElement {
|
||||
` :
|
||||
this.renderChatScroller()}
|
||||
<mwc-dialog
|
||||
|
||||
id="showDialogPublicKey"
|
||||
?open=${this.imageFile}
|
||||
|
||||
@closed=${() => {
|
||||
this.chatEditor.enable();
|
||||
this.caption = "";
|
||||
this.imageFile = null;
|
||||
this.chatEditor.enable()
|
||||
|
||||
}}>
|
||||
<div class="dialog-header"></div>
|
||||
<div class="dialog-container mdc-dialog mdc-dialog__surface">
|
||||
@ -557,25 +525,26 @@ class ChatPage extends LitElement {
|
||||
`}
|
||||
<!-- Replace by reusable chatbar component -->
|
||||
<div class="caption-container">
|
||||
<textarea @change=${(e) => this.onCaptionChange(e.target.value)} .value=${this.caption} placeholder="Caption" class="chatbar-caption" tabindex='1' rows="1"></textarea>
|
||||
<div style="display:flex; ${this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 ? 'margin-bottom: 5px' : "margin-bottom: 0"}">
|
||||
${this.isLoading === false ? html`
|
||||
<img
|
||||
src="/img/qchat-send-message-icon.svg"
|
||||
alt="send-icon"
|
||||
class="send-icon"
|
||||
@click=${()=> {
|
||||
this._sendMessage({
|
||||
type: 'image',
|
||||
imageFile: this.imageFile,
|
||||
caption: this.caption,
|
||||
})
|
||||
}} />
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
<chat-text-editor
|
||||
iframeId="newChat"
|
||||
?hasGlobalEvents=${false}
|
||||
placeholder=${this.chatEditorPlaceholder}
|
||||
._sendMessage=${this._sendMessage}
|
||||
.setChatEditor=${(editor)=> this.setChatEditorNewChat(editor)}
|
||||
.chatEditor=${this.chatEditorNewChat}
|
||||
.imageFile=${this.imageFile}
|
||||
.insertImage=${this.insertImage}
|
||||
|
||||
.editedMessageObj=${this.editedMessageObj}
|
||||
|
||||
?isLoading=${this.isLoading}
|
||||
?isLoadingMessages=${this.isLoadingMessages}
|
||||
></chat-text-editor>
|
||||
<!-- <iframe
|
||||
}}" id="newChat" class="chat-editor" tabindex="-1" height=${this.iframeHeight}>
|
||||
</iframe> -->
|
||||
|
||||
|
||||
</div>
|
||||
${this.chatMessageSize >= 750 ?
|
||||
html`
|
||||
@ -604,7 +573,6 @@ class ChatPage extends LitElement {
|
||||
this._sendMessage({
|
||||
type: 'image',
|
||||
imageFile: this.imageFile,
|
||||
caption: this.caption,
|
||||
})
|
||||
}}
|
||||
>
|
||||
@ -649,78 +617,22 @@ class ChatPage extends LitElement {
|
||||
</div>
|
||||
`}
|
||||
<div class="chatbar" style="${this.chatMessageSize >= 750 && 'padding-bottom: 7px'}">
|
||||
<div class="chatbar-container" style="${this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 ? 'align-items: flex-end' : "align-items: center"}"
|
||||
>
|
||||
${this.accountName && (
|
||||
html`
|
||||
<div class="file-picker-container">
|
||||
<vaadin-icon
|
||||
class="paperclip-icon"
|
||||
icon="vaadin:paperclip"
|
||||
slot="icon"
|
||||
@click=${() => this.closeEditMessageContainer()}
|
||||
>
|
||||
</vaadin-icon>
|
||||
<div class="file-picker-input-container">
|
||||
<input
|
||||
.value="${this.imageFile}"
|
||||
@change="${e => this.insertImage(e.target.files[0])}"
|
||||
class="file-picker-input" type="file" name="myImage" accept="image/*" />
|
||||
</div>
|
||||
</div>
|
||||
`
|
||||
)}
|
||||
<textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea>
|
||||
<iframe
|
||||
id="_chatEditorDOM" class="chat-editor" tabindex="-1" height=${this.iframeHeight}>
|
||||
</iframe>
|
||||
<button class="emoji-button" ?disabled=${this.isLoading || this.isLoadingMessages}>
|
||||
${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`}
|
||||
</button>
|
||||
${this.editedMessageObj ? (
|
||||
html`
|
||||
<div>
|
||||
${this.isLoading === false ? html`
|
||||
<vaadin-icon
|
||||
class="checkmark-icon"
|
||||
icon="vaadin:check"
|
||||
slot="icon"
|
||||
@click=${() => this._sendMessage()}
|
||||
>
|
||||
</vaadin-icon>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
) :
|
||||
html`
|
||||
<div style="display:flex; ${this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 ? 'margin-bottom: 5px' : "margin-bottom: 0"}">
|
||||
${this.isLoading === false ? html`
|
||||
<img
|
||||
src="/img/qchat-send-message-icon.svg"
|
||||
alt="send-icon"
|
||||
class="send-icon"
|
||||
@click=${() => this._sendMessage()} />
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
${this.chatMessageSize >= 750 ?
|
||||
html`
|
||||
<div class="message-size-container">
|
||||
<div class="message-size" style="${this.chatMessageSize >= 1000 && 'color: #bd1515'}">
|
||||
${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 1000`}
|
||||
</div>
|
||||
</div>
|
||||
` :
|
||||
html``}
|
||||
</div>
|
||||
<chat-text-editor
|
||||
?hasGlobalEvents=${true}
|
||||
iframeId="_chatEditorDOM"
|
||||
placeholder=${this.chatEditorPlaceholder}
|
||||
._sendMessage=${this._sendMessage}
|
||||
.setChatEditor=${(editor)=> this.setChatEditor(editor)}
|
||||
.chatEditor=${this.chatEditor}
|
||||
.imageFile=${this.imageFile}
|
||||
.insertImage=${this.insertImage}
|
||||
.chatMessageInput=${this.chatMessageInput}
|
||||
.editedMessageObj=${this.editedMessageObj}
|
||||
.mirrorChatInput=${this.mirrorChatInput}
|
||||
?isLoading=${this.isLoading}
|
||||
?isLoadingMessages=${this.isLoadingMessages}
|
||||
></chat-text-editor>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -742,11 +654,23 @@ class ChatPage extends LitElement {
|
||||
`
|
||||
}
|
||||
|
||||
setChatEditor(editor){
|
||||
console.log({editor})
|
||||
this.chatEditor = editor
|
||||
}
|
||||
|
||||
setChatEditorNewChat(editor){
|
||||
this.chatEditorNewChat = editor
|
||||
}
|
||||
|
||||
insertImage(file){
|
||||
|
||||
if(file.type.includes('image')){
|
||||
this.imageFile = file
|
||||
this.chatEditor.disable();
|
||||
this.chatEditor.disable()
|
||||
// this.changeMsgInput('newChat')
|
||||
// this.initChatEditor();
|
||||
// this.chatEditor.disable();
|
||||
return
|
||||
}
|
||||
|
||||
@ -754,48 +678,31 @@ class ChatPage extends LitElement {
|
||||
|
||||
}
|
||||
|
||||
changeMsgInput(id){
|
||||
|
||||
this.chatEditor.remove()
|
||||
this.chatMessageInput = this.shadowRoot.getElementById(id);
|
||||
this.initChatEditor();
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
|
||||
// TODO: Load and fetch messages from localstorage (maybe save messages to localstorage...)
|
||||
// this.changeLanguage();
|
||||
this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button');
|
||||
this.mirrorChatInput = this.shadowRoot.getElementById('messageBox');
|
||||
this.chatMessageInput = this.shadowRoot.getElementById('_chatEditorDOM');
|
||||
this.accountName = window.parent.reduxStore.getState().app.accountInfo.names[0];
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (!this.chatEditor.content.body.matches(':focus')) {
|
||||
// WARNING: Deprecated methods from KeyBoard Event
|
||||
if (e.code === "Space" || e.keyCode === 32 || e.which === 32) {
|
||||
this.chatEditor.insertText(' ');
|
||||
} else if (inputKeyCodes.includes(e.keyCode)) {
|
||||
this.chatEditor.insertText(e.key);
|
||||
return this.chatEditor.focus();
|
||||
} else {
|
||||
return this.chatEditor.focus();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Init EmojiPicker
|
||||
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'
|
||||
});
|
||||
// document.addEventListener('keydown', (e) => {
|
||||
// if (!this.chatEditor.content.body.matches(':focus')) {
|
||||
// // WARNING: Deprecated methods from KeyBoard Event
|
||||
// if (e.code === "Space" || e.keyCode === 32 || e.which === 32) {
|
||||
// this.chatEditor.insertText(' ');
|
||||
// } else if (inputKeyCodes.includes(e.keyCode)) {
|
||||
// this.chatEditor.insertText(e.key);
|
||||
// return this.chatEditor.focus();
|
||||
// } else {
|
||||
// return this.chatEditor.focus();
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
this.emojiPicker.on('emoji', selection => {
|
||||
const emojiHtmlString = `<img class="emoji" draggable="false" alt="${selection.emoji}" src="${selection.url}">`;
|
||||
this.chatEditor.insertEmoji(emojiHtmlString);
|
||||
});
|
||||
|
||||
// Attach Event Handler
|
||||
this.emojiPickerHandler.addEventListener('click', () => this.emojiPicker.togglePicker(this.emojiPickerHandler));
|
||||
window.addEventListener('storage', () => {
|
||||
const checkLanguage = localStorage.getItem('qortalLanguage')
|
||||
use(checkLanguage)
|
||||
@ -838,7 +745,7 @@ class ChatPage extends LitElement {
|
||||
this.isReceipient ? getAddressPublicKey() : this.fetchChatMessages(this._chatId);
|
||||
|
||||
// Init ChatEditor
|
||||
this.initChatEditor();
|
||||
// this.initChatEditor();
|
||||
}, 100)
|
||||
|
||||
parentEpml.ready().then(() => {
|
||||
@ -868,35 +775,15 @@ class ChatPage extends LitElement {
|
||||
|
||||
|
||||
async updated(changedProperties) {
|
||||
if (changedProperties.has('messagesRendered')) {
|
||||
const chatReference1 = this.isReceipient ? 'direct' : 'group';
|
||||
const chatReference2 = this._chatId
|
||||
// if (chatReference1 && chatReference2) {
|
||||
// await messagesCache.setItem(`${chatReference1}-${chatReference2}`, this.messagesRendered);
|
||||
// }
|
||||
}
|
||||
|
||||
if (changedProperties && changedProperties.has('editedMessageObj')) {
|
||||
this.chatEditor.insertText(this.editedMessageObj.message)
|
||||
}
|
||||
if (changedProperties && changedProperties.has('chatMessageSize')) {
|
||||
console.log(this.chatMessageSize, "Chat Message Size");
|
||||
}
|
||||
if(changedProperties && changedProperties.has("imageFile")) {
|
||||
this.chatbarCaption = this.shadowRoot.querySelector('.chatbar-caption');
|
||||
this.chatbarCaption.focus();
|
||||
}
|
||||
}
|
||||
|
||||
calculateIFrameHeight(height) {
|
||||
|
||||
setTimeout(()=> {
|
||||
const editorTest = this.shadowRoot.getElementById('_chatEditorDOM').contentWindow.document.getElementById('testingId').scrollHeight
|
||||
|
||||
console.log('editor', editorTest)
|
||||
this.iframeHeight = editorTest + 20
|
||||
}, 50)
|
||||
|
||||
|
||||
// if(changedProperties && changedProperties.has("imageFile")) {
|
||||
// this.chatbarCaption = this.shadowRoot.querySelector('.chatbar-caption');
|
||||
// this.chatbarCaption.focus();
|
||||
// }
|
||||
}
|
||||
|
||||
onCaptionChange(e) {
|
||||
@ -917,14 +804,13 @@ class ChatPage extends LitElement {
|
||||
renderPlaceholder() {
|
||||
const mstring = get("chatpage.cchange8")
|
||||
const placeholder = this.isReceipient === true ? `Message ${this._chatId}` : `${mstring}`;
|
||||
this.chatEditorPlaceholder = placeholder;
|
||||
return placeholder;
|
||||
}
|
||||
|
||||
renderChatScroller() {
|
||||
return html`
|
||||
<chat-scroller
|
||||
.messages=${this.messagesRendered}
|
||||
.emojiPicker=${this.emojiPicker}
|
||||
.escapeHTML=${escape}
|
||||
.getOldMessage=${this.getOldMessage}
|
||||
.setRepliedToMessageObj=${(val) => this.setRepliedToMessageObj(val)}
|
||||
@ -932,7 +818,8 @@ class ChatPage extends LitElement {
|
||||
.focusChatEditor=${() => this.focusChatEditor()}
|
||||
.sendMessage=${(val)=> this._sendMessage(val)}
|
||||
>
|
||||
</chat-scroller>`
|
||||
</chat-scroller>
|
||||
`
|
||||
}
|
||||
|
||||
async getUpdateComplete() {
|
||||
@ -1057,55 +944,6 @@ class ChatPage extends LitElement {
|
||||
}
|
||||
}
|
||||
|
||||
getMessageSize(message){
|
||||
try {
|
||||
const messageText = message;
|
||||
// Format and Sanitize Message
|
||||
const sanitizedMessage = messageText.replace(/ /gi, ' ').replace(/<br\s*[\/]?>/gi, '\n');
|
||||
const trimmedMessage = sanitizedMessage.trim();
|
||||
let messageObject = {};
|
||||
|
||||
if (this.repliedToMessageObj) {
|
||||
let chatReference = this.repliedToMessageObj.reference;
|
||||
if (this.repliedToMessageObj.chatReference) {
|
||||
chatReference = this.repliedToMessageObj.chatReference;
|
||||
}
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: chatReference,
|
||||
version: 1
|
||||
}
|
||||
} else if (this.editedMessageObj) {
|
||||
let message = "";
|
||||
try {
|
||||
const parsedMessageObj = JSON.parse(this.editedMessageObj.decodedMessage);
|
||||
message = parsedMessageObj;
|
||||
} catch (error) {
|
||||
message = this.messageObj.decodedMessage
|
||||
}
|
||||
messageObject = {
|
||||
...message,
|
||||
messageText: trimmedMessage,
|
||||
}
|
||||
} else {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: '',
|
||||
version: 1
|
||||
};
|
||||
}
|
||||
|
||||
const stringified = JSON.stringify(messageObject);
|
||||
const size = new Blob([stringified]).size;
|
||||
this.chatMessageSize = size;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// set replied to message in chat editor
|
||||
|
||||
@ -1144,47 +982,6 @@ class ChatPage extends LitElement {
|
||||
* @property id or index
|
||||
* @property sender and other info..
|
||||
*/
|
||||
chatMessageTemplate(messageObj) {
|
||||
const hidemsg = this.hideMessages
|
||||
|
||||
let avatarImg = ''
|
||||
let nameMenu = ''
|
||||
let levelFounder = ''
|
||||
let hideit = hidemsg.includes(messageObj.sender)
|
||||
|
||||
levelFounder = `<level-founder checkleveladdress="${messageObj.sender}"></level-founder>`
|
||||
|
||||
if (messageObj.senderName) {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node]
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port
|
||||
const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`
|
||||
avatarImg = `<img src="${avatarUrl}" style="max-width:100%; max-height:100%;" onerror="this.onerror=null; this.src='/img/qortal-chat-logo.png';" />`
|
||||
}
|
||||
|
||||
if (messageObj.sender === this.myAddress) {
|
||||
nameMenu = `<span style="color: #03a9f4;">${messageObj.senderName ? messageObj.senderName : messageObj.sender}</span>`
|
||||
} else {
|
||||
nameMenu = `<name-menu toblockaddress="${messageObj.sender}" nametodialog="${messageObj.senderName ? messageObj.senderName : messageObj.sender}"></name-menu>`
|
||||
}
|
||||
|
||||
if (hideit === true) {
|
||||
return `
|
||||
<li class="clearfix"></li>
|
||||
`
|
||||
} else {
|
||||
return `
|
||||
<li class="clearfix">
|
||||
<div class="message-data ${messageObj.sender === this.selectedAddress.address ? "" : ""}">
|
||||
<span class="message-data-name">${nameMenu}</span>
|
||||
<span class="message-data-level">${levelFounder}</span>
|
||||
<span class="message-data-time"><message-time timestamp=${messageObj.timestamp}></message-time></span>
|
||||
</div>
|
||||
<div class="message-data-avatar" style="width:42px; height:42px; ${messageObj.sender === this.selectedAddress.address ? "float:left;" : "float:left;"} margin:3px;">${avatarImg}</div>
|
||||
<div class="message ${messageObj.sender === this.selectedAddress.address ? "my-message float-left" : "other-message float-left"}">${this.emojiPicker.parse(escape(messageObj.decodedMessage))}</div>
|
||||
</li>
|
||||
`
|
||||
}
|
||||
}
|
||||
|
||||
async renderNewMessage(newMessage) {
|
||||
if(newMessage.chatReference){
|
||||
@ -1457,6 +1254,7 @@ class ChatPage extends LitElement {
|
||||
}
|
||||
|
||||
async _sendMessage(outSideMsg) {
|
||||
|
||||
// have params to determine if it's a reply or not
|
||||
// have variable to determine if it's a response, holds signature in constructor
|
||||
// need original message signature
|
||||
@ -1467,7 +1265,8 @@ class ChatPage extends LitElement {
|
||||
|
||||
this.isLoading = true;
|
||||
this.chatEditor.disable();
|
||||
const messageText = this.mirrorChatInput.value;
|
||||
this.chatEditorNewChat.disable()
|
||||
const messageText = this.chatEditor.mirror.value;
|
||||
// Format and Sanitize Message
|
||||
const sanitizedMessage = messageText.replace(/ /gi, ' ').replace(/<br\s*[\/]?>/gi, '\n');
|
||||
const trimmedMessage = sanitizedMessage.trim();
|
||||
@ -1552,6 +1351,7 @@ class ChatPage extends LitElement {
|
||||
console.error(error)
|
||||
this.isLoading = false;
|
||||
this.chatEditor.enable();
|
||||
this.chatEditorNewChat.enable()
|
||||
return
|
||||
}
|
||||
typeMessage = 'edit';
|
||||
@ -1585,18 +1385,20 @@ class ChatPage extends LitElement {
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange27"));
|
||||
this.isLoading = false;
|
||||
this.chatEditor.enable();
|
||||
this.chatEditorNewChat.enable()
|
||||
return;
|
||||
}
|
||||
const image = this.imageFile
|
||||
const id = this.uid();
|
||||
const identifier = `qchat_${id}`;
|
||||
let compressedFile = '';
|
||||
await new Promise(resolve => {
|
||||
new Compressor(outSideMsg.imageFile, {
|
||||
new Compressor( image, {
|
||||
quality: .6,
|
||||
maxWidth: 500,
|
||||
success(result){
|
||||
const file = new File([result], "name", {
|
||||
type: outSideMsg.imageFile.type
|
||||
type: image.type
|
||||
});
|
||||
compressedFile = file
|
||||
resolve()
|
||||
@ -1611,6 +1413,7 @@ class ChatPage extends LitElement {
|
||||
parentEpml.request('showSnackBar', get("chatpage.cchange26"));
|
||||
this.isLoading = false;
|
||||
this.chatEditor.enable();
|
||||
this.chatEditorNewChat.enable()
|
||||
return;
|
||||
}
|
||||
|
||||
@ -1632,10 +1435,15 @@ class ChatPage extends LitElement {
|
||||
console.error(error)
|
||||
this.isLoading = false;
|
||||
this.chatEditor.enable();
|
||||
this.chatEditorNewChat.enable()
|
||||
return
|
||||
}
|
||||
const messageTextWithImage = this.chatEditorNewChat.mirror.value;
|
||||
// Format and Sanitize Message
|
||||
const sanitizedMessageWithImage = messageTextWithImage.replace(/ /gi, ' ').replace(/<br\s*[\/]?>/gi, '\n');
|
||||
const trimmedMessageWithImage = sanitizedMessageWithImage.trim();
|
||||
const messageObject = {
|
||||
messageText: outSideMsg.caption,
|
||||
messageText: trimmedMessageWithImage,
|
||||
images: [{
|
||||
service: "IMAGE",
|
||||
name: userName,
|
||||
@ -1702,9 +1510,11 @@ class ChatPage extends LitElement {
|
||||
} else if (/^\s*$/.test(trimmedMessage)) {
|
||||
this.isLoading = false;
|
||||
this.chatEditor.enable();
|
||||
this.chatEditorNewChat.enable()
|
||||
} else if (this.chatMessageSize >= 1000) {
|
||||
this.isLoading = false;
|
||||
this.chatEditor.enable();
|
||||
this.chatEditorNewChat.enable()
|
||||
let err1string = get("chatpage.cchange29");
|
||||
parentEpml.request('showSnackBar', `${err1string}`);
|
||||
} else if (this.repliedToMessageObj) {
|
||||
@ -1813,7 +1623,6 @@ class ChatPage extends LitElement {
|
||||
let nonce = null
|
||||
let chatBytesArray = null
|
||||
await new Promise((res, rej) => {
|
||||
console.log({chatBytes})
|
||||
worker.postMessage({chatBytes, path, difficulty});
|
||||
|
||||
worker.onmessage = e => {
|
||||
@ -1847,6 +1656,7 @@ class ChatPage extends LitElement {
|
||||
|
||||
this.isLoading = false;
|
||||
this.chatEditor.enable();
|
||||
this.chatEditorNewChat.enable()
|
||||
this.closeEditMessageContainer()
|
||||
this.closeRepliedToContainer()
|
||||
};
|
||||
@ -1909,320 +1719,6 @@ class ChatPage extends LitElement {
|
||||
if (!arr) { return true }
|
||||
return arr.length === 0
|
||||
}
|
||||
|
||||
|
||||
|
||||
initChatEditor() {
|
||||
|
||||
const ChatEditor = function (editorConfig) {
|
||||
|
||||
const ChatEditor = function () {
|
||||
const editor = this;
|
||||
editor.init();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.getValue = function () {
|
||||
const editor = this;
|
||||
|
||||
if (editor.content) {
|
||||
return editor.contentDiv.innerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
ChatEditor.prototype.setValue = function (value) {
|
||||
const editor = this;
|
||||
|
||||
if (value) {
|
||||
editor.contentDiv.innerHTML = value;
|
||||
editor.updateMirror();
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.resetValue = function () {
|
||||
const editor = this;
|
||||
editor.contentDiv.innerHTML = '';
|
||||
editor.updateMirror();
|
||||
editor.focus();
|
||||
editorConfig.calculateIFrameHeight()
|
||||
};
|
||||
|
||||
ChatEditor.prototype.styles = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.styles = document.createElement('style');
|
||||
editor.styles.setAttribute('type', 'text/css');
|
||||
editor.styles.innerText = `
|
||||
html {
|
||||
cursor: text;
|
||||
}
|
||||
div {
|
||||
font-size: 1rem;
|
||||
line-height: 1.38rem;
|
||||
font-weight: 400;
|
||||
font-family: "Open Sans", helvetica, sans-serif;
|
||||
padding-right: 3px;
|
||||
text-align: left;
|
||||
white-space: break-spaces;
|
||||
word-break: break-word;
|
||||
outline: none;
|
||||
min-height: 20px;
|
||||
}
|
||||
div[contentEditable=true]:empty:before {
|
||||
content: attr(data-placeholder);
|
||||
display: block;
|
||||
color: rgb(103, 107, 113);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
div[contentEditable=false]{
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
img.emoji {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin-bottom: -2px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
`;
|
||||
editor.content.head.appendChild(editor.styles);
|
||||
};
|
||||
|
||||
ChatEditor.prototype.enable = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.setAttribute('contenteditable', 'true');
|
||||
editor.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.disable = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.setAttribute('contenteditable', 'false');
|
||||
};
|
||||
|
||||
ChatEditor.prototype.state = function () {
|
||||
const editor = this;
|
||||
|
||||
return editor.contentDiv.getAttribute('contenteditable');
|
||||
};
|
||||
|
||||
ChatEditor.prototype.focus = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.clearSelection = function () {
|
||||
const editor = this;
|
||||
|
||||
let selection = editor.content.getSelection().toString();
|
||||
if (!/^\s*$/.test(selection)) editor.content.getSelection().removeAllRanges();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.insertEmoji = function (emojiImg) {
|
||||
const editor = this;
|
||||
|
||||
const doInsert = () => {
|
||||
|
||||
if (editor.content.queryCommandSupported("InsertHTML")) {
|
||||
editor.content.execCommand("insertHTML", false, emojiImg);
|
||||
editor.updateMirror();
|
||||
}
|
||||
};
|
||||
|
||||
editor.focus();
|
||||
return doInsert();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.insertText = function (text) {
|
||||
const editor = this;
|
||||
|
||||
const parsedText = editorConfig.emojiPicker.parse(text);
|
||||
const doPaste = () => {
|
||||
|
||||
if (editor.content.queryCommandSupported("InsertHTML")) {
|
||||
editor.content.execCommand("insertHTML", false, parsedText);
|
||||
editor.updateMirror();
|
||||
}
|
||||
};
|
||||
|
||||
editor.focus();
|
||||
return doPaste();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.updateMirror = function () {
|
||||
const editor = this;
|
||||
|
||||
const chatInputValue = editor.getValue();
|
||||
const filteredValue = chatInputValue.replace(/<img.*?alt=".*?/g, '').replace(/".?src=.*?>/g, '');
|
||||
|
||||
let unescapedValue = editorConfig.unescape(filteredValue);
|
||||
editor.mirror.value = unescapedValue;
|
||||
};
|
||||
|
||||
ChatEditor.prototype.listenChanges = function () {
|
||||
const editor = this;
|
||||
|
||||
const events = ['drop', 'contextmenu', 'mouseup', 'click', 'touchend', 'keydown', 'blur', 'paste']
|
||||
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const event = events[i]
|
||||
editor.content.body.addEventListener(event, async function (e) {
|
||||
console.log({event})
|
||||
if (e.type === 'click') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (e.type === 'paste') {
|
||||
e.preventDefault();
|
||||
const item_list = await navigator.clipboard.read();
|
||||
let image_type; // we will feed this later
|
||||
const item = item_list.find( item => // choose the one item holding our image
|
||||
item.types.some( type => {
|
||||
if (type.startsWith( 'image/')) {
|
||||
image_type = type;
|
||||
return true;
|
||||
}
|
||||
})
|
||||
);
|
||||
if(item){
|
||||
const blob = item && await item.getType( image_type );
|
||||
var file = new File([blob], "name", {
|
||||
type: image_type
|
||||
});
|
||||
|
||||
editorConfig.insertImage(file)
|
||||
} else {
|
||||
navigator.clipboard.readText().then(clipboardText => {
|
||||
let escapedText = editorConfig.escape(clipboardText);
|
||||
editor.insertText(escapedText);
|
||||
}).catch(err => {
|
||||
// Fallback if everything fails...
|
||||
let textData = (e.originalEvent || e).clipboardData.getData('text/plain');
|
||||
editor.insertText(textData);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.type === 'contextmenu') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.type === 'keydown') {
|
||||
editorConfig.calculateIFrameHeight(editorConfig.editableElement.contentDocument.body.scrollHeight);
|
||||
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.innerHTML);
|
||||
|
||||
// Handle Enter
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
|
||||
// Update Mirror
|
||||
editor.updateMirror();
|
||||
|
||||
if (editor.state() === 'false') return false;
|
||||
|
||||
editorConfig.sendFunc();
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle Commands with CTR or CMD
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
switch (e.keyCode) {
|
||||
case 66:
|
||||
case 98: e.preventDefault();
|
||||
return false;
|
||||
case 73:
|
||||
case 105: e.preventDefault();
|
||||
return false;
|
||||
case 85:
|
||||
case 117: e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.type === 'blur') {
|
||||
editor.clearSelection();
|
||||
}
|
||||
|
||||
if (e.type === 'drop') {
|
||||
e.preventDefault();
|
||||
|
||||
let droppedText = e.dataTransfer.getData('text/plain')
|
||||
let escapedText = editorConfig.escape(droppedText)
|
||||
|
||||
editor.insertText(escapedText);
|
||||
return false;
|
||||
}
|
||||
|
||||
editor.updateMirror();
|
||||
});
|
||||
}
|
||||
|
||||
editor.content.addEventListener('click', function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
editor.focus();
|
||||
});
|
||||
};
|
||||
|
||||
ChatEditor.prototype.init = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.frame = editorConfig.editableElement;
|
||||
editor.mirror = editorConfig.mirrorElement;
|
||||
|
||||
editor.content = (editor.frame.contentDocument || editor.frame.document);
|
||||
|
||||
let elemDiv = document.createElement('div');
|
||||
elemDiv.setAttribute('contenteditable', 'true');
|
||||
elemDiv.setAttribute('spellcheck', 'false');
|
||||
elemDiv.setAttribute('data-placeholder', editorConfig.placeholder);
|
||||
elemDiv.style.cssText = 'width:100%';
|
||||
elemDiv.id = 'testingId'
|
||||
editor.content.body.appendChild(elemDiv);
|
||||
console.log('body', editor.frame.contentDocument.body, 'div', editor.frame.contentDocument.body.firstChild)
|
||||
editor.contentDiv = editor.frame.contentDocument.body.firstChild
|
||||
editor.styles();
|
||||
editor.listenChanges();
|
||||
};
|
||||
|
||||
|
||||
function doInit() {
|
||||
return new ChatEditor();
|
||||
}
|
||||
return doInit();
|
||||
};
|
||||
|
||||
const editorConfig = {
|
||||
getMessageSize: this.getMessageSize,
|
||||
calculateIFrameHeight: this.calculateIFrameHeight,
|
||||
mirrorElement: this.mirrorChatInput,
|
||||
editableElement: this.chatMessageInput,
|
||||
sendFunc: this._sendMessage,
|
||||
emojiPicker: this.emojiPicker,
|
||||
escape: escape,
|
||||
unescape: unescape,
|
||||
placeholder: this.chatEditorPlaceholder,
|
||||
imageFile: this.imageFile,
|
||||
requestUpdate: this.requestUpdate,
|
||||
insertImage: this.insertImage,
|
||||
chatMessageSize: this.chatMessageSize
|
||||
};
|
||||
this.chatEditor = new ChatEditor(editorConfig);
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define('chat-page', ChatPage)
|
||||
|
@ -22,7 +22,6 @@ class ChatScroller extends LitElement {
|
||||
return {
|
||||
getNewMessage: { attribute: false },
|
||||
getOldMessage: { attribute: false },
|
||||
emojiPicker: { attribute: false },
|
||||
escapeHTML: { attribute: false },
|
||||
messages: { type: Array },
|
||||
hideMessages: { type: Array },
|
||||
@ -42,11 +41,19 @@ class ChatScroller extends LitElement {
|
||||
this._downObserverHandler = this._downObserverHandler.bind(this)
|
||||
this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address
|
||||
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'
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
render() {
|
||||
console.log({messages: this.messages})
|
||||
|
||||
let formattedMessages = this.messages.reduce((messageArray, message) => {
|
||||
const lastGroupedMessage = messageArray[messageArray.length - 1];
|
||||
@ -118,10 +125,12 @@ class ChatScroller extends LitElement {
|
||||
this.upObserverElement = this.shadowRoot.getElementById('upObserver');
|
||||
this.downObserverElement = this.shadowRoot.getElementById('downObserver');
|
||||
// Intialize Observers
|
||||
this.upElementObserver();
|
||||
this.downElementObserver();
|
||||
await this.updateComplete;
|
||||
this.viewElement.scrollTop = this.viewElement.scrollHeight;
|
||||
this.upElementObserver()
|
||||
this.downElementObserver()
|
||||
await this.updateComplete
|
||||
this.viewElement.scrollTop = this.viewElement.scrollHeight + 50
|
||||
|
||||
|
||||
}
|
||||
|
||||
_getOldMessage(_scrollElement) {
|
||||
@ -272,7 +281,6 @@ class MessageTemplate extends LitElement {
|
||||
let hideit = hidemsg.includes(this.messageObj.sender);
|
||||
|
||||
levelFounder = html`<level-founder checkleveladdress="${this.messageObj.sender}"></level-founder>`;
|
||||
console.log({message})
|
||||
if (this.messageObj.senderName) {
|
||||
const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node];
|
||||
const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port;
|
||||
@ -518,7 +526,7 @@ class ChatMenu extends LitElement {
|
||||
focusChatEditor: { type: Function },
|
||||
myAddress: { type: Object },
|
||||
emojiPicker: { attribute: false },
|
||||
sendMessage: {type: Function}
|
||||
sendMessage: {type: Function},
|
||||
}
|
||||
}
|
||||
|
||||
@ -557,7 +565,7 @@ class ChatMenu extends LitElement {
|
||||
this.emojiPicker.on('emoji', selection => {
|
||||
this.sendMessage({
|
||||
type: 'reaction',
|
||||
editedMessageObj: this.originalMessage,
|
||||
editedMessageObj: this.originalMessage,
|
||||
reaction: selection.emoji,
|
||||
|
||||
|
||||
|
691
qortal-ui-plugins/plugins/core/components/ChatTextEditor.js
Normal file
691
qortal-ui-plugins/plugins/core/components/ChatTextEditor.js
Normal file
@ -0,0 +1,691 @@
|
||||
import { LitElement, html, css } from "lit"
|
||||
import { render } from "lit/html.js"
|
||||
import { escape, unescape } from 'html-escaper';
|
||||
import { EmojiPicker } from 'emoji-picker-js';
|
||||
import { inputKeyCodes } from '../../utils/keyCodes.js'
|
||||
|
||||
|
||||
class ChatTextEditor extends LitElement {
|
||||
static get properties() {
|
||||
return {
|
||||
isLoading: { type: Boolean },
|
||||
isLoadingMessages: { type: Boolean },
|
||||
_sendMessage: {attribute: false},
|
||||
placeholder: {type: String},
|
||||
imageFile: {type: Object},
|
||||
insertImage: {attribute: false},
|
||||
iframeHeight: { type: Number },
|
||||
editedMessageObj: {type: Object},
|
||||
chatEditor: {type: Object},
|
||||
setChatEditor: {attribute: false},
|
||||
iframeId: {type: String},
|
||||
hasGlobalEvents: {type: Boolean}
|
||||
}
|
||||
}
|
||||
|
||||
static get styles() {
|
||||
return css`
|
||||
:host {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: auto;
|
||||
overflow-y: hidden;
|
||||
width: 100%;
|
||||
}
|
||||
.chatbar-container {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.emoji-button {
|
||||
width: 45px;
|
||||
height: 40px;
|
||||
padding-top: 4px;
|
||||
border: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
cursor: pointer;
|
||||
max-height: 40px;
|
||||
color: var(--black);
|
||||
}
|
||||
|
||||
.message-size-container {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.message-size {
|
||||
font-family: Roboto, sans-serif;
|
||||
font-size: 12px;
|
||||
color: black;
|
||||
}
|
||||
.paperclip-icon {
|
||||
color: #494949;
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.paperclip-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon {
|
||||
width: 30px;
|
||||
margin-left: 5px;
|
||||
transition: all 0.1s ease-in-out;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.send-icon:hover {
|
||||
filter: brightness(1.1);
|
||||
}
|
||||
|
||||
.file-picker-container {
|
||||
position: relative;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
}
|
||||
|
||||
.file-picker-input-container {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
right: 0px;
|
||||
z-index: 10;
|
||||
opacity: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
input[type=file]::-webkit-file-upload-button {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.chatbar-container textarea {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.chatbar-container .chat-editor {
|
||||
display: flex;
|
||||
max-height: -webkit-fill-available;
|
||||
width: 100%;
|
||||
border-color: transparent;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: none;
|
||||
}
|
||||
`
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super()
|
||||
this.isLoadingMessages = true
|
||||
this.isLoading = false
|
||||
this.getMessageSize = this.getMessageSize.bind(this)
|
||||
this.calculateIFrameHeight = this.calculateIFrameHeight.bind(this)
|
||||
this.addGlobalEventListener = this.addGlobalEventListener.bind(this)
|
||||
this.removeGlobalEventListener = this.removeGlobalEventListener.bind(this)
|
||||
this.initialChat = this.initialChat.bind(this)
|
||||
this.iframeHeight = 42
|
||||
|
||||
}
|
||||
|
||||
render() {
|
||||
return html`
|
||||
<div class="chatbar-container" style="${this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 ? 'align-items: flex-end' : "align-items: center"}"
|
||||
>
|
||||
<div class="file-picker-container">
|
||||
<vaadin-icon
|
||||
class="paperclip-icon"
|
||||
icon="vaadin:paperclip"
|
||||
slot="icon"
|
||||
>
|
||||
</vaadin-icon>
|
||||
<div class="file-picker-input-container">
|
||||
<input
|
||||
.value="${this.imageFile}"
|
||||
@change="${e => this.insertImage(e.target.files[0])}"
|
||||
class="file-picker-input" type="file" name="myImage" accept="image/*" />
|
||||
</div>
|
||||
</div>
|
||||
<textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea>
|
||||
<iframe
|
||||
}}" id=${this.iframeId} class="chat-editor" tabindex="-1" height=${this.iframeHeight}>
|
||||
</iframe>
|
||||
<button class="emoji-button" ?disabled=${this.isLoading || this.isLoadingMessages}>
|
||||
${html`<img class="emoji" draggable="false" alt="😀" src="/emoji/svg/1f600.svg" />`}
|
||||
</button>
|
||||
${this.editedMessageObj ? (
|
||||
html`
|
||||
<div>
|
||||
${this.isLoading === false ? html`
|
||||
<vaadin-icon
|
||||
class="checkmark-icon"
|
||||
icon="vaadin:check"
|
||||
slot="icon"
|
||||
@click=${() => this._sendMessage()}
|
||||
>
|
||||
</vaadin-icon>
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
) :
|
||||
html`
|
||||
<div style="display:flex; ${this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 ? 'margin-bottom: 5px' : "margin-bottom: 0"}">
|
||||
${this.isLoading === false ? html`
|
||||
<img
|
||||
src="/img/qchat-send-message-icon.svg"
|
||||
alt="send-icon"
|
||||
class="send-icon"
|
||||
@click=${() => this._sendMessage()} />
|
||||
` :
|
||||
html`
|
||||
<paper-spinner-lite active></paper-spinner-lite>
|
||||
`}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
</div>
|
||||
${this.chatMessageSize >= 750 ?
|
||||
html`
|
||||
<div class="message-size-container">
|
||||
<div class="message-size" style="${this.chatMessageSize >= 1000 && 'color: #bd1515'}">
|
||||
${`Your message size is of ${this.chatMessageSize} bytes out of a maximum of 1000`}
|
||||
</div>
|
||||
</div>
|
||||
` :
|
||||
html``}
|
||||
</div>
|
||||
`
|
||||
}
|
||||
|
||||
initialChat(e) {
|
||||
console.log('hello initial', this.chatEditor)
|
||||
if (!this.chatEditor?.contentDiv.matches(':focus')) {
|
||||
// WARNING: Deprecated methods from KeyBoard Event
|
||||
if (e.code === "Space" || e.keyCode === 32 || e.which === 32) {
|
||||
this.chatEditor.insertText(' ');
|
||||
} else if (inputKeyCodes.includes(e.keyCode)) {
|
||||
this.chatEditor.insertText(e.key);
|
||||
return this.chatEditor.focus();
|
||||
} else {
|
||||
return this.chatEditor.focus();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
addGlobalEventListener(){
|
||||
document.addEventListener('keydown', this.initialChat);
|
||||
}
|
||||
|
||||
removeGlobalEventListener(){
|
||||
document.removeEventListener('keydown', this.initialChat);
|
||||
}
|
||||
|
||||
async firstUpdated() {
|
||||
console.log('this.hasGlobalEvents', this.hasGlobalEvents)
|
||||
if(this.hasGlobalEvents){
|
||||
this.addGlobalEventListener()
|
||||
}
|
||||
|
||||
this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button');
|
||||
this.mirrorChatInput = this.shadowRoot.getElementById('messageBox');
|
||||
this.chatMessageInput = this.shadowRoot.getElementById(this.iframeId);
|
||||
console.log('test', this.chatMessageInput )
|
||||
|
||||
|
||||
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',
|
||||
zIndex: 100
|
||||
|
||||
});
|
||||
|
||||
this.emojiPicker.on('emoji', selection => {
|
||||
console.log('hello selection')
|
||||
const emojiHtmlString = `<img class="emoji" draggable="false" alt="${selection.emoji}" src="${selection.url}">`;
|
||||
this.chatEditor.insertEmoji(emojiHtmlString);
|
||||
});
|
||||
|
||||
|
||||
this.emojiPickerHandler.addEventListener('click', () => this.emojiPicker.togglePicker(this.emojiPickerHandler));
|
||||
|
||||
await this.updateComplete;
|
||||
this.initChatEditor();
|
||||
}
|
||||
|
||||
async updated(changedProperties) {
|
||||
console.log({changedProperties})
|
||||
if (changedProperties && changedProperties.has('editedMessageObj')) {
|
||||
this.chatEditor.insertText(this.editedMessageObj.message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
shouldUpdate(changedProperties) {
|
||||
// Only update element if prop1 changed.
|
||||
if(changedProperties.has('setChatEditor') && changedProperties.size === 1) return false
|
||||
return true
|
||||
}
|
||||
|
||||
getMessageSize(message){
|
||||
try {
|
||||
const messageText = message;
|
||||
// Format and Sanitize Message
|
||||
const sanitizedMessage = messageText.replace(/ /gi, ' ').replace(/<br\s*[\/]?>/gi, '\n');
|
||||
const trimmedMessage = sanitizedMessage.trim();
|
||||
let messageObject = {};
|
||||
|
||||
if (this.repliedToMessageObj) {
|
||||
let chatReference = this.repliedToMessageObj.reference;
|
||||
if (this.repliedToMessageObj.chatReference) {
|
||||
chatReference = this.repliedToMessageObj.chatReference;
|
||||
}
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: chatReference,
|
||||
version: 1
|
||||
}
|
||||
} else if (this.editedMessageObj) {
|
||||
let message = "";
|
||||
try {
|
||||
const parsedMessageObj = JSON.parse(this.editedMessageObj.decodedMessage);
|
||||
message = parsedMessageObj;
|
||||
} catch (error) {
|
||||
message = this.messageObj.decodedMessage
|
||||
}
|
||||
messageObject = {
|
||||
...message,
|
||||
messageText: trimmedMessage,
|
||||
}
|
||||
} else {
|
||||
messageObject = {
|
||||
messageText: trimmedMessage,
|
||||
images: [''],
|
||||
repliedTo: '',
|
||||
version: 1
|
||||
};
|
||||
}
|
||||
|
||||
const stringified = JSON.stringify(messageObject);
|
||||
const size = new Blob([stringified]).size;
|
||||
this.chatMessageSize = size;
|
||||
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
calculateIFrameHeight(height) {
|
||||
|
||||
setTimeout(()=> {
|
||||
const editorTest = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('testingId').scrollHeight
|
||||
|
||||
|
||||
this.iframeHeight = editorTest + 20
|
||||
}, 50)
|
||||
|
||||
|
||||
}
|
||||
|
||||
initChatEditor() {
|
||||
console.log('hello editor')
|
||||
const ChatEditor = function (editorConfig) {
|
||||
|
||||
const ChatEditor = function () {
|
||||
const editor = this;
|
||||
editor.init();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.getValue = function () {
|
||||
const editor = this;
|
||||
|
||||
if (editor.contentDiv) {
|
||||
return editor.contentDiv.innerHTML;
|
||||
}
|
||||
};
|
||||
|
||||
ChatEditor.prototype.setValue = function (value) {
|
||||
const editor = this;
|
||||
|
||||
if (value) {
|
||||
editor.contentDiv.innerHTML = value;
|
||||
editor.updateMirror();
|
||||
}
|
||||
|
||||
editor.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.resetValue = function () {
|
||||
const editor = this;
|
||||
editor.contentDiv.innerHTML = '';
|
||||
editor.updateMirror();
|
||||
editor.focus();
|
||||
editorConfig.calculateIFrameHeight()
|
||||
};
|
||||
|
||||
ChatEditor.prototype.styles = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.styles = document.createElement('style');
|
||||
editor.styles.setAttribute('type', 'text/css');
|
||||
editor.styles.innerText = `
|
||||
html {
|
||||
cursor: text;
|
||||
}
|
||||
div {
|
||||
font-size: 1rem;
|
||||
line-height: 1.38rem;
|
||||
font-weight: 400;
|
||||
font-family: "Open Sans", helvetica, sans-serif;
|
||||
padding-right: 3px;
|
||||
text-align: left;
|
||||
white-space: break-spaces;
|
||||
word-break: break-word;
|
||||
outline: none;
|
||||
min-height: 20px;
|
||||
}
|
||||
div[contentEditable=true]:empty:before {
|
||||
content: attr(data-placeholder);
|
||||
display: block;
|
||||
color: rgb(103, 107, 113);
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
user-select: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
div[contentEditable=false]{
|
||||
background: rgba(0,0,0,0.1);
|
||||
}
|
||||
img.emoji {
|
||||
width: 1.7em;
|
||||
height: 1.5em;
|
||||
margin-bottom: -2px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
`;
|
||||
editor.content.head.appendChild(editor.styles);
|
||||
};
|
||||
|
||||
ChatEditor.prototype.enable = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.setAttribute('contenteditable', 'true');
|
||||
editor.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.getMirrorElement = function (){
|
||||
return editor.mirror
|
||||
}
|
||||
|
||||
ChatEditor.prototype.disable = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.setAttribute('contenteditable', 'false');
|
||||
};
|
||||
|
||||
ChatEditor.prototype.state = function () {
|
||||
const editor = this;
|
||||
|
||||
return editor.contentDiv.getAttribute('contenteditable');
|
||||
};
|
||||
|
||||
ChatEditor.prototype.focus = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.contentDiv.focus();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.clearSelection = function () {
|
||||
const editor = this;
|
||||
|
||||
let selection = editor.content.getSelection().toString();
|
||||
if (!/^\s*$/.test(selection)) editor.content.getSelection().removeAllRanges();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.insertEmoji = function (emojiImg) {
|
||||
const editor = this;
|
||||
|
||||
const doInsert = () => {
|
||||
|
||||
if (editor.content.queryCommandSupported("InsertHTML")) {
|
||||
editor.content.execCommand("insertHTML", false, emojiImg);
|
||||
editor.updateMirror();
|
||||
}
|
||||
};
|
||||
|
||||
editor.focus();
|
||||
return doInsert();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.insertText = function (text) {
|
||||
const editor = this;
|
||||
|
||||
const parsedText = editorConfig.emojiPicker.parse(text);
|
||||
const doPaste = () => {
|
||||
|
||||
if (editor.content.queryCommandSupported("InsertHTML")) {
|
||||
editor.content.execCommand("insertHTML", false, parsedText);
|
||||
editor.updateMirror();
|
||||
}
|
||||
};
|
||||
|
||||
editor.focus();
|
||||
return doPaste();
|
||||
};
|
||||
|
||||
ChatEditor.prototype.updateMirror = function () {
|
||||
const editor = this;
|
||||
|
||||
const chatInputValue = editor.getValue();
|
||||
const filteredValue = chatInputValue.replace(/<img.*?alt=".*?/g, '').replace(/".?src=.*?>/g, '');
|
||||
|
||||
let unescapedValue = editorConfig.unescape(filteredValue);
|
||||
console.log({unescapedValue})
|
||||
editor.mirror.value = unescapedValue;
|
||||
};
|
||||
|
||||
ChatEditor.prototype.listenChanges = function () {
|
||||
|
||||
const editor = this;
|
||||
|
||||
const events = ['drop', 'contextmenu', 'mouseup', 'click', 'touchend', 'keydown', 'blur', 'paste']
|
||||
|
||||
for (let i = 0; i < events.length; i++) {
|
||||
const event = events[i]
|
||||
editor.content.body.addEventListener(event, async function (e) {
|
||||
|
||||
if (e.type === 'click') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
|
||||
if (e.type === 'paste') {
|
||||
e.preventDefault();
|
||||
const item_list = await navigator.clipboard.read();
|
||||
let image_type; // we will feed this later
|
||||
const item = item_list.find( item => // choose the one item holding our image
|
||||
item.types.some( type => {
|
||||
if (type.startsWith( 'image/')) {
|
||||
image_type = type;
|
||||
return true;
|
||||
}
|
||||
})
|
||||
);
|
||||
if(item){
|
||||
const blob = item && await item.getType( image_type );
|
||||
var file = new File([blob], "name", {
|
||||
type: image_type
|
||||
});
|
||||
|
||||
editorConfig.insertImage(file)
|
||||
} else {
|
||||
navigator.clipboard.readText().then(clipboardText => {
|
||||
let escapedText = editorConfig.escape(clipboardText);
|
||||
editor.insertText(escapedText);
|
||||
}).catch(err => {
|
||||
// Fallback if everything fails...
|
||||
let textData = (e.originalEvent || e).clipboardData.getData('text/plain');
|
||||
editor.insertText(textData);
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.type === 'contextmenu') {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (e.type === 'keydown') {
|
||||
editorConfig.calculateIFrameHeight(editorConfig.editableElement.contentDocument.body.scrollHeight);
|
||||
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.innerHTML);
|
||||
|
||||
// Handle Enter
|
||||
if (e.keyCode === 13 && !e.shiftKey) {
|
||||
|
||||
// Update Mirror
|
||||
editor.updateMirror();
|
||||
|
||||
if (editor.state() === 'false') return false;
|
||||
if(editorConfig.iframeId === 'newChat'){
|
||||
editorConfig.sendFunc(
|
||||
{
|
||||
type: 'image',
|
||||
imageFile: editorConfig.imageFile,
|
||||
}
|
||||
);
|
||||
} else {
|
||||
editorConfig.sendFunc();
|
||||
}
|
||||
|
||||
e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle Commands with CTR or CMD
|
||||
if (e.ctrlKey || e.metaKey) {
|
||||
switch (e.keyCode) {
|
||||
case 66:
|
||||
case 98: e.preventDefault();
|
||||
return false;
|
||||
case 73:
|
||||
case 105: e.preventDefault();
|
||||
return false;
|
||||
case 85:
|
||||
case 117: e.preventDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (e.type === 'blur') {
|
||||
editor.clearSelection();
|
||||
}
|
||||
|
||||
if (e.type === 'drop') {
|
||||
e.preventDefault();
|
||||
|
||||
let droppedText = e.dataTransfer.getData('text/plain')
|
||||
let escapedText = editorConfig.escape(droppedText)
|
||||
|
||||
editor.insertText(escapedText);
|
||||
return false;
|
||||
}
|
||||
|
||||
editor.updateMirror();
|
||||
});
|
||||
}
|
||||
|
||||
editor.content.addEventListener('click', function (event) {
|
||||
|
||||
event.preventDefault();
|
||||
editor.focus();
|
||||
});
|
||||
};
|
||||
|
||||
ChatEditor.prototype.remove = function () {
|
||||
const editor = this;
|
||||
var old_element = editor.content.body
|
||||
var new_element = old_element.cloneNode(true);
|
||||
editor.content.body.parentNode.replaceChild(new_element, old_element);
|
||||
while (editor.content.body.firstChild) {
|
||||
editor.content.body.removeChild(editor.content.body.lastChild);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
ChatEditor.prototype.init = function () {
|
||||
const editor = this;
|
||||
|
||||
editor.frame = editorConfig.editableElement;
|
||||
editor.mirror = editorConfig.mirrorElement;
|
||||
|
||||
editor.content = (editor.frame.contentDocument || editor.frame.document);
|
||||
|
||||
let elemDiv = document.createElement('div');
|
||||
elemDiv.setAttribute('contenteditable', 'true');
|
||||
elemDiv.setAttribute('spellcheck', 'false');
|
||||
elemDiv.setAttribute('data-placeholder', editorConfig.placeholder);
|
||||
elemDiv.style.cssText = 'width:100%';
|
||||
elemDiv.id = 'testingId'
|
||||
editor.content.body.appendChild(elemDiv);
|
||||
editor.contentDiv = editor.frame.contentDocument.body.firstChild
|
||||
editor.styles();
|
||||
editor.listenChanges();
|
||||
|
||||
};
|
||||
|
||||
|
||||
function doInit() {
|
||||
return new ChatEditor();
|
||||
}
|
||||
return doInit();
|
||||
};
|
||||
|
||||
const editorConfig = {
|
||||
getMessageSize: this.getMessageSize,
|
||||
calculateIFrameHeight: this.calculateIFrameHeight,
|
||||
mirrorElement: this.mirrorChatInput,
|
||||
editableElement: this.chatMessageInput,
|
||||
sendFunc: this._sendMessage,
|
||||
emojiPicker: this.emojiPicker,
|
||||
escape: escape,
|
||||
unescape: unescape,
|
||||
placeholder: this.placeholder,
|
||||
imageFile: this.imageFile,
|
||||
requestUpdate: this.requestUpdate,
|
||||
insertImage: this.insertImage,
|
||||
chatMessageSize: this.chatMessageSize,
|
||||
addGlobalEventListener: this.addGlobalEventListener,
|
||||
removeGlobalEventListener: this.removeGlobalEventListener,
|
||||
iframeId: this.iframeId
|
||||
};
|
||||
console.log('after')
|
||||
const newChat = new ChatEditor(editorConfig)
|
||||
console.log({newChat})
|
||||
this.setChatEditor(newChat)
|
||||
}
|
||||
}
|
||||
|
||||
window.customElements.define("chat-text-editor", ChatTextEditor)
|
Loading…
x
Reference in New Issue
Block a user