add new editor

This commit is contained in:
Phillip Lang Martinez 2023-01-09 15:40:21 -05:00
parent 53f2aa93f7
commit 8d61f8eb2d
7 changed files with 1448 additions and 519 deletions

View File

@ -20,10 +20,26 @@
"@lit-labs/motion": "^1.0.3",
"@material/mwc-list": "0.27.0",
"@material/mwc-select": "0.27.0",
"@tiptap/core": "^2.0.0-beta.209",
"@tiptap/extension-image": "^2.0.0-beta.209",
"@tiptap/extension-placeholder": "^2.0.0-beta.209",
"@tiptap/extension-underline": "^2.0.0-beta.209",
"@tiptap/html": "^2.0.0-beta.209",
"@tiptap/starter-kit": "^2.0.0-beta.209",
"asmcrypto.js": "2.3.2",
"compressorjs": "^1.1.1",
"emoji-picker-js": "https://github.com/Qortal/emoji-picker-js",
"localforage": "^1.10.0",
"prosemirror-commands": "^1.5.0",
"prosemirror-dropcursor": "^1.6.1",
"prosemirror-gapcursor": "^1.3.1",
"prosemirror-history": "^1.3.0",
"prosemirror-keymap": "^1.2.0",
"prosemirror-model": "^1.18.3",
"prosemirror-schema-list": "^1.2.2",
"prosemirror-state": "^1.4.2",
"prosemirror-transform": "^1.7.0",
"prosemirror-view": "^1.29.1",
"short-unique-id": "^4.4.4"
},
"devDependencies": {
@ -47,8 +63,6 @@
"@polymer/paper-slider": "3.0.1",
"@polymer/paper-spinner": "3.0.2",
"@polymer/paper-tooltip": "3.0.1",
"@vaadin/horizontal-layout": "23.3.2",
"@vaadin/tabs": "23.3.2",
"@rollup/plugin-alias": "4.0.2",
"@rollup/plugin-babel": "6.0.3",
"@rollup/plugin-commonjs": "24.0.0",
@ -58,7 +72,9 @@
"@vaadin/avatar": "23.3.2",
"@vaadin/button": "23.3.2",
"@vaadin/grid": "23.3.2",
"@vaadin/horizontal-layout": "23.3.2",
"@vaadin/icons": "23.3.2",
"@vaadin/tabs": "23.3.2",
"@vaadin/tooltip": "23.3.2",
"epml": "0.3.3",
"file-saver": "2.0.5",

View File

@ -4,6 +4,12 @@ import {animate} from '@lit-labs/motion';
import { Epml } from '../../../epml.js';
import { use, get, translate, registerTranslateConfig } from 'lit-translate';
import { chatStyles } from './ChatScroller-css.js'
import { generateHTML } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline';
import Placeholder from '@tiptap/extension-placeholder'
import {unsafeHTML} from 'lit/directives/unsafe-html.js';
import { Editor, Extension } from '@tiptap/core'
// import localForage from "localforage";
registerTranslateConfig({
@ -72,8 +78,6 @@ class ChatPage extends LitElement {
iframeHeight: { type: Number },
imageFile: { type: Object },
isUploadingImage: { type: Boolean },
chatEditor: { type: Object },
chatEditorNewChat: { type: Object },
userLanguage: { type: String },
lastMessageRefVisible: { type: Boolean },
isLoadingOldMessages: { type: Boolean },
@ -91,7 +95,9 @@ class ChatPage extends LitElement {
userFoundModalOpen: { type: Boolean },
webWorker: { type: Object },
webWorkerImage: { type: Object },
myTrimmedMeassage: { type: String }
myTrimmedMeassage: { type: String },
editor: {type: Object},
currentEditor: {type: String}
}
}
@ -299,6 +305,7 @@ class ChatPage extends LitElement {
justify-content: center;
min-height: 60px;
max-height: 100%;
overflow: hidden;
}
.chat-text-area .typing-area {
@ -348,6 +355,10 @@ class ChatPage extends LitElement {
gap: 5px;
width: 100%;
}
.repliedTo-message p {
margin: 0px;
padding: 0px;
}
.reply-icon {
width: 20px;
@ -858,6 +869,7 @@ class ChatPage extends LitElement {
}
this.webWorker = null;
this.webWorkerImage = null;
this.currentEditor = '_chatEditorDOM'
}
_toggle(value) {
@ -922,7 +934,11 @@ class ChatPage extends LitElement {
<vaadin-icon class="reply-icon" icon="vaadin:reply" slot="icon"></vaadin-icon>
<div class="repliedTo-message">
<p class="senderName">${this.repliedToMessageObj.senderName ? this.repliedToMessageObj.senderName : this.repliedToMessageObj.sender}</p>
<p class="original-message">${this.repliedToMessageObj.message}</p>
${unsafeHTML(generateHTML(this.repliedToMessageObj.message, [
StarterKit,
Underline
// other extensions …
]))}
</div>
<vaadin-icon
class="close-icon"
@ -939,7 +955,11 @@ class ChatPage extends LitElement {
<vaadin-icon class="reply-icon" icon="vaadin:pencil" slot="icon"></vaadin-icon>
<div class="repliedTo-message">
<p class="senderName">${translate("chatpage.cchange25")}</p>
<p class="original-message">${this.editedMessageObj.message}</p>
${unsafeHTML(generateHTML(this.editedMessageObj.message, [
StarterKit,
Underline
// other extensions …
]))}
</div>
<vaadin-icon
class="close-icon"
@ -956,14 +976,16 @@ class ChatPage extends LitElement {
iframeId="_chatEditorDOM"
placeholder=${this.chatEditorPlaceholder}
._sendMessage=${this._sendMessage}
.setChatEditor=${(editor)=> this.setChatEditor(editor)}
.chatEditor=${this.chatEditor}
.imageFile=${this.imageFile}
.insertImage=${this.insertImage}
.editedMessageObj=${this.editedMessageObj}
?isLoading=${this.isLoading}
?isLoadingMessages=${this.isLoadingMessages}
?isEditMessageOpen=${this.isEditMessageOpen}>
?isEditMessageOpen=${this.isEditMessageOpen}
.editor=${this.editor}
.updatePlaceholder=${(editor, value)=> this.updatePlaceholder(editor, value)}
id="_chatEditorDOM"
>
</chat-text-editor>
</div>
</div>
@ -985,10 +1007,9 @@ class ChatPage extends LitElement {
`: ''}
<wrapper-modal
.onClickFunc=${() => {
this.chatEditorNewChat.resetValue();
this.removeImage();
}}
style=${(this.imageFile && !this.isUploadingImage) ? "display: block" : "display: none"}>
style=${(this.imageFile && !this.isUploadingImage) ? "visibility:visible;z-index:50" : "visibility: hidden;z-index:-100"}>
<div>
<div class="dialog-container">
${this.imageFile && html`
@ -1000,20 +1021,20 @@ class ChatPage extends LitElement {
?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}
id="chatTextCaption"
.editor=${this.editorImage}
.updatePlaceholder=${(editor, value)=> this.updatePlaceholder(editor, value)}
>
</chat-text-editor>
</div>
<div class="modal-button-row">
<button class="modal-button-red" @click=${() => {
this.chatEditorNewChat.resetValue();
this.removeImage();
}}>
${translate("chatpage.cchange33")}
@ -1047,7 +1068,6 @@ class ChatPage extends LitElement {
.onClickFunc=${() => {
this.openForwardOpen = false;
this.forwardActiveChatHeadUrl = {};
this.chatEditor.enable();
this.requestUpdate();
} }
style=${this.openForwardOpen ? "display: block" : "display: none"}>
@ -1136,7 +1156,6 @@ class ChatPage extends LitElement {
<button class="modal-button-red" @click=${() => {
this.openForwardOpen = false;
this.forwardActiveChatHeadUrl = {};
this.chatEditor.enable();
this.requestUpdate();
}}>
${translate("chatpage.cchange33")}
@ -1214,16 +1233,71 @@ class ChatPage extends LitElement {
}
}
connectedCallback() {
async connectedCallback() {
super.connectedCallback();
this.webWorker = new WebWorker();
this.webWorkerImage = new WebWorkerImage();
await this.getUpdateCompleteTextEditor();
const elementChatId = this.shadowRoot.getElementById('_chatEditorDOM').shadowRoot.getElementById('_chatEditorDOM')
const elementChatImageId = this.shadowRoot.getElementById('chatTextCaption').shadowRoot.getElementById('newChat')
console.log({elementChatId, elementChatImageId })
this.editor = new Editor({
element: elementChatId,
extensions: [
StarterKit,
Underline,
Placeholder.configure({
placeholder: 'Write something …',
}),
Extension.create({
addKeyboardShortcuts:()=> {
return {
'Enter': ()=> {
const chatTextEditor = this.shadowRoot.getElementById('_chatEditorDOM')
chatTextEditor.sendMessageFunc({
})
return true
}
}
}})
]
})
this.editorImage = new Editor({
element: elementChatImageId,
extensions: [
StarterKit,
Underline,
Placeholder.configure({
placeholder: 'Write something …',
}),
Extension.create({
addKeyboardShortcuts:()=> {
return {
'Enter':()=> {
const chatTextEditor = this.shadowRoot.getElementById('chatTextCaption')
chatTextEditor.sendMessageFunc({
type: 'image',
imageFile: this.imageFile,
})
return true
}
}
}})
]
})
}
disconnectedCallback() {
super.disconnectedCallback();
console.log('disconnected')
this.webWorker.terminate();
this.webWorkerImage.terminate();
this.editor.destroy()
this.editorImage.destroy()
}
async userSearch() {
@ -1285,18 +1359,11 @@ class ChatPage extends LitElement {
this.lastMessageRefVisible = props;
}
setChatEditor(editor) {
this.chatEditor = editor;
}
setChatEditorNewChat(editor) {
this.chatEditorNewChat = editor;
}
insertImage(file) {
if (file.type.includes('image')) {
this.imageFile = file;
this.chatEditor.disable();
this.currentEditor = 'newChat'
return;
}
parentEpml.request('showSnackBar', get("chatpage.cchange28"));
@ -1304,12 +1371,11 @@ class ChatPage extends LitElement {
removeImage() {
this.imageFile = null;
this.chatEditor.enable();
this.resetChatEditor()
this.currentEditor = '_chatEditorDOM'
}
changeMsgInput(id) {
this.chatEditor.remove()
this.chatMessageInput = this.shadowRoot.getElementById(id);
this.initChatEditor();
}
@ -1451,6 +1517,7 @@ class ChatPage extends LitElement {
})
})
parentEpml.imReady();
await this.initUpdate()
}
@ -1472,7 +1539,6 @@ class ChatPage extends LitElement {
if (changedProperties && changedProperties.has('openForwardOpen')) {
if (this.openForwardOpen === true) {
this.chatEditor.disable();
}
}
@ -1532,7 +1598,6 @@ async getName (recipient) {
.getOldMessage=${this.getOldMessage}
.setRepliedToMessageObj=${(val) => this.setRepliedToMessageObj(val)}
.setEditedMessageObj=${(val) => this.setEditedMessageObj(val)}
.focusChatEditor=${() => this.focusChatEditor()}
.sendMessage=${(val) => this._sendMessage(val)}
.sendMessageForward=${(messageText, typeMessage, chatReference, isForward, forwardParams)=> this.sendMessage(messageText, typeMessage, chatReference, isForward, forwardParams)}
.showLastMessageRefScroller=${(val) => this.showLastMessageRefScroller(val)}
@ -1555,6 +1620,25 @@ async getName (recipient) {
return true;
}
async getUpdateCompleteTextEditor() {
await super.getUpdateComplete();
const marginElements = Array.from(this.shadowRoot.querySelectorAll('chat-text-editor'));
await Promise.all(marginElements.map(el => el.updateComplete));
const marginElements2 = Array.from(this.shadowRoot.querySelectorAll('wrapper-modal'));
await Promise.all(marginElements2.map(el => el.updateComplete));
return true;
}
updatePlaceholder(editor, text){
editor.extensionManager.extensions.forEach((extension) => {
if (extension.name === "placeholder") {
extension.options["placeholder"] = text
editor.commands.focus('end')
}
})
}
async getOldMessage(scrollElement) {
if (this.isReceipient) {
@ -1685,6 +1769,7 @@ async getName (recipient) {
// set replied to message in chat editor
setRepliedToMessageObj(messageObj) {
this.editor.commands.focus('end')
this.repliedToMessageObj = {...messageObj};
this.editedMessageObj = null;
this.requestUpdate();
@ -1693,6 +1778,7 @@ async getName (recipient) {
// set edited message in chat editor
setEditedMessageObj(messageObj) {
this.editor.commands.focus('end')
this.editedMessageObj = {...messageObj};
this.repliedToMessageObj = null;
this.requestUpdate();
@ -1701,7 +1787,6 @@ async getName (recipient) {
closeEditMessageContainer() {
this.editedMessageObj = null;
this.isEditMessageOpen = !this.isEditMessageOpen;
this.chatEditor.resetValue();
}
closeRepliedToContainer() {
@ -1709,9 +1794,7 @@ async getName (recipient) {
this.requestUpdate();
}
focusChatEditor() {
this.chatEditor.focus();
}
/**
* New Message Template implementation, takes in a message object.
@ -1990,7 +2073,16 @@ async getName (recipient) {
// Add to the messages... TODO: Save messages to localstorage and fetch from it to make it persistent...
}
async _sendMessage(outSideMsg) {
resetChatEditor(){
if(this.currentEditor === '_chatEditorDOM'){
this.editor.commands.setContent('')
}
if(this.currentEditor === 'newChat'){
this.editorImage.commands.setContent('')
}
}
async _sendMessage(outSideMsg, msg) {
if(this.isReceipient){
let hasPublicKey = true
if(!this._publicKey.hasPubKey){
@ -2032,12 +2124,7 @@ async getName (recipient) {
let typeMessage = 'regular';
let workerImage;
this.isLoading = true;
this.chatEditor.disable();
this.chatEditorNewChat.disable()
const messageText = this.chatEditor.mirror.value;
// Format and Sanitize Message
const sanitizedMessage = messageText.replace(/&nbsp;/gi, ' ').replace(/<br\s*[\/]?>/gi, '\n');
const trimmedMessage = sanitizedMessage.trim();
const trimmedMessage = msg
const getName = async (recipient)=> {
try {
@ -2121,10 +2208,7 @@ async getName (recipient) {
})
this.isDeletingImage = false
} catch (error) {
console.error(error)
this.isLoading = false;
this.chatEditor.enable();
this.chatEditorNewChat.enable()
return
}
typeMessage = 'edit';
@ -2157,8 +2241,6 @@ async getName (recipient) {
if (!userName) {
parentEpml.request('showSnackBar', get("chatpage.cchange27"));
this.isLoading = false;
this.chatEditor.enable();
this.chatEditorNewChat.enable()
return;
}
@ -2193,8 +2275,6 @@ async getName (recipient) {
parentEpml.request('showSnackBar', get("chatpage.cchange26"));
this.isLoading = false;
this.isUploadingImage = false;
this.chatEditor.enable();
this.chatEditorNewChat.enable();
return;
}
try {
@ -2210,21 +2290,17 @@ async getName (recipient) {
worker: workerImage
});
this.isUploadingImage = false;
this.imageFile = null;
this.removeImage()
} catch (error) {
console.error(error)
this.isLoading = false;
this.isUploadingImage = false;
this.chatEditor.enable();
this.chatEditorNewChat.enable();
return;
}
const messageTextWithImage = this.chatEditorNewChat.mirror.value;
// Format and Sanitize Message
const sanitizedMessageWithImage = messageTextWithImage.replace(/&nbsp;/gi, ' ').replace(/<br\s*[\/]?>/gi, '\n');
const trimmedMessageWithImage = sanitizedMessageWithImage.trim();
const messageObject = {
messageText: trimmedMessageWithImage,
messageText: trimmedMessage,
images: [{
service: "QCHAT_IMAGE",
name: userName,
@ -2232,7 +2308,7 @@ async getName (recipient) {
}],
isImageDeleted: false,
repliedTo: '',
version: 1
version: 2
};
const stringifyMessageObject = JSON.stringify(messageObject);
this.sendMessage(stringifyMessageObject, typeMessage);
@ -2287,8 +2363,7 @@ async getName (recipient) {
this.sendMessage(stringifyMessageObject, typeMessage, chatReference);
} else if (/^\s*$/.test(trimmedMessage)) {
this.isLoading = false;
this.chatEditor.enable();
this.chatEditorNewChat.enable();
}
else if (this.repliedToMessageObj) {
let chatReference = this.repliedToMessageObj.reference;
@ -2300,7 +2375,7 @@ async getName (recipient) {
messageText: trimmedMessage,
images: [''],
repliedTo: chatReference,
version: 1
version: 2
}
const stringifyMessageObject = JSON.stringify(messageObject);
this.sendMessage(stringifyMessageObject, typeMessage);
@ -2332,7 +2407,7 @@ async getName (recipient) {
messageText: trimmedMessage,
images: [''],
repliedTo: '',
version: 1
version: 2
}
const stringifyMessageObject = JSON.stringify(messageObject)
@ -2448,7 +2523,6 @@ async getName (recipient) {
const recipientAddress = this.forwardActiveChatHeadUrl.url.split('/')[1];
this.openForwardOpen = false;
this.chatEditor.enable();
if (isRecipient === true) {
if(!publicKey.hasPubKey){
let err4string = get("chatpage.cchange39");
@ -2532,8 +2606,7 @@ async getName (recipient) {
const getSendChatResponse = (response, isForward) => {
if (response === true) {
this.chatEditor.resetValue();
this.chatEditorNewChat.resetValue()
this.resetChatEditor()
if(isForward){
let successString = get("blockpage.bcchange15");
parentEpml.request('showSnackBar', `${successString}`);
@ -2546,8 +2619,6 @@ async getName (recipient) {
}
this.isLoading = false;
this.chatEditor.enable();
this.chatEditorNewChat.enable()
this.closeEditMessageContainer()
this.closeRepliedToContainer()
this.openForwardOpen = false

View File

@ -190,6 +190,10 @@ export const chatStyles = css`
text-overflow: ellipsis;
max-width: 300px;
}
.replied-message p {
margin: 0px;
padding: 0px;
}
.message {
color: var(--chat-bubble-msg-color);
@ -494,4 +498,128 @@ export const chatStyles = css`
cursor: pointer;
background-color: #03a8f475;
}
#messageContent p {
margin: 0px;
padding: 0px;
}
#messageContent > * + * {
margin-top: 0.75em;
outline: none;
}
#messageContent ul,
ol {
padding: 0 1rem;
}
#messageContent h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
#messageContent code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
#messageContent pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
}
#messageContent pre code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
#messageContent img {
width: 1.7em;
height: 1.5em;
margin: 0px;
}
#messageContent blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
#messageContent hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
margin: 2rem 0;
}
.replied-message p {
margin: 0px;
padding: 0px;
}
.replied-message > * + * {
margin-top: 0.75em;
outline: none;
}
.replied-message ul,
ol {
padding: 0 1rem;
}
.replied-message h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
.replied-message code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
.replied-message pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
}
.replied-message pre code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
.replied-message img {
width: 1.7em;
height: 1.5em;
margin: 0px;
}
.replied-message blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
.replied-message hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
margin: 2rem 0;
}
`

View File

@ -15,6 +15,9 @@ import '@material/mwc-dialog';
import '@material/mwc-icon';
import { EmojiPicker } from 'emoji-picker-js';
import { cropAddress } from "../../utils/cropAddress";
import { generateHTML } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline';
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
let toggledMessage = {}
@ -28,7 +31,6 @@ class ChatScroller extends LitElement {
hideMessages: { type: Array },
setRepliedToMessageObj: {attribute: false},
setEditedMessageObj: {attribute: false},
focusChatEditor: {attribute: false},
sendMessage: {attribute: false},
sendMessageForward: {attribute: false},
showLastMessageRefScroller: { type: Function },
@ -54,6 +56,7 @@ class ChatScroller extends LitElement {
render() {
console.log('this.messages', this.messages)
let formattedMessages = this.messages.reduce((messageArray, message, index) => {
const lastGroupedMessage = messageArray[messageArray.length - 1];
let timestamp;
@ -108,7 +111,6 @@ class ChatScroller extends LitElement {
.hideMessages=${this.hideMessages}
.setRepliedToMessageObj=${this.setRepliedToMessageObj}
.setEditedMessageObj=${this.setEditedMessageObj}
.focusChatEditor=${this.focusChatEditor}
.sendMessage=${this.sendMessage}
.sendMessageForward=${this.sendMessageForward}
?isFirstMessage=${indexMessage === 0}
@ -231,7 +233,6 @@ class MessageTemplate extends LitElement {
showBlockAddressIcon: { type: Boolean },
setRepliedToMessageObj: { attribute: false },
setEditedMessageObj: { attribute: false },
focusChatEditor: { attribute: false },
sendMessage: { attribute: false },
sendMessageForward: { attribute: false },
openDialogImage: { attribute: false },
@ -294,6 +295,7 @@ class MessageTemplate extends LitElement {
render() {
const hidemsg = this.hideMessages;
let message = "";
let messageVersion2 = ""
let reactions = [];
let repliedToData = null;
let image = null;
@ -302,6 +304,14 @@ class MessageTemplate extends LitElement {
let isForwarded = false
try {
const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage);
if(parsedMessageObj.version.toString() === '2'){
messageVersion2 = generateHTML(parsedMessageObj.messageText, [
StarterKit,
Underline
// other extensions …
])
}
message = parsedMessageObj.messageText;
repliedToData = this.messageObj.repliedToData;
isImageDeleted = parsedMessageObj.isImageDeleted;
@ -466,7 +476,12 @@ class MessageTemplate extends LitElement {
${repliedToData.senderName ?? cropAddress(repliedToData.sender)}
</p>
<p class="replied-message">
${repliedToData.decodedMessage.messageText}
${unsafeHTML(generateHTML(repliedToData.decodedMessage.messageText, [
StarterKit,
Underline
// other extensions …
]))}
<!-- ${repliedToData.decodedMessage.messageText} -->
</p>
</div>
`}
@ -501,7 +516,8 @@ class MessageTemplate extends LitElement {
id="messageContent"
class="message"
style=${(image && replacedMessage !== "") &&"margin-top: 15px;"}>
${unsafeHTML(this.emojiPicker.parse(replacedMessage))}
${unsafeHTML(messageVersion2)}
<div class="${((this.isFirstMessage === false &&
this.isSingleMessageInGroup === true &&
this.isLastMessageInGroup === true) ||
@ -527,7 +543,6 @@ class MessageTemplate extends LitElement {
.originalMessage=${{...this.messageObj, message}}
.setRepliedToMessageObj=${this.setRepliedToMessageObj}
.setEditedMessageObj=${this.setEditedMessageObj}
.focusChatEditor=${this.focusChatEditor}
.myAddress=${this.myAddress}
@blur=${() => this.showBlockIconFunc(false)}
.sendMessage=${this.sendMessage}
@ -634,7 +649,6 @@ class ChatMenu extends LitElement {
originalMessage: { type: Object },
setRepliedToMessageObj: {attribute: false},
setEditedMessageObj: {attribute: false},
focusChatEditor: {attribute: false},
myAddress: { type: Object },
emojiPicker: { attribute: false },
sendMessage: { attribute: false },
@ -767,7 +781,6 @@ class ChatMenu extends LitElement {
return
}
this.setRepliedToMessageObj(this.originalMessage);
this.focusChatEditor();
}}">
<vaadin-icon icon="vaadin:reply" slot="icon"></vaadin-icon>
</div>
@ -783,7 +796,6 @@ class ChatMenu extends LitElement {
return
}
this.setEditedMessageObj(this.originalMessage);
this.focusChatEditor();
}}>
<vaadin-icon icon="vaadin:pencil" slot="icon"></vaadin-icon>
</div>

View File

@ -0,0 +1,828 @@
import { LitElement, html, css } from "lit";
import { get } from 'lit-translate';
import { escape, unescape } from 'html-escaper';
import { EmojiPicker } from 'emoji-picker-js';
import { inputKeyCodes } from '../../utils/keyCodes.js';
import { Epml } from '../../../epml.js';
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent });
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 },
chatMessageSize: { type: Number },
isEditMessageOpen: { type: Boolean },
theme: {
type: String,
reflect: true
}
}
}
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;
}
.chatbar-caption {
border-bottom: 2px solid var(--mdc-theme-primary);
}
.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: var(--paperclip-icon);
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;
}
.checkmark-icon {
width: 30px;
color: var(--mdc-theme-primary);
margin-bottom: 6px;
}
.checkmark-icon:hover {
cursor: pointer;
}
`
}
constructor() {
super()
this.isLoadingMessages = true
this.isLoading = false
this.getMessageSize = this.getMessageSize.bind(this)
this.calculateIFrameHeight = this.calculateIFrameHeight.bind(this)
this.resetIFrameHeight = this.resetIFrameHeight.bind(this)
this.addGlobalEventListener = this.addGlobalEventListener.bind(this)
this.sendMessageFunc = this.sendMessageFunc.bind(this)
this.removeGlobalEventListener = this.removeGlobalEventListener.bind(this)
this.initialChat = this.initialChat.bind(this)
this.iframeHeight = 42
this.chatMessageSize = 0
this.userName = window.parent.reduxStore.getState().app.accountInfo.names[0]
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
}
render() {
let scrollHeightBool = false;
try {
if (this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 && this.shadowRoot.querySelector(".chat-editor").contentDocument.body.querySelector("#chatbarId").innerHTML.trim() !== "") {
scrollHeightBool = true;
}
} catch (error) {
scrollHeightBool = false;
}
return html`
<div
class=${["chatbar-container", (this.iframeId === "newChat" || this.iframeId === "privateMessage") ? "chatbar-caption" : ""].join(" ")}
style="${scrollHeightBool ? 'align-items: flex-end' : "align-items: center"}">
<div
style=${this.iframeId === "privateMessage" ? "display: none" : "display: block"}
class="file-picker-container"
@click=${(e) => {
this.preventUserSendingImage(e)
}}>
<vaadin-icon
class="paperclip-icon"
icon="vaadin:paperclip"
slot="icon"
>
</vaadin-icon>
<div class="file-picker-input-container">
<input
@change="${e => {
this.insertImage(e.target.files[0]);
const filePickerInput = this.shadowRoot.getElementById('file-picker')
if(filePickerInput){
filePickerInput.value = ""
}
}
}"
id="file-picker"
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 style=${(this.iframeId === "newChat" && this.iframeHeight > 42) && "height: 100%;"} 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.sendMessageFunc();
}}
>
</vaadin-icon>
` :
html`
<paper-spinner-lite active></paper-spinner-lite>
`}
</div>
`
) :
html`
<div
style="${scrollHeightBool
? 'margin-bottom: 5px;'
: "margin-bottom: 0;"}
${this.iframeId === 'newChat'
? 'display: none;'
: 'display: flex;'}">
${this.isLoading === false ? html`
<img
src="/img/qchat-send-message-icon.svg"
alt="send-icon"
class="send-icon"
@click=${() => {
this.sendMessageFunc();
}}
/>
` :
html`
<paper-spinner-lite active></paper-spinner-lite>
`}
</div>
`
}
</div>
${this.chatMessageSize >= 750 ?
html`
<div class="message-size-container" style=${this.imageFile && "margin-top: 10px;"}>
<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>
`
}
preventUserSendingImage(e) {
if (!this.userName) {
e.preventDefault();
parentEpml.request('showSnackBar', get("chatpage.cchange27"));
};
}
initialChat(e) {
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('&nbsp;');
} 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() {
if (this.hasGlobalEvents) {
this.addGlobalEventListener();
}
window.addEventListener('storage', () => {
const checkTheme = localStorage.getItem('qortalTheme');
const chatbar = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId');
if (checkTheme === 'dark') {
this.theme = 'dark';
chatbar.style.cssText = "color:#ffffff;"
} else {
this.theme = 'light';
chatbar.style.cssText = "color:#080808;"
}
})
this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button');
this.mirrorChatInput = this.shadowRoot.getElementById('messageBox');
this.chatMessageInput = this.shadowRoot.getElementById(this.iframeId);
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 => {
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) {
if (changedProperties && changedProperties.has('editedMessageObj')) {
if (this.editedMessageObj) {
this.chatEditor.insertText(this.editedMessageObj.message);
this.getMessageSize(this.editedMessageObj.message);
} else {
this.chatEditor.insertText("");
this.chatMessageSize = 0;
}
}
if (changedProperties && changedProperties.has('placeholder')) {
const captionEditor = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId');
captionEditor.setAttribute('data-placeholder', this.placeholder);
}
if (changedProperties && changedProperties.has("imageFile")) {
this.chatMessageInput = "newChat";
}
}
shouldUpdate(changedProperties) {
// Only update element if prop1 changed.
if(changedProperties.has('setChatEditor') && changedProperties.size === 1) return false
return true
}
sendMessageFunc(props) {
if (this.chatMessageSize > 1000 ) {
parentEpml.request('showSnackBar', get("chatpage.cchange29"));
return;
};
this.chatMessageSize = 0;
this.chatEditor.updateMirror();
this._sendMessage(props);
}
getMessageSize(message){
try {
const messageText = message;
// Format and Sanitize Message
const sanitizedMessage = messageText.replace(/&nbsp;/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 if(this.imageFile && this.iframeId === 'newChat') {
messageObject = {
messageText: trimmedMessage,
images: [{
service: "QCHAT_IMAGE",
name: '123456789123456789123456789',
identifier: '123456'
}],
repliedTo: '',
version: 1
};
} 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('chatbarId').scrollHeight;
this.iframeHeight = editorTest + 20;
}, 50)
}
resetIFrameHeight(height) {
this.iframeHeight = 42;
}
initChatEditor() {
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.resetIFrameHeight()
};
ChatEditor.prototype.styles = function () {
const editor = this;
editor.styles = document.createElement('style');
editor.styles.setAttribute('type', 'text/css');
editor.styles.innerText = `
html {
cursor: text;
}
.chatbar-body {
display: flex;
align-items: center;
}
.chatbar-body::-webkit-scrollbar-track {
background-color: whitesmoke;
border-radius: 7px;
}
.chatbar-body::-webkit-scrollbar {
width: 6px;
border-radius: 7px;
background-color: whitesmoke;
}
.chatbar-body::-webkit-scrollbar-thumb {
background-color: rgb(180, 176, 176);
border-radius: 7px;
transition: all 0.3s ease-in-out;
}
.chatbar-body::-webkit-scrollbar-thumb:hover {
background-color: rgb(148, 146, 146);
cursor: pointer;
}
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;
width: 100%;
}
div[contentEditable=true]:empty:before {
content: attr(data-placeholder);
display: block;
text-overflow: ellipsis;
overflow: hidden;
user-select: none;
white-space: nowrap;
opacity: 0.7;
}
div[contentEditable=false]{
background: rgba(0,0,0,0.1);
width: 100%;
}
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);
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);
})
.then(() => {
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.querySelector("#chatbarId").innerHTML);
})
.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') {
await new Promise((res, rej) => {
setTimeout(() => {
editorConfig.calculateIFrameHeight(editorConfig.editableElement.contentDocument.body.scrollHeight);
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.querySelector("#chatbarId").innerHTML);
}, 0);
res();
})
// Handle Enter
if (e.keyCode === 13 && !e.shiftKey) {
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);
editor.content.body.classList.add("chatbar-body");
let elemDiv = document.createElement('div');
elemDiv.setAttribute('contenteditable', 'true');
elemDiv.setAttribute('spellcheck', 'false');
elemDiv.setAttribute('data-placeholder', editorConfig.placeholder);
elemDiv.style.cssText = `width:100%; ${editorConfig.theme === "dark" ? "color:#ffffff;" : "color: #080808"}`;
elemDiv.id = 'chatbarId';
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.sendMessageFunc,
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,
theme: this.theme,
resetIFrameHeight: this.resetIFrameHeight
};
const newChat = new ChatEditor(editorConfig);
this.setChatEditor(newChat);
}
}
window.customElements.define("chat-text-editor", ChatTextEditor)

View File

@ -4,6 +4,10 @@ import { escape, unescape } from 'html-escaper';
import { EmojiPicker } from 'emoji-picker-js';
import { inputKeyCodes } from '../../utils/keyCodes.js';
import { Epml } from '../../../epml.js';
import { Editor } from '@tiptap/core'
import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline';
import Image from '@tiptap/extension-image'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent });
class ChatTextEditor extends LitElement {
@ -17,12 +21,12 @@ class ChatTextEditor extends LitElement {
insertImage: { attribute: false },
iframeHeight: { type: Number },
editedMessageObj: { type: Object },
chatEditor: { type: Object },
setChatEditor: { attribute: false },
iframeId: { type: String },
hasGlobalEvents: { type: Boolean },
chatMessageSize: { type: Number },
isEditMessageOpen: { type: Boolean },
editor: {type: Object},
theme: {
type: String,
reflect: true
@ -39,8 +43,8 @@ class ChatTextEditor extends LitElement {
justify-content: center;
align-items: center;
height: auto;
overflow-y: hidden;
width: 100%;
overflow-y: hidden;
}
.chatbar-container {
width: 100%;
@ -63,6 +67,7 @@ class ChatTextEditor extends LitElement {
cursor: pointer;
max-height: 40px;
color: var(--black);
margin-bottom: 5px;
}
.message-size-container {
@ -101,6 +106,7 @@ class ChatTextEditor extends LitElement {
position: relative;
height: 25px;
width: 25px;
margin-bottom: 10px;
}
.file-picker-input-container {
@ -141,6 +147,129 @@ class ChatTextEditor extends LitElement {
.checkmark-icon:hover {
cursor: pointer;
}
.element {
width: 100%;
max-height: 100%;
overflow: auto;
color: var(--black);
padding: 0px 10px;
}
.element::-webkit-scrollbar-track {
background-color: whitesmoke;
border-radius: 7px;
}
.element::-webkit-scrollbar {
width: 6px;
border-radius: 7px;
background-color: whitesmoke;
}
.element::-webkit-scrollbar-thumb {
background-color: rgb(180, 176, 176);
border-radius: 7px;
transition: all 0.3s ease-in-out;
}
.element::-webkit-scrollbar-thumb:hover {
background-color: rgb(148, 146, 146);
cursor: pointer;
}
.ProseMirror:focus {
outline: none;
}
.is-active {
background-color: var(--white)
}
.ProseMirror > * + * {
margin-top: 0.75em;
outline: none;
}
.ProseMirror ul,
ol {
padding: 0 1rem;
}
.ProseMirror h1,
h2,
h3,
h4,
h5,
h6 {
line-height: 1.1;
}
.ProseMirror code {
background-color: rgba(#616161, 0.1);
color: #616161;
}
.ProseMirror pre {
background: #0D0D0D;
color: #FFF;
font-family: 'JetBrainsMono', monospace;
padding: 0.75rem 1rem;
border-radius: 0.5rem;
}
.ProseMirror pre code {
color: inherit;
padding: 0;
background: none;
font-size: 0.8rem;
}
.ProseMirror img {
width: 1.7em;
height: 1.5em;
margin: 0px;
}
.ProseMirror blockquote {
padding-left: 1rem;
border-left: 2px solid rgba(#0D0D0D, 0.1);
}
.ProseMirror hr {
border: none;
border-top: 2px solid rgba(#0D0D0D, 0.1);
margin: 2rem 0;
}
.chatbar-button-single {
background: var(--white);
outline: none;
border: none;
color: var(--black);
padding: 4px 8px;
border-radius: 5px;
cursor: pointer;
margin-right: 2px;
filter: brightness(100%);
transition: all 0.2s;
}
.chatbar-button-single:hover {
filter: brightness(120%);
}
.chatbar-buttons {
visibility: hidden;
transition: all .2s;
}
.chatbar-container:hover .chatbar-buttons {
visibility: visible;
}
.ProseMirror p.is-editor-empty:first-child::before {
color: #adb5bd;
content: attr(data-placeholder);
float: left;
height: 0;
pointer-events: none;
}
`
}
@ -159,21 +288,68 @@ class ChatTextEditor extends LitElement {
this.chatMessageSize = 0
this.userName = window.parent.reduxStore.getState().app.accountInfo.names[0]
this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light'
this.editor = null
}
render() {
console.log('this.editor', this.editor)
let scrollHeightBool = false;
try {
if (this.chatMessageInput && this.chatMessageInput.contentDocument.body.scrollHeight > 60 && this.shadowRoot.querySelector(".chat-editor").contentDocument.body.querySelector("#chatbarId").innerHTML.trim() !== "") {
console.log('this.chatMessageInput', this.chatMessageInput)
if (this.chatMessageInput && this.chatMessageInput.scrollHeight > 60) {
scrollHeightBool = true;
}
} catch (error) {
scrollHeightBool = false;
}
return html`
<div
class=${["chatbar-container", (this.iframeId === "newChat" || this.iframeId === "privateMessage") ? "chatbar-caption" : ""].join(" ")}
style="${scrollHeightBool ? 'align-items: flex-end' : "align-items: center"}">
style="align-items: flex-end; position: relative">
<div
class=${["chatbar-container", "chatbar-buttons"].join(" ")}
style="align-items: center; position:absolute; top: -20px">
<button
@click=${() => this.editor.chain().focus().toggleBold().run()}
?disabled=${
this.editor &&
!this.editor.can()
.chain()
.focus()
.toggleBold()
.run()
}
class=${["chatbar-button-single",this.editor && this.editor.isActive('bold') ? 'is-active' : ''].join(" ")}
>
bold
</button>
<button
@click=${() => this.editor.chain().focus().toggleItalic().run()}
?disabled=${ this.editor &&
!this.editor.can()
.chain()
.focus()
.toggleItalic()
.run()
}
class=${["chatbar-button-single",this.editor && this.editor.isActive('italic') ? 'is-active' : ''].join(' ')}
>
italic
</button>
<button
@click=${() => this.editor.chain().focus().toggleCodeBlock().run()}
class=${["chatbar-button-single",this.editor && this.editor.isActive('codeBlock') ? 'is-active' : ''].join(' ')}
>
code block
</button>
<button
@click=${() => this.editor.chain().focus().toggleUnderline().run()}
class=${["chatbar-button-single", this.editor && this.editor.isActive('underline') ? 'is-active' : ''].join(' ')}
>
underline
</button>
</div>
<div
style=${this.iframeId === "privateMessage" ? "display: none" : "display: block"}
class="file-picker-container"
@ -201,13 +377,14 @@ class ChatTextEditor extends LitElement {
</div>
</div>
<textarea style="color: var(--black);" tabindex='1' ?autofocus=${true} ?disabled=${this.isLoading || this.isLoadingMessages} id="messageBox" rows="1"></textarea>
<iframe style=${(this.iframeId === "newChat" && this.iframeHeight > 42) && "height: 100%;"} id=${this.iframeId} class="chat-editor" tabindex="-1" height=${this.iframeHeight}></iframe>
<!-- <iframe style=${(this.iframeId === "newChat" && this.iframeHeight > 42) && "height: 100%;"} id=${this.iframeId} class="chat-editor" tabindex="-1" height=${this.iframeHeight}></iframe> -->
<div id=${this.iframeId} class="element"></div>
<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>
<div style="margin-bottom: 10px">
${this.isLoading === false ? html`
<vaadin-icon
class="checkmark-icon"
@ -227,9 +404,7 @@ class ChatTextEditor extends LitElement {
) :
html`
<div
style="${scrollHeightBool
? 'margin-bottom: 5px;'
: "margin-bottom: 0;"}
style="margin-bottom: 10px;
${this.iframeId === 'newChat'
? 'display: none;'
: 'display: flex;'}">
@ -271,17 +446,17 @@ class ChatTextEditor extends LitElement {
}
initialChat(e) {
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('&nbsp;');
} else if (inputKeyCodes.includes(e.keyCode)) {
this.chatEditor.insertText(e.key);
return this.chatEditor.focus();
} else {
return this.chatEditor.focus();
}
}
// 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('&nbsp;');
// } else if (inputKeyCodes.includes(e.keyCode)) {
// this.chatEditor.insertText(e.key);
// return this.chatEditor.focus();
// } else {
// return this.chatEditor.focus();
// }
// }
}
addGlobalEventListener(){
@ -294,24 +469,38 @@ class ChatTextEditor extends LitElement {
async firstUpdated() {
if (this.hasGlobalEvents) {
this.addGlobalEventListener();
// this.addGlobalEventListener();
}
Image.configure({
inline: true,
})
// this.editor = new Editor({
// element: this.shadowRoot.querySelector('.element'),
// extensions: [
// StarterKit,
// Underline,
// Image
// ],
// content: '<p>Hello World!</p>',
// })
window.addEventListener('storage', () => {
const checkTheme = localStorage.getItem('qortalTheme');
const chatbar = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId');
const chatbar = this.shadowRoot.querySelector('.element')
if (checkTheme === 'dark') {
this.theme = 'dark';
chatbar.style.cssText = "color:#ffffff;"
} else {
this.theme = 'light';
chatbar.style.cssText = "color:#080808;"
}
})
this.emojiPickerHandler = this.shadowRoot.querySelector('.emoji-button');
this.mirrorChatInput = this.shadowRoot.getElementById('messageBox');
this.chatMessageInput = this.shadowRoot.getElementById(this.iframeId);
this.chatMessageInput = this.shadowRoot.querySelector('.element')
this.emojiPicker = new EmojiPicker({
style: "twemoji",
@ -327,29 +516,32 @@ class ChatTextEditor extends LitElement {
this.emojiPicker.on('emoji', selection => {
const emojiHtmlString = `<img class="emoji" draggable="false" alt="${selection.emoji}" src="${selection.url}">`;
this.chatEditor.insertEmoji(emojiHtmlString);
console.log('hello insert 6', selection)
this.editor.commands.insertContent(selection.emoji, {
parseOptions: {
preserveWhitespace: false
}
})
});
this.emojiPickerHandler.addEventListener('click', () => this.emojiPicker.togglePicker(this.emojiPickerHandler));
await this.updateComplete;
this.initChatEditor();
// this.initChatEditor();
}
async updated(changedProperties) {
if (changedProperties && changedProperties.has('editedMessageObj')) {
if (this.editedMessageObj) {
this.chatEditor.insertText(this.editedMessageObj.message);
this.editor.commands.setContent(this.editedMessageObj.message)
this.getMessageSize(this.editedMessageObj.message);
} else {
this.chatEditor.insertText("");
this.chatMessageSize = 0;
}
}
if (changedProperties && changedProperties.has('placeholder')) {
const captionEditor = this.shadowRoot.getElementById(this.iframeId).contentWindow.document.getElementById('chatbarId');
captionEditor.setAttribute('data-placeholder', this.placeholder);
this.updatePlaceholder(this.editor, this.placeholder )
}
if (changedProperties && changedProperties.has("imageFile")) {
@ -364,13 +556,12 @@ class ChatTextEditor extends LitElement {
}
sendMessageFunc(props) {
if (this.chatMessageSize > 1000 ) {
parentEpml.request('showSnackBar', get("chatpage.cchange29"));
return;
};
this.chatMessageSize = 0;
this.chatEditor.updateMirror();
this._sendMessage(props);
// if (this.chatMessageSize > 1000 ) {
// parentEpml.request('showSnackBar', get("chatpage.cchange29"));
// return;
// };
// this.chatMessageSize = 0;
this._sendMessage(props, this.editor.getJSON());
}
getMessageSize(message){
@ -442,387 +633,7 @@ class ChatTextEditor extends LitElement {
resetIFrameHeight(height) {
this.iframeHeight = 42;
}
initChatEditor() {
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.resetIFrameHeight()
};
ChatEditor.prototype.styles = function () {
const editor = this;
editor.styles = document.createElement('style');
editor.styles.setAttribute('type', 'text/css');
editor.styles.innerText = `
html {
cursor: text;
}
.chatbar-body {
display: flex;
align-items: center;
}
.chatbar-body::-webkit-scrollbar-track {
background-color: whitesmoke;
border-radius: 7px;
}
.chatbar-body::-webkit-scrollbar {
width: 6px;
border-radius: 7px;
background-color: whitesmoke;
}
.chatbar-body::-webkit-scrollbar-thumb {
background-color: rgb(180, 176, 176);
border-radius: 7px;
transition: all 0.3s ease-in-out;
}
.chatbar-body::-webkit-scrollbar-thumb:hover {
background-color: rgb(148, 146, 146);
cursor: pointer;
}
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;
width: 100%;
}
div[contentEditable=true]:empty:before {
content: attr(data-placeholder);
display: block;
text-overflow: ellipsis;
overflow: hidden;
user-select: none;
white-space: nowrap;
opacity: 0.7;
}
div[contentEditable=false]{
background: rgba(0,0,0,0.1);
width: 100%;
}
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);
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);
})
.then(() => {
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.querySelector("#chatbarId").innerHTML);
})
.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') {
await new Promise((res, rej) => {
setTimeout(() => {
editorConfig.calculateIFrameHeight(editorConfig.editableElement.contentDocument.body.scrollHeight);
editorConfig.getMessageSize(editorConfig.editableElement.contentDocument.body.querySelector("#chatbarId").innerHTML);
}, 0);
res();
})
// Handle Enter
if (e.keyCode === 13 && !e.shiftKey) {
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);
editor.content.body.classList.add("chatbar-body");
let elemDiv = document.createElement('div');
elemDiv.setAttribute('contenteditable', 'true');
elemDiv.setAttribute('spellcheck', 'false');
elemDiv.setAttribute('data-placeholder', editorConfig.placeholder);
elemDiv.style.cssText = `width:100%; ${editorConfig.theme === "dark" ? "color:#ffffff;" : "color: #080808"}`;
elemDiv.id = 'chatbarId';
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.sendMessageFunc,
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,
theme: this.theme,
resetIFrameHeight: this.resetIFrameHeight
};
const newChat = new ChatEditor(editorConfig);
this.setChatEditor(newChat);
}
}
window.customElements.define("chat-text-editor", ChatTextEditor)

View File

@ -22,6 +22,10 @@ import '@material/mwc-dialog'
import '@material/mwc-icon'
import '@material/mwc-snackbar'
import '@vaadin/grid'
import StarterKit from '@tiptap/starter-kit'
import Underline from '@tiptap/extension-underline';
import Placeholder from '@tiptap/extension-placeholder'
import { Editor, Extension } from '@tiptap/core'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
@ -47,7 +51,8 @@ class Chat extends LitElement {
openPrivateMessage: { type: Boolean },
userFound: { type: Array},
userFoundModalOpen: { type: Boolean },
userSelected: { type: Object }
userSelected: { type: Object },
editor: {type: Object}
}
}
@ -94,6 +99,66 @@ class Chat extends LitElement {
this.activeChatHeadUrl = url
}
resetChatEditor(){
this.editor.commands.setContent('')
}
async getUpdateCompleteTextEditor() {
await super.getUpdateComplete();
const marginElements = Array.from(this.shadowRoot.querySelectorAll('chat-text-editor'));
await Promise.all(marginElements.map(el => el.updateComplete));
const marginElements2 = Array.from(this.shadowRoot.querySelectorAll('wrapper-modal'));
await Promise.all(marginElements2.map(el => el.updateComplete));
return true;
}
async connectedCallback() {
super.connectedCallback();
await this.getUpdateCompleteTextEditor();
const elementChatId = this.shadowRoot.getElementById('messageBox').shadowRoot.getElementById('privateMessage')
console.log({elementChatId})
this.editor = new Editor({
element: elementChatId,
extensions: [
StarterKit,
Underline,
Placeholder.configure({
placeholder: 'Write something …',
}),
Extension.create({
addKeyboardShortcuts:()=> {
return {
'Enter': ()=> {
const chatTextEditor = this.shadowRoot.getElementById('messageBox')
chatTextEditor.sendMessageFunc({
})
return true
}
}
}})
]
})
}
disconnectedCallback() {
super.disconnectedCallback();
this.editor.destroy()
}
updatePlaceholder(editor, text){
editor.extensionManager.extensions.forEach((extension) => {
if (extension.name === "placeholder") {
extension.options["placeholder"] = text
editor.commands.focus('end')
}
})
}
render() {
return html`
<div class="container clearfix">
@ -127,13 +192,13 @@ class Chat extends LitElement {
<!-- Start Chatting Dialog -->
<wrapper-modal
.onClickFunc=${() => {
this.chatEditor.resetValue();
this.resetChatEditor();
this.openPrivateMessage = false;
this.shadowRoot.getElementById('sendTo').value = "";
this.userFoundModalOpen = false;
this.userFound = [];
} }
style=${this.openPrivateMessage ? "display: block" : "display: none"}>
style=${this.openPrivateMessage ? "visibility:visible;z-index:50" : "visibility: hidden;z-index:-100;position: relative"}>
<div style=${"position: relative"}>
<div class="dialog-container">
<div class="dialog-header" style="text-align: center">
@ -177,21 +242,21 @@ class Chat extends LitElement {
iframeId="privateMessage"
?hasGlobalEvents=${false}
placeholder="${translate("chatpage.cchange8")}"
.setChatEditor=${(editor)=> this.setChatEditor(editor)}
.chatEditor=${this.chatEditor}
.imageFile=${this.imageFile}
._sendMessage=${this._sendMessage}
.insertImage=${this.insertImage}
?isLoading=${this.isLoading}
.isLoadingMessages=${false}
id="messageBox"
.editor=${this.editor}
.updatePlaceholder=${(editor, value)=> this.updatePlaceholder(editor, value)}
>
</chat-text-editor>
<div class="modal-button-row">
<button
class="modal-button-red"
@click=${() => {
this.chatEditor.resetValue();
this.resetChatEditor();
this.openPrivateMessage = false;
}}
?disabled="${this.isLoading}"
@ -201,8 +266,10 @@ class Chat extends LitElement {
<button
class="modal-button"
@click=${()=> {
this.chatEditor.updateMirror()
this._sendMessage()
const chatTextEditor = this.shadowRoot.getElementById('messageBox')
chatTextEditor.sendMessageFunc({
})
}}
?disabled="${this.isLoading}">
@ -439,26 +506,22 @@ class Chat extends LitElement {
}
}
setChatEditor(editor) {
this.chatEditor = editor;
}
async _sendMessage() {
async _sendMessage(outSideMsg, msg) {
this.isLoading = true;
this.chatEditor.disable();
const messageText = this.chatEditor.mirror.value;
// Format and Sanitize Message
const sanitizedMessage = messageText.replace(/&nbsp;/gi, ' ').replace(/<br\s*[\/]?>/gi, '\n');
const trimmedMessage = sanitizedMessage.trim();
// this.chatEditor.disable();
const trimmedMessage = msg
if (/^\s*$/.test(trimmedMessage)) {
this.isLoading = false;
this.chatEditor.enable();
// this.chatEditor.enable();
} else {
const messageObject = {
messageText: trimmedMessage,
images: [''],
repliedTo: '',
version: 1
version: 2
}
const stringifyMessageObject = JSON.stringify(messageObject)
this.sendMessage(stringifyMessageObject);
@ -510,7 +573,7 @@ class Chat extends LitElement {
_publicKey = false;
let err4string = get("chatpage.cchange19");
parentEpml.request('showSnackBar', `${err4string}`);
this.chatEditor.enable();
// this.chatEditor.enable();
this.isLoading = false;
} else if (addressPublicKey !== false) {
isEncrypted = 1;
@ -519,7 +582,7 @@ class Chat extends LitElement {
} else {
let err4string = get("chatpage.cchange39");
parentEpml.request('showSnackBar', `${err4string}`);
this.chatEditor.enable();
// this.chatEditor.enable();
this.isLoading = false;
}
};
@ -576,7 +639,7 @@ class Chat extends LitElement {
this.setActiveChatHeadUrl(`direct/${recipient}`);
this.shadowRoot.getElementById('sendTo').value = "";
this.openPrivateMessage = false;
this.chatEditor.resetValue();
this.resetChatEditor();
} else if (response.error) {
parentEpml.request('showSnackBar', response.message);
} else {
@ -585,7 +648,7 @@ class Chat extends LitElement {
}
this.isLoading = false;
this.chatEditor.enable();
// this.chatEditor.enable();
};
// Exec..
@ -596,7 +659,7 @@ class Chat extends LitElement {
insertImage(file) {
if (file.type.includes('image')) {
this.imageFile = file;
this.chatEditor.disable();
// this.chatEditor.disable();
return;
}
parentEpml.request('showSnackBar', get("chatpage.cchange28"));