diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 00000000..30bc1627 --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +/node_modules \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index a8b6f1a4..3737f840 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -3,6 +3,7 @@ "browser": true, "es2021": true }, + "plugins": ["lit", "wc"], "extends": ["eslint:recommended", "plugin:lit/recommended", "plugin:wc/recommended"], "parserOptions": { "ecmaVersion": 12, diff --git a/core/language/us.json b/core/language/us.json index dacd7b7c..76458f9f 100644 --- a/core/language/us.json +++ b/core/language/us.json @@ -200,6 +200,8 @@ "snack3": "Successfully added and saved custom node", "snack4": "Nodes successfully saved as", "snack5": "Nodes successfully imported", + "snack6": "Successfully removed custom node", + "snack7": "Successfully edited custom node", "exp1": "Export Private Master Key", "exp2": "Export Master Key", "exp3": "Export", @@ -519,7 +521,8 @@ "nchange44": "To the new name", "nchange45": "On pressing confirm, the name update request will be sent!", "nchange46": "Name Sale History", - "nchange47": "Name Update Successful!" + "nchange47": "Name Update Successful!", + "nchange48": "Warning! If you update your name, you will forfeit the resources associated with the original name. In other words, you will lose ownership of the content under the original name in the QDN. Proceed with caution!" }, "websitespage": { "schange1": "Browse Websites", @@ -829,7 +832,14 @@ "cchange80": "This image has been deleted", "cchange81": "This image type is not supported", "cchange82": "This attachment has been deleted", - "cchange90": "No messages" + "cchange90": "No messages", + "cchange91": "Sending...", + "cchange92": "Unread messages below", + "cchange93": "Image copied to clipboard", + "cchange94": "loaded", + "cchange95": "Only my resources", + "cchange96": "Open Group Management", + "cchange97": "Join group link copied to clipboard" }, "welcomepage": { "wcchange1": "Welcome to Q-Chat", @@ -1161,5 +1171,10 @@ "lot11": "There are no open lotteries!", "lot12": "There are no finished lotteries!", "lot13": "Players" + }, + "notifications": { + "notify1": "Confirming transaction", + "notify2": "Transaction confirmed", + "explanation": "Your transaction is getting confirmed. To track its progress, click on the bell icon." } } \ No newline at end of file diff --git a/core/src/components/app-view.js b/core/src/components/app-view.js index 08b9c019..ca5a6732 100644 --- a/core/src/components/app-view.js +++ b/core/src/components/app-view.js @@ -42,6 +42,8 @@ import '../functional-components/side-menu.js' import '../functional-components/side-menu-item.js' import './start-minting.js' import './notification-view/notification-bell.js' +import './notification-view/notification-bell-general.js' + const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) @@ -556,7 +558,10 @@ class AppView extends connect(store)(LitElement) { +
+ +
diff --git a/core/src/components/login-view/login-view.js b/core/src/components/login-view/login-view.js index 24bef336..bc044daa 100644 --- a/core/src/components/login-view/login-view.js +++ b/core/src/components/login-view/login-view.js @@ -15,7 +15,7 @@ import './login-section.js' import '../qort-theme-toggle.js' import settings from '../../functional-components/settings-page.js' -import { addAutoLoadImageChat, removeAutoLoadImageChat, addChatLastSeen, allowQAPPAutoAuth, removeQAPPAutoAuth, removeQAPPAutoLists, allowQAPPAutoLists, addTabInfo, setTabNotifications, setNewTab } from '../../redux/app/app-actions.js' +import { addAutoLoadImageChat, removeAutoLoadImageChat, addChatLastSeen, allowQAPPAutoAuth, removeQAPPAutoAuth, removeQAPPAutoLists, allowQAPPAutoLists, addTabInfo, setTabNotifications, setNewTab, setNewNotification, setSideEffectAction } from '../../redux/app/app-actions.js' window.reduxStore = store window.reduxAction = { @@ -28,7 +28,9 @@ window.reduxAction = { removeQAPPAutoLists: removeQAPPAutoLists, addTabInfo: addTabInfo, setTabNotifications: setTabNotifications, - setNewTab: setNewTab + setNewTab: setNewTab, + setNewNotification: setNewNotification, + setSideEffectAction: setSideEffectAction } const animationDuration = 0.7 // Seconds diff --git a/core/src/components/notification-view/notification-bell-general.js b/core/src/components/notification-view/notification-bell-general.js new file mode 100644 index 00000000..125c8095 --- /dev/null +++ b/core/src/components/notification-view/notification-bell-general.js @@ -0,0 +1,527 @@ +import { LitElement, html, css } from 'lit'; +import { connect } from 'pwa-helpers'; + +import '@vaadin/item'; +import '@vaadin/list-box'; +import '@polymer/paper-icon-button/paper-icon-button.js'; +import '@polymer/iron-icons/iron-icons.js'; +import { store } from '../../store.js'; +import { setNewNotification, setNewTab } from '../../redux/app/app-actions.js'; +import { routes } from '../../plugins/routes.js'; +import '@material/mwc-icon'; +import { translate, get } from 'lit-translate'; +import { repeat } from 'lit/directives/repeat.js'; + +import config from '../../notifications/config.js'; +import '../../../../plugins/plugins/core/components/TimeAgo.js'; +import './popover.js'; + + + + +class NotificationBellGeneral extends connect(store)(LitElement) { + static properties = { + notifications: { type: Array }, + showNotifications: { type: Boolean }, + notificationCount: { type: Boolean }, + theme: { type: String, reflect: true }, + notifications: { type: Array }, + currentNotification: { type: Object }, + }; + + constructor() { + super(); + this.notifications = []; + this.showNotifications = false; + this.notificationCount = false; + this.initialFetch = false; + this.theme = localStorage.getItem('qortalTheme') + ? localStorage.getItem('qortalTheme') + : 'light'; + this.currentNotification = null; + } + + firstUpdated() { + try { + let value = JSON.parse(localStorage.getItem('isFirstTimeUser')); + if (!value && value !== false) { + value = true; + } + this.isFirstTimeUser = value; + } catch (error) {} + } + + async stateChanged(state) { + if (state.app.newNotification) { + const newNotification = state.app.newNotification; + this.notifications = [newNotification, ...this.notifications]; + store.dispatch(setNewNotification(null)); + if (this.isFirstTimeUser) { + const target = this.shadowRoot.getElementById( + 'popover-notification' + ); + const popover = + this.shadowRoot.querySelector('popover-component'); + if (popover) { + popover.openPopover(target); + } + + localStorage.setItem('isFirstTimeUser', JSON.stringify(false)); + this.isFirstTimeUser = false; + } + } + } + + handleBlur() { + setTimeout(() => { + if (!this.shadowRoot.contains(document.activeElement)) { + this.showNotifications = false; + } + }, 0); + } + + changeStatus(signature, statusTx) { + const copyNotifications = [...this.notifications]; + const findNotification = this.notifications.findIndex( + (notification) => notification.reference.signature === signature + ); + if (findNotification !== -1) { + copyNotifications[findNotification] = { + ...copyNotifications[findNotification], + status: statusTx, + }; + this.notifications = copyNotifications; + + } + } + + render() { + const hasOngoing = this.notifications.find( + (notification) => notification.status !== 'confirmed' + ); + return html` +
+ +
this._toggleNotifications()} + > + ${hasOngoing + ? html` + notifications + ` + : html` + notifications + `} +
+ ${hasOngoing + ? html` + this._toggleNotifications()} + > + pending + + ` + : ''} + +
+
+ ${repeat( + this.notifications, + (notification) => notification.reference.signature, // key function + (notification) => html` + + this.changeStatus(val1, val2)} + status=${notification.status} + timestamp=${notification.timestamp} + type=${notification.type} + signature=${notification.reference + .signature} + > + ` + )} +
+
+
+ `; + } + + _toggleNotifications() { + if (this.notifications.length === 0) return; + this.showNotifications = !this.showNotifications; + if (this.showNotifications) { + requestAnimationFrame(() => { + this.shadowRoot.getElementById('notification-panel').focus(); + }); + } + } + + static styles = css` + .layout { + display: flex; + flex-direction: column; + align-items: center; + position: relative; + margin-right: 20px; + } + + .count { + position: absolute; + top: -5px; + right: -5px; + font-size: 12px; + background-color: red; + color: white; + border-radius: 50%; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + } + + .nocount { + display: none; + } + + .popover-panel { + position: absolute; + width: 200px; + padding: 10px; + background-color: var(--white); + border: 1px solid var(--black); + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + top: 40px; + max-height: 350px; + overflow: auto; + scrollbar-width: thin; + scrollbar-color: #6a6c75 #a1a1a1; + } + + .popover-panel::-webkit-scrollbar { + width: 11px; + } + + .popover-panel::-webkit-scrollbar-track { + background: #a1a1a1; + } + + .popover-panel::-webkit-scrollbar-thumb { + background-color: #6a6c75; + border-radius: 6px; + border: 3px solid #a1a1a1; + } + + .notifications-list { + display: flex; + flex-direction: column; + } + + .notification-item { + padding: 5px; + border-bottom: 1px solid; + display: flex; + justify-content: space-between; + cursor: pointer; + transition: 0.2s all; + } + + .notification-item:hover { + background: var(--nav-color-hover); + } + + p { + font-size: 14px; + color: var(--black); + margin: 0px; + padding: 0px; + } + `; +} + +customElements.define('notification-bell-general', NotificationBellGeneral); + +class NotificationItemTx extends connect(store)(LitElement) { + static properties = { + status: { type: String }, + type: { type: String }, + timestamp: { type: Number }, + signature: { type: String }, + changeStatus: { attribute: false }, + }; + + constructor() { + super(); + this.nodeUrl = this.getNodeUrl(); + this.myNode = this.getMyNode(); + } + + getNodeUrl() { + const myNode = + store.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + return nodeUrl; + } + getMyNode() { + const myNode = + store.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + return myNode; + } + + async getStatus() { + let interval = null; + let stop = false; + const getAnswer = async () => { + const getTx = async (minterAddr) => { + const url = `${this.nodeUrl}/transactions/signature/${this.signature}`; + const res = await fetch(url); + const data = await res.json(); + return data; + }; + + if (!stop) { + stop = true; + try { + const txTransaction = await getTx(); + if (!txTransaction.error && txTransaction.signature && txTransaction.blockHeight) { + clearInterval(interval); + this.changeStatus(this.signature, 'confirmed'); + } + } catch (error) {} + stop = false; + } + }; + interval = setInterval(getAnswer, 20000); + } + + firstUpdated() { + this.getStatus(); + } + + render() { + return html` +
{}}> +
+

+ ${translate('transpage.tchange1')} +

+
+
+

+ ${translate('walletpage.wchange35')}: ${this.type} +

+

+ ${translate('tubespage.schange28')}: + ${this.status === 'confirming' + ? translate('notifications.notify1') + : translate('notifications.notify2')} +

+ ${this.status !== 'confirmed' + ? html` +
+
Loading...
+
+ ` + : ''} +
+ + ${this.status === 'confirmed' + ? html` + done + ` + : ''} +
+
+
+ `; + } + + _toggleNotifications() { + if (this.notifications.length === 0) return; + this.showNotifications = !this.showNotifications; + } + + static styles = css` + .centered { + display: flex; + justify-content: center; + align-items: center; + } + .layout { + width: 100px; + display: flex; + flex-direction: column; + align-items: center; + position: relative; + } + + .count { + position: absolute; + top: -5px; + right: -5px; + font-size: 12px; + background-color: red; + color: white; + border-radius: 50%; + width: 16px; + height: 16px; + display: flex; + align-items: center; + justify-content: center; + } + + .nocount { + display: none; + } + + .popover-panel { + position: absolute; + width: 200px; + padding: 10px; + background-color: var(--white); + border: 1px solid var(--black); + border-radius: 4px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + top: 40px; + max-height: 350px; + overflow: auto; + scrollbar-width: thin; + scrollbar-color: #6a6c75 #a1a1a1; + } + + .popover-panel::-webkit-scrollbar { + width: 11px; + } + + .popover-panel::-webkit-scrollbar-track { + background: #a1a1a1; + } + + .popover-panel::-webkit-scrollbar-thumb { + background-color: #6a6c75; + border-radius: 6px; + border: 3px solid #a1a1a1; + } + + .notifications-list { + display: flex; + flex-direction: column; + } + + .notification-item { + padding: 5px; + border-bottom: 1px solid; + display: flex; + flex-direction: column; + cursor: default; + } + + .notification-item:hover { + background: var(--nav-color-hover); + } + + p { + font-size: 14px; + color: var(--black); + margin: 0px; + padding: 0px; + } + + .loader, + .loader:before, + .loader:after { + border-radius: 50%; + width: 10px; + height: 10px; + -webkit-animation-fill-mode: both; + animation-fill-mode: both; + -webkit-animation: load7 1.8s infinite ease-in-out; + animation: load7 1.8s infinite ease-in-out; + } + .loader { + color: var(--black); + font-size: 5px; + margin-bottom: 20px; + position: relative; + text-indent: -9999em; + -webkit-transform: translateZ(0); + -ms-transform: translateZ(0); + transform: translateZ(0); + -webkit-animation-delay: -0.16s; + animation-delay: -0.16s; + } + .loader:before, + .loader:after { + content: ''; + position: absolute; + top: 0; + } + .loader:before { + left: -3.5em; + -webkit-animation-delay: -0.32s; + animation-delay: -0.32s; + } + .loader:after { + left: 3.5em; + } + @-webkit-keyframes load7 { + 0%, + 80%, + 100% { + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + box-shadow: 0 2.5em 0 0; + } + } + @keyframes load7 { + 0%, + 80%, + 100% { + box-shadow: 0 2.5em 0 -1.3em; + } + 40% { + box-shadow: 0 2.5em 0 0; + } + } + `; +} + +customElements.define('notification-item-tx', NotificationItemTx); diff --git a/core/src/components/notification-view/notification-bell.js b/core/src/components/notification-view/notification-bell.js index bb3913c5..8a4b0aec 100644 --- a/core/src/components/notification-view/notification-bell.js +++ b/core/src/components/notification-view/notification-bell.js @@ -158,7 +158,7 @@ class NotificationBell extends connect(store)(LitElement) { url: `qdn/browser/index.html${query}`, id: 'q-mail-notification', myPlugObj: { - "url": "qapps", + "url": "myapp", "domain": "core", "page": `qdn/browser/index.html${query}`, "title": "Q-Mail", @@ -196,7 +196,7 @@ class NotificationBell extends connect(store)(LitElement) { url: `qdn/browser/index.html${query}`, id: 'q-mail-notification', myPlugObj: { - "url": "qapps", + "url": "myapp", "domain": "core", "page": `qdn/browser/index.html${query}`, "title": "Q-Mail", @@ -210,7 +210,6 @@ class NotificationBell extends connect(store)(LitElement) { static styles = css` .layout { - width: 100px; display: flex; flex-direction: column; align-items: center; @@ -220,7 +219,7 @@ class NotificationBell extends connect(store)(LitElement) { .count { position: absolute; top: 2px; - right: 32px; + right: 0px; font-size: 12px; background-color: red; color: white; diff --git a/core/src/components/notification-view/popover.js b/core/src/components/notification-view/popover.js new file mode 100644 index 00000000..43e85a94 --- /dev/null +++ b/core/src/components/notification-view/popover.js @@ -0,0 +1,74 @@ +// popover-component.js +import { LitElement, html, css } from 'lit'; +import { createPopper } from '@popperjs/core'; +import '@material/mwc-icon' + +export class PopoverComponent extends LitElement { + static styles = css` + :host { + display: none; + position: absolute; + background-color: var(--white); + border: 1px solid #ddd; + padding: 8px; + z-index: 10; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); + color: var(--black); + max-width: 250px; + } + + .close-icon { + cursor: pointer; + float: right; + margin-left: 10px; + color: var(--black) + } + `; + + static properties = { + for: { type: String, reflect: true }, + message: { type: String } + }; + + constructor() { + super(); + this.message = ''; + } + + firstUpdated() { + // We'll defer the popper attachment to the openPopover() method to ensure target availability + } + + attachToTarget(target) { + console.log({target}) + if (!this.popperInstance && target) { + this.popperInstance = createPopper(target, this, { + placement: 'bottom', + strategy: 'fixed' + }); + } + } + + openPopover(target) { + this.attachToTarget(target); + this.style.display = 'block'; + } + + closePopover() { + this.style.display = 'none'; + if (this.popperInstance) { + this.popperInstance.destroy(); + this.popperInstance = null; + } + this.requestUpdate(); + } + + render() { + return html` + close +
info ${this.message}
+ `; + } +} + +customElements.define('popover-component', PopoverComponent); diff --git a/core/src/components/show-plugin.js b/core/src/components/show-plugin.js index 00cd7882..c6ff4d34 100644 --- a/core/src/components/show-plugin.js +++ b/core/src/components/show-plugin.js @@ -97,13 +97,13 @@ class ShowPlugin extends connect(store)(LitElement) { .hideIframe { display: none; position: absolute; - zIndex: -10; + z-Index: -10; } .showIframe { - display: block; + display: flex; position: relative; - zIndex: 1; + z-Index: 1; } .tabs { @@ -135,7 +135,7 @@ class ShowPlugin extends connect(store)(LitElement) { min-width: 110px; max-width: 220px; overflow: hidden; - zIndex: 2; + z-index: 2; } .tabCard { @@ -171,7 +171,7 @@ class ShowPlugin extends connect(store)(LitElement) { border-left: 1px solid var(--black); border-right: 1px solid var(--black); border-bottom: 1px solid var(--white); - zIndex: 1; + z-index: 1; } .close { @@ -432,7 +432,9 @@ class ShowPlugin extends connect(store)(LitElement) {
${icon} @@ -441,10 +443,13 @@ class ShowPlugin extends connect(store)(LitElement) { ${count ? html` ${title} ${count} - {this.removeTab(index, tab.id)}}>close + { + event.stopPropagation(); this.removeTab(index, tab.id) + + }}>close ` : html` ${title} - {this.removeTab(index, tab.id)}}>close + {event.stopPropagation(); this.removeTab(index, tab.id)}}>close `}
@@ -460,6 +465,7 @@ class ShowPlugin extends connect(store)(LitElement) { id: this.uid.rnd() }) this.currentTab = lengthOfTabs + }} >+
@@ -655,7 +661,7 @@ class ShowPlugin extends connect(store)(LitElement) { this.createEpmlInstance(frame, newIndex) } - removeTab(index, tabA) { + removeTab(index, tabA) { const tabB = this.tabs.length - 1 const tabC = this.tabs[tabB].id @@ -667,10 +673,10 @@ class ShowPlugin extends connect(store)(LitElement) { let iconId = '' this.tabs = this.tabs.filter((tab, tIndex) => tIndex !== index) + this.currentTab = this.tabs.length - 1; const tabD = this.tabs.length - 1 const plugObj = this.tabs[tabD].url - theId = this.tabs[tabD].id tabId = 'tab-' + theId frameId = 'frame-' + theId @@ -819,7 +825,14 @@ class ShowPlugin extends connect(store)(LitElement) { if (state.app.newTab) { const newTab = state.app.newTab - if (!this.tabs.find((tab) => tab.id === newTab.id)) { + if(newTab.openExisting && this.tabs.find((tab)=> tab.url === newTab.url)){ + const findIndex = this.tabs.findIndex((tab) => tab.url === newTab.url) + if (findIndex !== -1) { + this.currentTab = findIndex + } + + store.dispatch(setNewTab(null)) + } else if (!this.tabs.find((tab) => tab.id === newTab.id)) { this.addTab(newTab) this.currentTab = this.tabs.length - 1 store.dispatch(setNewTab(null)) @@ -893,7 +906,7 @@ class NavBar extends connect(store)(LitElement) { flex-flow: column; align-items: center; padding: 20px; - height: 100vh; + height: calc(100vh - 120px); overflow-y: auto; } @@ -901,7 +914,7 @@ class NavBar extends connect(store)(LitElement) { display: flex; justify-content: space-between; align-items: center; - background-color: color: var(--white); + background-color: var(--white); padding: 10px 20px; max-width: 750px; width: 80%; @@ -2276,10 +2289,10 @@ class NavBar extends connect(store)(LitElement) { if (service === "APP") { this.changePage({ - "url": "qapp", + "url": "myapp", "domain": "core", "page": `qdn/browser/index.html${query}`, - "title": "Q-App", + "title": name || "Q-App", "icon": "vaadin:external-browser", "mwcicon": "open_in_browser", "menus": [], @@ -2287,10 +2300,10 @@ class NavBar extends connect(store)(LitElement) { }) } else if (service === "WEBSITE") { this.changePage({ - "url": "websites", + "url": "myapp", "domain": "core", "page": `qdn/browser/index.html${query}`, - "title": "Website", + "title": name || "Website", "icon": "vaadin:desktop", "mwcicon": "desktop_mac", "menus": [], diff --git a/core/src/functional-components/settings-page.js b/core/src/functional-components/settings-page.js index 31b3b330..76ed6811 100644 --- a/core/src/functional-components/settings-page.js +++ b/core/src/functional-components/settings-page.js @@ -1,419 +1,776 @@ -import { LitElement, html, css } from 'lit' -import { connect } from 'pwa-helpers' -import { store } from '../store.js' -import { doAddNode, doSetNode, doLoadNodeConfig } from '../redux/app/app-actions.js' -import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' -import snackbar from './snackbar.js' -import '../components/language-selector.js' -import '../custom-elements/frag-file-input.js' -import FileSaver from 'file-saver' +import { LitElement, html, css } from 'lit'; +import { connect } from 'pwa-helpers'; +import { store } from '../store.js'; +import { + doAddNode, + doSetNode, + doLoadNodeConfig, + doRemoveNode, + doEditNode, +} from '../redux/app/app-actions.js'; +import { + use, + get, + translate, + translateUnsafeHTML, + registerTranslateConfig, +} from 'lit-translate'; +import snackbar from './snackbar.js'; +import '../components/language-selector.js'; +import '../custom-elements/frag-file-input.js'; +import FileSaver from 'file-saver'; -import '@material/mwc-dialog' -import '@material/mwc-button' -import '@material/mwc-select' -import '@material/mwc-textfield' -import '@material/mwc-icon' -import '@material/mwc-list/mwc-list-item.js' +import '@material/mwc-dialog'; +import '@material/mwc-button'; +import '@material/mwc-select'; +import '@material/mwc-textfield'; +import '@material/mwc-icon'; +import '@material/mwc-list/mwc-list-item.js'; registerTranslateConfig({ - loader: lang => fetch(`/language/${lang}.json`).then(res => res.json()) -}) + loader: (lang) => fetch(`/language/${lang}.json`).then((res) => res.json()), +}); -const checkLanguage = localStorage.getItem('qortalLanguage') +const checkLanguage = localStorage.getItem('qortalLanguage'); if (checkLanguage === null || checkLanguage.length === 0) { - localStorage.setItem('qortalLanguage', 'us') - use('us') + localStorage.setItem('qortalLanguage', 'us'); + use('us'); } else { - use(checkLanguage) + use(checkLanguage); } -let settingsDialog +let settingsDialog; class SettingsPage extends connect(store)(LitElement) { - static get properties() { - return { - lastSelected: { type: Number }, - nodeConfig: { type: Object }, - theme: { type: String, reflect: true }, - nodeIndex: { type: Number } - } - } + static get properties() { + return { + lastSelected: { type: Number }, + nodeConfig: { type: Object }, + theme: { type: String, reflect: true }, + nodeIndex: { type: Number }, + isBeingEdited: { type: Boolean }, + dropdownOpen: { type: Boolean }, + }; + } - static get styles() { - return css` - * { - --mdc-theme-primary: rgb(3, 169, 244); - --mdc-theme-secondary: var(--mdc-theme-primary); - --mdc-dialog-content-ink-color: var(--black); - --mdc-theme-surface: var(--white); - --mdc-theme-text-primary-on-background: var(--black); - --mdc-dialog-min-width: 300px; - --mdc-dialog-max-width: 650px; - --mdc-dialog-max-height: 700px; + static get styles() { + return css` + * { + --mdc-theme-primary: rgb(3, 169, 244); + --mdc-theme-secondary: var(--mdc-theme-primary); + --mdc-dialog-content-ink-color: var(--black); + --mdc-theme-surface: var(--white); + --mdc-theme-text-primary-on-background: var(--black); + --mdc-dialog-min-width: 300px; + --mdc-dialog-max-width: 650px; + --mdc-dialog-max-height: 700px; + --mdc-list-item-text-width: 100%; + } + + #main { + width: 210px; + display: flex; + align-items: center; + } + + .globe { + color: var(--black); + --mdc-icon-size: 36px; + } + + span.name { + display: inline-block; + width: 150px; + font-weight: 600; + color: #03a9f4; + border: 1px solid transparent; + } + + .red { + --mdc-theme-primary: red; + } + + .buttonred { + color: #f44336; + } + + .buttongreen { + color: #03c851; + } + .buttonBlue { + color: #03a9f4; } + .floatleft { + float: left; + } - #main { - width: 210px; + .floatright { + float: right; + } + .list-parent { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + } + #customSelect { + position: relative; + border: 1px solid #ccc; + cursor: pointer; + background: var(--plugback); + } + + #customSelect .selected { + padding: 10px; + display: flex; + align-items: center; + justify-content: space-between; + } + + #customSelect ul { + position: absolute; + top: 100%; + left: 0; + list-style: none; + margin: 0; + padding: 0; + border: 1px solid #ccc; + display: none; + background: var(--plugback); + width: 100%; + box-sizing: border-box; + z-index: 10; + } + + #customSelect ul.open { + display: block; + } + + #customSelect ul li { + padding: 10px; + transition: 0.2s all; + } + + #customSelect ul li:hover { + background-color: var(--graylight); + } + .selected-left-side{ display: flex; align-items: center; } + `; + } - .globe { - color: var(--black); - --mdc-icon-size: 36px; - } + constructor() { + super(); + this.nodeConfig = {}; + this.nodeIndex = localStorage.getItem('mySelectedNode'); + this.theme = localStorage.getItem('qortalTheme') + ? localStorage.getItem('qortalTheme') + : 'light'; + this.isBeingEdited = false; + this.isBeingEditedIndex = null; + this.dropdownOpen = false; + } - span.name { - display: inline-block; - width: 150px; - font-weight: 600; - color: #03a9f4; - border: 1px solid transparent; - } + render() { + return html` + +
+

${translate('settings.settings')}

+
+
+
+
+
+
+
+ link + + ${ + this.selectedItem + ? html` +
+ + ${this.selectedItem + .name} + ${this.selectedItem + .protocol + + '://' + + this.selectedItem.domain + + ':' + + this.selectedItem + .port} +
+ ` + : 'Please select an option' + } +
+ expand_more +
+
    + ${this.nodeConfig.knownNodes.map( + (n, index) => html` +
  • +
    +
    + ${n.name} + ${n.protocol + + '://' + + n.domain + + ':' + + n.port} +
    +
    + edit + remove +
    +
    +
  • + ` + )} +
+
- .floatright { - float: right; - } - ` - } + - constructor() { - super() - this.nodeConfig = {} - this.nodeIndex = localStorage.getItem('mySelectedNode') - this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' - } +

+ ${translate('settings.nodehint')} +

+
+ add${translate('settings.addcustomnode')} +
+
+ remove${translate('settings.deletecustomnode')} +
+
+
+ ${this.renderExportNodesListButton()} +
+
+ ${this.renderImportNodesListButton()} +
+

+
+
+
+
+
+
+ language  +
+
+
+ + ${translate('general.close')} + +
- render() { - return html` - -
-

${translate("settings.settings")}

-
-
-
-
- - ${this.nodeConfig.knownNodes.map((n, index) => html` - - ${n.name} - ${n.protocol + '://' + n.domain + ':' + n.port} - - `)} - -

${translate("settings.nodehint")}

-
- add${translate("settings.addcustomnode")} -
-
- remove${translate("settings.deletecustomnode")} -
-
-
${this.renderExportNodesListButton()}
${this.renderImportNodesListButton()}
-

-
-
-

-
-
- language  -
-
-
- - ${translate("general.close")} - -
+ +
+

${translate('settings.addcustomnode')}

+
+
+
+ +
+ + http + https + +
+ + + + ${translate('general.close')} + + + ${translate('settings.addandsave')} + +
- -
-

${translate("settings.addcustomnode")}

-
-
-
- -
- - http - https - -
- - - - ${translate("general.close")} - - - ${translate("settings.addandsave")} - -
- - -
-

${translate("settings.import")}

-
-
-
-
- -

${translate("walletpage.wchange56")}

-
${translate("settings.warning")}
-
- - ${translate("general.close")} - -
- ` - } - - firstUpdated() { - const checkNode = localStorage.getItem('mySelectedNode') - if (checkNode === null || checkNode.length === 0) { - localStorage.setItem('mySelectedNode', 0) - } else { - } - } - - show() { - this.shadowRoot.getElementById('settingsDialog').show() - } - - close() { - this.shadowRoot.getElementById('settingsDialog').close() - } - - removeList() { - localStorage.removeItem("myQortalNodes") - - const obj1 = { - name: 'Local Node', - protocol: 'http', - domain: '127.0.0.1', - port: 12391, - enableManagement: true - } - - const obj2 = { - name: 'Local Testnet', - protocol: 'http', - domain: '127.0.0.1', - port: 62391, - enableManagement: true - } - - var renewNodes = []; - renewNodes.push(obj1,obj2) - localStorage.setItem('myQortalNodes', JSON.stringify(renewNodes)) - - let snack1string = get("settings.snack1") - snackbar.add({ - labelText: `${snack1string}`, - dismiss: true - }) - - localStorage.removeItem('mySelectedNode') - localStorage.setItem('mySelectedNode', 0) - - store.dispatch(doLoadNodeConfig()) - } - - nodeSelected(e) { - const selectedNodeIndex = this.shadowRoot.getElementById('nodeSelect').value - const selectedNode = this.nodeConfig.knownNodes[selectedNodeIndex] - const selectedNodeUrl = `${selectedNode.protocol + '://' + selectedNode.domain + ':' + selectedNode.port}` - - const index = parseInt(selectedNodeIndex) - if (isNaN(index)) return - - store.dispatch(doSetNode(selectedNodeIndex)) - - localStorage.removeItem('mySelectedNode') - localStorage.setItem('mySelectedNode', selectedNodeIndex) - - let snack2string = get("settings.snack2") - snackbar.add({ - labelText: `${snack2string} : ${selectedNodeUrl}`, - dismiss: true - }) - - this.shadowRoot.querySelector('#settingsDialog').close() - } - - addNode() { - const nameInput = this.shadowRoot.getElementById('nameInput').value - const protocolList = this.shadowRoot.getElementById('protocolList').value - const domainInput = this.shadowRoot.getElementById('domainInput').value - const portInput = this.shadowRoot.getElementById('portInput').value - - if (protocolList.length >= 4 && domainInput.length >= 3 && portInput.length >= 2) { - const nodeObject = { - name: nameInput, - protocol: protocolList, - domain: domainInput, - port: portInput, - enableManagement: true - } - - store.dispatch(doAddNode(nodeObject)) - - const haveNodes = JSON.parse(localStorage.getItem('myQortalNodes')) - - if (haveNodes === null || haveNodes.length === 0) { - - var savedNodes = []; - savedNodes.push(nodeObject); - localStorage.setItem('myQortalNodes', JSON.stringify(savedNodes)) - - let snack3string = get("settings.snack3") - snackbar.add({ - labelText: `${snack3string}`, - dismiss: true - }) - - this.shadowRoot.getElementById('nameInput').value = '' - this.shadowRoot.getElementById('protocolList').value = '' - this.shadowRoot.getElementById('domainInput').value = '' - this.shadowRoot.getElementById('portInput').value = '' - - this.shadowRoot.querySelector('#addNodeDialog').close() - - } else { - - var stored = JSON.parse(localStorage.getItem('myQortalNodes')); - stored.push(nodeObject); - localStorage.setItem('myQortalNodes', JSON.stringify(stored)); - - let snack3string = get("settings.snack3") - snackbar.add({ - labelText: `${snack3string}`, - dismiss: true - }) - - this.shadowRoot.getElementById('nameInput').value = '' - this.shadowRoot.getElementById('protocolList').value = '' - this.shadowRoot.getElementById('domainInput').value = '' - this.shadowRoot.getElementById('portInput').value = '' - - this.shadowRoot.querySelector('#addNodeDialog').close() - } - } - } - - openImportNodesDialog() { - this.shadowRoot.querySelector("#importQortalNodesListDialog").show() - } - - closeImportNodesDialog() { - this.shadowRoot.querySelector("#importQortalNodesListDialog").close() - } - - renderExportNodesListButton() { - return html` - - ` - } - - exportQortalNodesList() { - let nodelist = "" - const qortalNodesList = JSON.stringify(localStorage.getItem("myQortalNodes")) - const qortalNodesListSave = JSON.parse((qortalNodesList) || "[]") - const blob = new Blob([qortalNodesListSave], { type: 'text/plain;charset=utf-8' }) - nodelist = "qortal.nodes" - this.saveFileToDisk(blob, nodelist) - } - - async saveFileToDisk(blob, fileName) { - try { - const fileHandle = await self.showSaveFilePicker({ - suggestedName: fileName, - types: [{ - description: "File", - }] - }) - const writeFile = async (fileHandle, contents) => { - const writable = await fileHandle.createWritable() - await writable.write(contents) - await writable.close() - } - writeFile(fileHandle, blob).then(() => console.log("FILE SAVED")) - let snack4string = get("settings.snack4") - snackbar.add({ - labelText: `${snack4string} qortal.nodes`, - dismiss: true - }) - } catch (error) { - if (error.name === 'AbortError') { - return - } - FileSaver.saveAs(blob, fileName) - } - } - - renderImportNodesListButton() { - return html` - - ` - } - - async importQortalNodesList(file) { - localStorage.removeItem("myQortalNodes") - const newItems = JSON.parse((file) || "[]") - localStorage.setItem("myQortalNodes", JSON.stringify(newItems)) - this.shadowRoot.querySelector('#importQortalNodesListDialog').close() - - let snack5string = get("settings.snack5") - snackbar.add({ - labelText: `${snack5string}`, - dismiss: true - }) - - localStorage.removeItem('mySelectedNode') - localStorage.setItem('mySelectedNode', 0) - - store.dispatch(doLoadNodeConfig()) - } - - stateChanged(state) { - this.config = state.config - this.nodeConfig = state.app.nodeConfig + +
+

${translate('settings.import')}

+
+
+
+
+ +

+ ${translate('walletpage.wchange56')} +

+
+ ${translate('settings.warning')} +
+
+ + ${translate('general.close')} + +
+ `; + } -window.customElements.define('settings-page', SettingsPage) + firstUpdated() { + const checkNode = localStorage.getItem('mySelectedNode'); + if (checkNode === null || checkNode.length === 0) { + localStorage.setItem('mySelectedNode', 0); + } else { + } + } -const settings = document.createElement('settings-page') -settingsDialog = document.body.appendChild(settings) + toggleDropdown() { + this.dropdownOpen = !this.dropdownOpen; + } -export default settingsDialog + handleBlur(event) { + if ( + !this.shadowRoot + .querySelector('#customSelect') + .contains(event.relatedTarget) + ) { + this.dropdownOpen = false; + } + } + focusOnCustomSelect() { + const customSelect = this.shadowRoot.querySelector('#customSelect'); + if (customSelect) { + customSelect.focus(); + } + } + + handleSelection(event, node, index) { + event.stopPropagation(); + + this.selectedItem = node; + this.dropdownOpen = false; + this.requestUpdate(); + this.nodeSelected(index); + } + + show() { + this.shadowRoot.getElementById('settingsDialog').show(); + } + + close() { + this.shadowRoot.getElementById('settingsDialog').close(); + } + + removeList() { + localStorage.removeItem('myQortalNodes'); + + const obj1 = { + name: 'Local Node', + protocol: 'http', + domain: '127.0.0.1', + port: 12391, + enableManagement: true, + }; + + const obj2 = { + name: 'Local Testnet', + protocol: 'http', + domain: '127.0.0.1', + port: 62391, + enableManagement: true, + }; + + var renewNodes = []; + renewNodes.push(obj1, obj2); + localStorage.setItem('myQortalNodes', JSON.stringify(renewNodes)); + + let snack1string = get('settings.snack1'); + snackbar.add({ + labelText: `${snack1string}`, + dismiss: true, + }); + + localStorage.removeItem('mySelectedNode'); + localStorage.setItem('mySelectedNode', 0); + + store.dispatch(doLoadNodeConfig()); + } + + nodeSelected(selectedNodeIndex) { + const selectedNode = this.nodeConfig.knownNodes[selectedNodeIndex]; + const selectedNodeUrl = `${ + selectedNode.protocol + + '://' + + selectedNode.domain + + ':' + + selectedNode.port + }`; + + const index = parseInt(selectedNodeIndex); + if (isNaN(index)) return; + + store.dispatch(doSetNode(selectedNodeIndex)); + + localStorage.removeItem('mySelectedNode'); + localStorage.setItem('mySelectedNode', selectedNodeIndex); + + let snack2string = get('settings.snack2'); + snackbar.add({ + labelText: `${snack2string} : ${selectedNodeUrl}`, + dismiss: true, + }); + + // this.shadowRoot.querySelector('#settingsDialog').close(); + } + + addNode(e) { + e.stopPropagation(); + if (this.isBeingEdited) { + this.editNode(this.isBeingEditedIndex); + return; + } + const nameInput = this.shadowRoot.getElementById('nameInput').value; + const protocolList = + this.shadowRoot.getElementById('protocolList').value; + const domainInput = this.shadowRoot.getElementById('domainInput').value; + const portInput = this.shadowRoot.getElementById('portInput').value; + + if ( + protocolList.length >= 4 && + domainInput.length >= 3 && + portInput.length >= 2 + ) { + const nodeObject = { + name: nameInput, + protocol: protocolList, + domain: domainInput, + port: portInput, + enableManagement: true, + }; + + store.dispatch(doAddNode(nodeObject)); + + const haveNodes = JSON.parse(localStorage.getItem('myQortalNodes')); + + if (haveNodes === null || haveNodes.length === 0) { + var savedNodes = []; + savedNodes.push(nodeObject); + localStorage.setItem( + 'myQortalNodes', + JSON.stringify(savedNodes) + ); + + let snack3string = get('settings.snack3'); + snackbar.add({ + labelText: `${snack3string}`, + dismiss: true, + }); + + this.shadowRoot.getElementById('nameInput').value = ''; + this.shadowRoot.getElementById('protocolList').value = ''; + this.shadowRoot.getElementById('domainInput').value = ''; + this.shadowRoot.getElementById('portInput').value = ''; + + this.shadowRoot.querySelector('#addNodeDialog').close(); + } else { + var stored = JSON.parse(localStorage.getItem('myQortalNodes')); + stored.push(nodeObject); + localStorage.setItem('myQortalNodes', JSON.stringify(stored)); + + let snack3string = get('settings.snack3'); + snackbar.add({ + labelText: `${snack3string}`, + dismiss: true, + }); + + this.shadowRoot.getElementById('nameInput').value = ''; + this.shadowRoot.getElementById('protocolList').value = ''; + this.shadowRoot.getElementById('domainInput').value = ''; + this.shadowRoot.getElementById('portInput').value = ''; + + this.shadowRoot.querySelector('#addNodeDialog').close(); + } + } + } + + removeNode(event, index) { + event.stopPropagation(); + let stored = JSON.parse(localStorage.getItem('myQortalNodes')); + stored.splice(index, 1); + localStorage.setItem('myQortalNodes', JSON.stringify(stored)); + store.dispatch(doRemoveNode(index)); + let snack3string = get('settings.snack6'); + snackbar.add({ + labelText: `${snack3string}`, + dismiss: true, + }); + + this.shadowRoot.querySelector('#addNodeDialog').close(); + } + editNode(index) { + const nameInput = this.shadowRoot.getElementById('nameInput').value; + const protocolList = + this.shadowRoot.getElementById('protocolList').value; + const domainInput = this.shadowRoot.getElementById('domainInput').value; + const portInput = this.shadowRoot.getElementById('portInput').value; + + if ( + protocolList.length >= 4 && + domainInput.length >= 3 && + portInput.length >= 2 + ) { + const nodeObject = { + name: nameInput, + protocol: protocolList, + domain: domainInput, + port: portInput, + enableManagement: true, + }; + + let stored = JSON.parse(localStorage.getItem('myQortalNodes')); + const copyStored = [...stored]; + copyStored[index] = nodeObject; + localStorage.setItem('myQortalNodes', JSON.stringify(copyStored)); + store.dispatch(doEditNode(index, nodeObject)); + let snack3string = get('settings.snack7'); + snackbar.add({ + labelText: `${snack3string}`, + dismiss: true, + }); + this.shadowRoot.getElementById('nameInput').value = ''; + this.shadowRoot.getElementById('protocolList').value = ''; + this.shadowRoot.getElementById('domainInput').value = ''; + this.shadowRoot.getElementById('portInput').value = ''; + this.isBeingEdited = false; + this.isBeingEditedIndex = null; + + this.shadowRoot.querySelector('#addNodeDialog').close(); + } + } + + openImportNodesDialog() { + this.shadowRoot.querySelector('#importQortalNodesListDialog').show(); + } + + closeImportNodesDialog() { + this.shadowRoot.querySelector('#importQortalNodesListDialog').close(); + } + + renderExportNodesListButton() { + return html` + + `; + } + + exportQortalNodesList() { + let nodelist = ''; + const qortalNodesList = JSON.stringify( + localStorage.getItem('myQortalNodes') + ); + const qortalNodesListSave = JSON.parse(qortalNodesList || '[]'); + const blob = new Blob([qortalNodesListSave], { + type: 'text/plain;charset=utf-8', + }); + nodelist = 'qortal.nodes'; + this.saveFileToDisk(blob, nodelist); + } + + async saveFileToDisk(blob, fileName) { + try { + const fileHandle = await self.showSaveFilePicker({ + suggestedName: fileName, + types: [ + { + description: 'File', + }, + ], + }); + const writeFile = async (fileHandle, contents) => { + const writable = await fileHandle.createWritable(); + await writable.write(contents); + await writable.close(); + }; + writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')); + let snack4string = get('settings.snack4'); + snackbar.add({ + labelText: `${snack4string} qortal.nodes`, + dismiss: true, + }); + } catch (error) { + if (error.name === 'AbortError') { + return; + } + FileSaver.saveAs(blob, fileName); + } + } + + renderImportNodesListButton() { + return html` + + `; + } + + async importQortalNodesList(file) { + localStorage.removeItem('myQortalNodes'); + const newItems = JSON.parse(file || '[]'); + localStorage.setItem('myQortalNodes', JSON.stringify(newItems)); + this.shadowRoot.querySelector('#importQortalNodesListDialog').close(); + + let snack5string = get('settings.snack5'); + snackbar.add({ + labelText: `${snack5string}`, + dismiss: true, + }); + + localStorage.removeItem('mySelectedNode'); + localStorage.setItem('mySelectedNode', 0); + + store.dispatch(doLoadNodeConfig()); + } + + stateChanged(state) { + this.config = state.config; + this.nodeConfig = state.app.nodeConfig; + } +} + +window.customElements.define('settings-page', SettingsPage); + +const settings = document.createElement('settings-page'); +settingsDialog = document.body.appendChild(settings); + +export default settingsDialog; diff --git a/core/src/plugins/streams.js b/core/src/plugins/streams.js index 8aee79d3..bc4bae3a 100644 --- a/core/src/plugins/streams.js +++ b/core/src/plugins/streams.js @@ -8,6 +8,7 @@ const APP_INFO_STATE = 'app_info_state' const CHAT_HEADS_STREAM_NAME = 'chat_heads' const NODE_CONFIG_STREAM_NAME = 'node_config' const CHAT_LAST_SEEN = 'chat_last_seen' +const SIDE_EFFECT_ACTION = 'side_effect_action' export const loggedInStream = new EpmlStream(LOGIN_STREAM_NAME, () => store.getState().app.loggedIn) export const configStream = new EpmlStream(CONFIG_STREAM_NAME, () => store.getState().config) @@ -16,6 +17,8 @@ export const appInfoStateStream = new EpmlStream(APP_INFO_STATE, () => store.get export const chatHeadsStateStream = new EpmlStream(CHAT_HEADS_STREAM_NAME, () => store.getState().app.chatHeads) export const nodeConfigStream = new EpmlStream(NODE_CONFIG_STREAM_NAME, () => store.getState().app.nodeConfig) export const chatLastSeenStream = new EpmlStream(CHAT_LAST_SEEN, () => store.getState().app.chatLastSeen) +export const sideEffectActionStream = new EpmlStream(SIDE_EFFECT_ACTION, () => store.getState().app.sideEffectAction) + let oldState = { @@ -56,6 +59,10 @@ store.subscribe(() => { if (oldState.app.appInfo !== state.app.appInfo) { appInfoStateStream.emit(state.app.appInfo) } + if (oldState.app.sideEffectAction !== state.app.sideEffectAction) { + sideEffectActionStream.emit(state.app.sideEffectAction) + } + oldState = state }) diff --git a/core/src/redux/app/actions/app-core.js b/core/src/redux/app/actions/app-core.js index 7c7ab9bd..fa2e8380 100644 --- a/core/src/redux/app/actions/app-core.js +++ b/core/src/redux/app/actions/app-core.js @@ -1,5 +1,5 @@ // Core App Actions here... -import { UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, CHAT_HEADS, ACCOUNT_INFO, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS, SET_NEW_TAB, ADD_TAB_INFO, SET_TAB_NOTIFICATIONS, IS_OPEN_DEV_DIALOG } from '../app-action-types.js' +import { UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, CHAT_HEADS, ACCOUNT_INFO, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS, SET_NEW_TAB, ADD_TAB_INFO, SET_TAB_NOTIFICATIONS, IS_OPEN_DEV_DIALOG, SET_NEW_NOTIFICATION, SET_SIDE_EFFECT } from '../app-action-types.js' export const doUpdateBlockInfo = (blockObj) => { return (dispatch, getState) => { @@ -126,6 +126,12 @@ export const setNewTab = (payload) => { payload } } +export const setNewNotification = (payload) => { + return { + type: SET_NEW_NOTIFICATION, + payload + } +} export const setIsOpenDevDialog = (payload)=> { return { type: IS_OPEN_DEV_DIALOG, @@ -144,4 +150,11 @@ export const setTabNotifications = (payload) => { type: SET_TAB_NOTIFICATIONS, payload } +} + +export const setSideEffectAction = (payload)=> { + return { + type: SET_SIDE_EFFECT, + payload + } } \ No newline at end of file diff --git a/core/src/redux/app/actions/node-config.js b/core/src/redux/app/actions/node-config.js index 2cf5eca1..700e0c5f 100644 --- a/core/src/redux/app/actions/node-config.js +++ b/core/src/redux/app/actions/node-config.js @@ -1,5 +1,5 @@ // Node Config Actions here... -import { LOAD_NODE_CONFIG, SET_NODE, ADD_NODE } from '../app-action-types.js' +import { LOAD_NODE_CONFIG, SET_NODE, ADD_NODE, REMOVE_NODE, EDIT_NODE } from '../app-action-types.js' import { UI_VERSION } from '../version.js' const nodeConfigUrl = '/getConfig' @@ -72,6 +72,16 @@ export const doAddNode = (nodeObject) => { return dispatch(addNode(nodeObject)) } } +export const doRemoveNode = (index) => { + return (dispatch, getState) => { + return dispatch(removeNode(index)) + } +} +export const doEditNode = (index, nodeObject) => { + return (dispatch, getState) => { + return dispatch(editNode({index, nodeObject})) + } +} const addNode = (payload) => { return { @@ -80,6 +90,18 @@ const addNode = (payload) => { } } +const editNode = (payload) => { + return { + type: EDIT_NODE, + payload + } +} +const removeNode = (payload) => { + return { + type: REMOVE_NODE, + payload + } +} const obj1 = { name: 'Local Node', protocol: 'http', diff --git a/core/src/redux/app/app-action-types.js b/core/src/redux/app/app-action-types.js index ba2cde2a..fd5e06f7 100644 --- a/core/src/redux/app/app-action-types.js +++ b/core/src/redux/app/app-action-types.js @@ -12,6 +12,8 @@ export const UPDATE_NODE_STATUS = 'UPDATE_NODE_STATUS' export const UPDATE_NODE_INFO = 'UPDATE_NODE_INFO' export const SET_NODE = 'SET_NODE' export const ADD_NODE = 'ADD_NODE' +export const EDIT_NODE = 'EDIT_NODE' +export const REMOVE_NODE = 'REMOVE_NODE' export const LOAD_NODE_CONFIG = 'LOAD_NODE_CONFIG' export const PAGE_URL = 'PAGE_URL' export const CHAT_HEADS = 'CHAT_HEADS' @@ -28,4 +30,6 @@ export const ADD_CHAT_LAST_SEEN = 'ADD_CHAT_LAST_SEEN' export const SET_NEW_TAB = 'SET_NEW_TAB' export const ADD_TAB_INFO = 'ADD_TAB_INFO' export const SET_TAB_NOTIFICATIONS = 'SET_TAB_NOTIFICATIONS' -export const IS_OPEN_DEV_DIALOG = 'IS_OPEN_DEV_DIALOG' \ No newline at end of file +export const IS_OPEN_DEV_DIALOG = 'IS_OPEN_DEV_DIALOG' +export const SET_NEW_NOTIFICATION = 'SET_NEW_NOTIFICATION' +export const SET_SIDE_EFFECT= 'SET_SIDE_EFFECT' diff --git a/core/src/redux/app/app-reducer.js b/core/src/redux/app/app-reducer.js index 11e77486..43f2dc27 100644 --- a/core/src/redux/app/app-reducer.js +++ b/core/src/redux/app/app-reducer.js @@ -1,9 +1,9 @@ // Loading state, login state, isNavDrawOpen state etc. None of this needs to be saved to localstorage. import { loadStateFromLocalStorage, saveStateToLocalStorage } from '../../localStorageHelpers.js' -import { LOG_IN, LOG_OUT, NETWORK_CONNECTION_STATUS, INIT_WORKERS, ADD_PLUGIN_URL, ADD_PLUGIN, ADD_NEW_PLUGIN_URL, NAVIGATE, SELECT_ADDRESS, ACCOUNT_INFO, CHAT_HEADS, UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, LOAD_NODE_CONFIG, SET_NODE, ADD_NODE, PAGE_URL, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS, SET_NEW_TAB, ADD_TAB_INFO, SET_TAB_NOTIFICATIONS, IS_OPEN_DEV_DIALOG } from './app-action-types.js' +import { LOG_IN, LOG_OUT, NETWORK_CONNECTION_STATUS, INIT_WORKERS, ADD_PLUGIN_URL, ADD_PLUGIN, ADD_NEW_PLUGIN_URL, NAVIGATE, SELECT_ADDRESS, ACCOUNT_INFO, CHAT_HEADS, UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, LOAD_NODE_CONFIG, SET_NODE, ADD_NODE, PAGE_URL, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, ALLOW_QAPP_AUTO_AUTH, REMOVE_QAPP_AUTO_AUTH, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN, ALLOW_QAPP_AUTO_LISTS, REMOVE_QAPP_AUTO_LISTS, SET_NEW_TAB, ADD_TAB_INFO, SET_TAB_NOTIFICATIONS, IS_OPEN_DEV_DIALOG, REMOVE_NODE, EDIT_NODE, SET_NEW_NOTIFICATION, SET_SIDE_EFFECT } from './app-action-types.js' import { initWorkersReducer } from './reducers/init-workers.js' import { loginReducer } from './reducers/login-reducer.js' -import { setNode, addNode } from './reducers/manage-node.js' +import { setNode, addNode, removeNode, editNode } from './reducers/manage-node.js' import localForage from "localforage"; const chatLastSeen = localForage.createInstance({ name: "chat-last-seen", @@ -50,7 +50,9 @@ const INITIAL_STATE = { chatLastSeen: [], newTab: null, tabInfo: {}, - isOpenDevDialog: false + isOpenDevDialog: false, + newNotification: null, + sideEffectAction: null } export default (state = INITIAL_STATE, action) => { @@ -120,6 +122,10 @@ export default (state = INITIAL_STATE, action) => { return setNode(state, action) case ADD_NODE: return addNode(state, action) + case EDIT_NODE: + return editNode(state, action) + case REMOVE_NODE: + return removeNode(state, action) case PAGE_URL: return { ...state, @@ -273,6 +279,19 @@ export default (state = INITIAL_STATE, action) => { } } + case SET_NEW_NOTIFICATION: { + return { + ...state, + newNotification: action.payload + } + } + case SET_SIDE_EFFECT: { + return { + ...state, + sideEffectAction: action.payload + } + } + default: return state } diff --git a/core/src/redux/app/reducers/manage-node.js b/core/src/redux/app/reducers/manage-node.js index ad284452..4e42f01f 100644 --- a/core/src/redux/app/reducers/manage-node.js +++ b/core/src/redux/app/reducers/manage-node.js @@ -20,3 +20,26 @@ export const addNode = (state, action) => { } } } + +export const editNode = (state, action) => { + const copyKnownNodes = [...state.nodeConfig.knownNodes] + copyKnownNodes[action.payload.index] = action.payload.nodeObject + return { + ...state, + nodeConfig: { + ...state.nodeConfig, + knownNodes: copyKnownNodes + } + } +} +export const removeNode = (state, action) => { + const copyKnownNodes = [...state.nodeConfig.knownNodes] + copyKnownNodes.splice(action.payload, 1); + return { + ...state, + nodeConfig: { + ...state.nodeConfig, + knownNodes: copyKnownNodes + } + } +} diff --git a/package-lock.json b/package-lock.json index 4f99875b..b088ddad 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@hapi/hapi": "21.3.2", "@hapi/inert": "7.1.0", "@lit-labs/motion": "1.0.4", + "@popperjs/core": "^2.11.8", "@tiptap/core": "2.0.4", "@tiptap/extension-highlight": "2.0.4", "@tiptap/extension-image": "2.0.4", @@ -105,6 +106,9 @@ "electron-builder": "24.6.4", "electron-packager": "17.1.2", "epml": "0.3.3", + "eslint": "^8.50.0", + "eslint-plugin-lit": "^1.9.1", + "eslint-plugin-wc": "^2.0.3", "file-saver": "2.0.5", "highcharts": "11.1.0", "html-escaper": "3.0.3", @@ -125,6 +129,15 @@ "node": ">=18.16.1" } }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/@ampproject/remapping": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", @@ -730,6 +743,111 @@ "node": ">= 10.0.0" } }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.1.tgz", + "integrity": "sha512-PWiOzLIUAjN/w5K17PoF4n6sKBw0gqLHPhywmYHP4t1VFQQVYeb1yWsJwnMVEMl3tUHME7X/SJPZLmtG7XBDxQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/eslintrc/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@eslint/eslintrc/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@eslint/js": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.50.0.tgz", + "integrity": "sha512-NCC3zz2+nvYd+Ckfh87rA47zfu2QsQpvc6k1yzTk+b9KzRj0wkGa8LSoGOXN6Zv4lRf/EIoZ80biDh9HOI+RNQ==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.4.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz", @@ -1083,6 +1201,61 @@ "@hapi/hoek": "^11.0.2" } }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", @@ -2072,6 +2245,41 @@ "tslib": "^2.1.0" } }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@open-wc/dedupe-mixin": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/@open-wc/dedupe-mixin/-/dedupe-mixin-1.4.0.tgz", @@ -4322,6 +4530,15 @@ "node": ">=8" } }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001532", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001532.tgz", @@ -4826,6 +5043,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, "node_modules/deepmerge": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", @@ -4986,6 +5209,18 @@ "node": ">=8" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/dot-prop": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-6.0.1.tgz", @@ -5647,12 +5882,417 @@ "node": ">=0.8.0" } }, + "node_modules/eslint": { + "version": "8.50.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.50.0.tgz", + "integrity": "sha512-FOnOGSuFuFLv/Sa+FDVRZl4GGVAAFFi8LecRsI5a1tMO5HIE8nCm4ivAlzt4dT3ol/PaaGC0rJEEXQmHJBGoOg==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.50.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-lit": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-lit/-/eslint-plugin-lit-1.9.1.tgz", + "integrity": "sha512-XFFVufVxYJwqRB9sLkDXB7SvV1xi9hrC4HRFEdX1h9+iZ3dm4x9uS7EuT9uaXs6zR3DEgcojia1F7pmvWbc4Gg==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1", + "parse5-htmlparser2-tree-adapter": "^6.0.1", + "requireindex": "^1.2.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "eslint": ">= 5" + } + }, + "node_modules/eslint-plugin-wc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-wc/-/eslint-plugin-wc-2.0.3.tgz", + "integrity": "sha512-O3i71FodYMArf8DBs+OuDQ8SH8SMiNaJ4GIcXRDsGURPdvBrVDNS9+GQ0xwmzhqUWV0df5xq8irpceA6YBdJmg==", + "dev": true, + "dependencies": { + "is-valid-element-name": "^1.0.0", + "js-levenshtein-esm": "^1.2.0" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/eslint/node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/eslint/node_modules/globals": { + "version": "13.22.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.22.0.tgz", + "integrity": "sha512-H1Ddc/PbZHTDVJSnj8kWptIRSD6AM3pK+mKytuIVF4uoBV7rshFlhhvA58ceJ5wp3Er58w6zj7bykMpYXt3ETw==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/eslint/node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree/node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/espree/node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/execa": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.10.0.tgz", @@ -5802,6 +6442,21 @@ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", @@ -5810,6 +6465,18 @@ "pend": "~1.2.0" } }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, "node_modules/file-saver": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", @@ -5873,6 +6540,26 @@ "node": ">=6" } }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.9", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz", + "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==", + "dev": true + }, "node_modules/flora-colossus": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/flora-colossus/-/flora-colossus-2.0.0.tgz", @@ -6302,6 +6989,12 @@ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==" }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, "node_modules/has": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", @@ -6498,6 +7191,15 @@ } ] }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", @@ -6508,6 +7210,31 @@ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.4.tgz", "integrity": "sha512-fsXeu4J4i6WNWSikpI88v/PcVflZz+6kMhUfIwc5SY+poQRPnaf5V7qds6SUyUN3cVxEzuCab7QIoLOQ+DQ1wA==" }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "engines": { + "node": ">=0.8.19" + } + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -6690,6 +7417,15 @@ "node": ">=8" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", @@ -6709,6 +7445,12 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true + }, "node_modules/is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -6726,6 +7468,15 @@ "node": ">=0.10.0" } }, + "node_modules/is-valid-element-name": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-valid-element-name/-/is-valid-element-name-1.0.0.tgz", + "integrity": "sha512-GZITEJY2LkSjQfaIPBha7eyZv+ge0PhBR7KITeCCWvy7VBQrCUdFkvpI+HrAPQjVtVjy1LvlEkqQTHckoszruw==", + "dev": true, + "dependencies": { + "is-potential-custom-element-name": "^1.0.0" + } + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -6873,6 +7624,12 @@ "node": ">=8" } }, + "node_modules/js-levenshtein-esm": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/js-levenshtein-esm/-/js-levenshtein-esm-1.2.0.tgz", + "integrity": "sha512-fzreKVq1eD7eGcQr7MtRpQH94f8gIfhdrc7yeih38xh684TNMK9v5aAu2wxfIRMk/GpAJRrzcirMAPIaSDaByQ==", + "dev": true + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6919,6 +7676,12 @@ "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-7.0.3.tgz", "integrity": "sha512-7DE8mpG+/fVw+dTpjbxnx47TaMnDfOI1jwft9g1VybltZCduyRQPJPvc+zzKY9WPHxhPWczyFuYa6I8Mw4iU5A==" }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true + }, "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -6988,6 +7751,19 @@ "node": ">=6" } }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/lie": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz", @@ -7097,6 +7873,12 @@ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -7333,6 +8115,12 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, "node_modules/nice-try": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", @@ -7498,6 +8286,23 @@ "node": ">=6" } }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/orderedmap": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", @@ -7574,6 +8379,18 @@ "node": ">=6" } }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/parse-author": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/parse-author/-/parse-author-2.0.0.tgz", @@ -7598,6 +8415,21 @@ "node": ">=0.10.0" } }, + "node_modules/parse5": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-6.0.1.tgz", + "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", + "dev": true + }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-6.0.1.tgz", + "integrity": "sha512-qPuWvbLgvDGilKc5BoicRovlT4MtYT6JfJyBOMDsKoiT+GiuP5qyrPCnR9HcPECIJJmZh5jRndyNThnhhb/vlA==", + "dev": true, + "dependencies": { + "parse5": "^6.0.1" + } + }, "node_modules/passive-events-support": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/passive-events-support/-/passive-events-support-1.1.0.tgz", @@ -7704,6 +8536,15 @@ "node": ">=10.4.0" } }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/process-es6": { "version": "0.11.6", "resolved": "https://registry.npmjs.org/process-es6/-/process-es6-0.11.6.tgz", @@ -7960,6 +8801,26 @@ "integrity": "sha512-4sP/C9sSxQ3w80AATmvCEI3R+MHzCwr2RSZEbLyMkeJgV3cRk7ySZRUrQnBDSA7A0/z6dkYtjuXlkhN1ZFw3iA==", "dev": true }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/quick-lru": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", @@ -8159,6 +9020,15 @@ "node": ">=0.10.0" } }, + "node_modules/requireindex": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/requireindex/-/requireindex-1.2.0.tgz", + "integrity": "sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww==", + "dev": true, + "engines": { + "node": ">=0.10.5" + } + }, "node_modules/resolve": { "version": "1.22.4", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.4.tgz", @@ -8182,6 +9052,15 @@ "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", "dev": true }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "engines": { + "node": ">=4" + } + }, "node_modules/responselike": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", @@ -8203,6 +9082,16 @@ "node": ">= 4" } }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -8361,6 +9250,29 @@ "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==" }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -8830,6 +9742,18 @@ "node": ">=0.10.0" } }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-outer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", @@ -8987,6 +9911,12 @@ "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", "dev": true }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, "node_modules/throttle-debounce": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/throttle-debounce/-/throttle-debounce-3.0.1.tgz", @@ -9104,6 +10034,18 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, "node_modules/type-fest": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", @@ -9391,6 +10333,18 @@ "fd-slicer": "~1.1.0" } }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/zeed-dom": { "version": "0.9.26", "resolved": "https://registry.npmjs.org/zeed-dom/-/zeed-dom-0.9.26.tgz", diff --git a/package.json b/package.json index 8027f6c5..94b6eafe 100644 --- a/package.json +++ b/package.json @@ -27,18 +27,31 @@ "build-electron": "electron-builder build --publish never", "deploy-electron": "electron-builder build --win --publish never", "release": "NODE_ENV=production electron-builder build --publish never", - "publish": "electron-builder -p always" + "publish": "electron-builder -p always", + "lint": "eslint './**/*.{js,mjs}'" }, "dependencies": { + "@hapi/hapi": "21.3.2", + "@hapi/inert": "7.1.0", + "@lit-labs/motion": "1.0.4", + "@popperjs/core": "^2.11.8", + "@tiptap/core": "2.0.4", + "@tiptap/extension-highlight": "2.0.4", + "@tiptap/extension-image": "2.0.4", + "@tiptap/extension-placeholder": "2.0.4", + "@tiptap/extension-underline": "2.0.4", + "@tiptap/html": "2.0.4", + "@tiptap/pm": "2.0.4", + "@tiptap/starter-kit": "2.0.4", "asmcrypto.js": "2.3.2", "bcryptjs": "2.4.3", "buffer": "6.0.3", "compressorjs": "1.2.1", "crypto-js": "4.1.1", - "electron-log": "4.4.8", - "electron-updater": "6.1.4", "electron-dl": "3.5.0", + "electron-log": "4.4.8", "electron-store": "8.1.0", + "electron-updater": "6.1.4", "emoji-picker-js": "https://github.com/Qortal/emoji-picker-js", "extract-zip": "2.0.1", "jssha": "3.3.1", @@ -57,40 +70,9 @@ "prosemirror-transform": "1.7.5", "prosemirror-view": "1.31.7", "sass": "1.66.1", - "short-unique-id": "5.0.2", - "@hapi/hapi": "21.3.2", - "@hapi/inert": "7.1.0", - "@lit-labs/motion": "1.0.4", - "@tiptap/pm": "2.0.4", - "@tiptap/core": "2.0.4", - "@tiptap/extension-highlight": "2.0.4", - "@tiptap/extension-image": "2.0.4", - "@tiptap/extension-placeholder": "2.0.4", - "@tiptap/extension-underline": "2.0.4", - "@tiptap/html": "2.0.4", - "@tiptap/starter-kit": "2.0.4" + "short-unique-id": "5.0.2" }, "devDependencies": { - "axios": "1.5.0", - "electron": "26.2.0", - "electron-builder": "24.6.4", - "electron-packager": "17.1.2", - "epml": "0.3.3", - "file-saver": "2.0.5", - "highcharts": "11.1.0", - "html-escaper": "3.0.3", - "is-electron": "2.2.2", - "lit": "2.8.0", - "lit-translate": "2.0.1", - "pwa-helpers": "0.9.1", - "passive-events-support": "1.1.0", - "redux": "4.2.1", - "redux-thunk": "2.4.2", - "rollup": "3.29.0", - "rollup-plugin-node-globals": "1.4.0", - "rollup-plugin-progress": "1.1.2", - "rollup-plugin-scss": "3.0.0", - "shelljs": "0.8.5", "@babel/core": "7.22.17", "@material/mwc-button": "0.27.0", "@material/mwc-checkbox": "0.27.0", @@ -140,7 +122,30 @@ "@vaadin/icons": "24.1.7", "@vaadin/password-field": "24.1.7", "@vaadin/tooltip": "24.1.7", - "@zip.js/zip.js": "2.7.29" + "@zip.js/zip.js": "2.7.29", + "axios": "1.5.0", + "electron": "26.2.0", + "electron-builder": "24.6.4", + "electron-packager": "17.1.2", + "epml": "0.3.3", + "eslint": "^8.50.0", + "eslint-plugin-lit": "^1.9.1", + "eslint-plugin-wc": "^2.0.3", + "file-saver": "2.0.5", + "highcharts": "11.1.0", + "html-escaper": "3.0.3", + "is-electron": "2.2.2", + "lit": "2.8.0", + "lit-translate": "2.0.1", + "passive-events-support": "1.1.0", + "pwa-helpers": "0.9.1", + "redux": "4.2.1", + "redux-thunk": "2.4.2", + "rollup": "3.29.0", + "rollup-plugin-node-globals": "1.4.0", + "rollup-plugin-progress": "1.1.2", + "rollup-plugin-scss": "3.0.0", + "shelljs": "0.8.5" }, "engines": { "node": ">=18.16.1" diff --git a/plugins/plugins/core/components/ChatGroupManager.js b/plugins/plugins/core/components/ChatGroupManager.js new file mode 100644 index 00000000..a6fc68c2 --- /dev/null +++ b/plugins/plugins/core/components/ChatGroupManager.js @@ -0,0 +1,348 @@ +import { LitElement, html, css } from 'lit'; +import { Epml } from '../../../epml'; +import '@material/mwc-button'; +import '@material/mwc-dialog'; +import '@polymer/paper-spinner/paper-spinner-lite.js'; +import '@polymer/paper-progress/paper-progress.js'; +import '@material/mwc-icon'; +import '@vaadin/button'; +import './WrapperModal'; +import './TipUser'; +import './UserInfo/UserInfo'; +import './ChatImage'; +import './ReusableImage'; +import { + get +} from 'lit-translate'; + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); + +class ChatGroupsManager extends LitElement { + static get properties() { + return { + leaveGroupObj: { type: Object }, + error: { type: Boolean }, + chatHeads: { type: Array }, + groupAdmin: { attribute: false }, + groupMembers: { attribute: false }, + selectedHead: { type: Object }, + toggle: { attribute: false }, + getMoreMembers: { attribute: false }, + setOpenPrivateMessage: { attribute: false }, + userName: { type: String }, + walletBalance: { type: Number }, + sendMoneyLoading: { type: Boolean }, + btnDisable: { type: Boolean }, + errorMessage: { type: String }, + successMessage: { type: String }, + setOpenTipUser: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setUserName: { attribute: false }, + chatId: { type: String }, + _chatId: { type: String }, + isReceipient: { type: Boolean }, + groups: { type: Array }, + viewImage: { type: Boolean }, + autoView: {type: Boolean}, + onlyMyImages: {type: Boolean}, + repost: {attribute: false} + }; + } + + constructor() { + super(); + this.leaveGroupObj = {}; + this.leaveFee = 0.001; + this.error = false; + this.chatHeads = []; + this.groupAdmin = []; + this.groupMembers = []; + this.observerHandler = this.observerHandler.bind(this); + this.getGroups = this.getGroups.bind(this); + this.viewElement = ''; + this.downObserverElement = ''; + + this.sendMoneyLoading = false; + this.btnDisable = false; + this.errorMessage = ''; + this.successMessage = ''; + + this.groups = []; + this.viewImage = false; + this.myName = + window.parent.reduxStore.getState().app.accountInfo.names[0].name; + this.myAddress = + window.parent.reduxStore.getState().app.selectedAddress.address; + this.autoView =false + this.onlyMyImages = true + } + + static get styles() { + return css` + .top-bar-icon { + cursor: pointer; + height: 18px; + width: 18px; + transition: 0.2s all; + } + + .top-bar-icon:hover { + color: var(--black); + } + + .modal-button { + font-family: Roboto, sans-serif; + font-size: 16px; + color: var(--mdc-theme-primary); + background-color: transparent; + padding: 8px 10px; + border-radius: 5px; + border: none; + transition: all 0.3s ease-in-out; + } + + .close-row { + width: 100%; + display: flex; + justify-content: flex-end; + height: 50px; + flex: 0; + align-items: center; + } + + .container-body { + width: 100%; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: auto; + margin-top: 5px; + padding: 0px 6px; + box-sizing: border-box; + } + + .container-body::-webkit-scrollbar-track { + background-color: whitesmoke; + border-radius: 7px; + } + + .container-body::-webkit-scrollbar { + width: 6px; + border-radius: 7px; + background-color: whitesmoke; + } + + .container-body::-webkit-scrollbar-thumb { + background-color: rgb(180, 176, 176); + border-radius: 7px; + transition: all 0.3s ease-in-out; + } + + .container-body::-webkit-scrollbar-thumb:hover { + background-color: rgb(148, 146, 146); + cursor: pointer; + } + + p { + color: var(--black); + margin: 0px; + padding: 0px; + word-break: break-all; + } + + .container { + display: flex; + width: 100%; + flex-direction: column; + height: 100%; + } + + .chat-right-panel-label { + font-family: Montserrat, sans-serif; + color: var(--group-header); + padding: 5px; + font-size: 13px; + user-select: none; + } + + .group-info { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 10px; + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + text-align: center; + user-select: none; + } + + .group-description { + font-family: Roboto, sans-serif; + color: var(--chat-bubble-msg-color); + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + margin-top: 15px; + word-break: break-word; + user-select: none; + } + + .group-subheader { + font-family: Montserrat, sans-serif; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + + .group-data { + font-family: Roboto, sans-serif; + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + .message-myBg { + background-color: var(--chat-bubble-myBg) !important; + margin-bottom: 15px; + border-radius: 5px; + padding: 5px; + } + .message-data-name { + user-select: none; + color: #03a9f4; + margin-bottom: 5px; + } + .message-user-info { + display: flex; + justify-content: space-between; + width: 100%; + gap: 10px; + } + + .hideImg { + visibility: hidden; + } + .checkbox-row { + position: relative; + display: flex; + align-items: center; + align-content: center; + font-family: Montserrat, sans-serif; + font-weight: 600; + color: var(--black); + padding-left: 5px; + } + `; + } + + async getGroups() { + try { + + let endpoint = `/groups` + + + const groups = await parentEpml.request('apiCall', { + type: 'api', + url: endpoint, + }); + + let list = groups + + this.groups = list + } catch (error) { + console.log(error); + } + } + + firstUpdated() { + // this.viewElement = this.shadowRoot.getElementById('viewElement'); + // this.downObserverElement = + // this.shadowRoot.getElementById('downObserver'); + // this.elementObserver(); + this.getGroups() + } + + + elementObserver() { + const options = { + root: this.viewElement, + rootMargin: '0px', + threshold: 1, + }; + // identify an element to observe + const elementToObserve = this.downObserverElement; + // passing it a callback function + const observer = new IntersectionObserver( + this.observerHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } + + observerHandler(entries) { + if (!entries[0].isIntersecting) { + return; + } else { + if (this.images.length < 20) { + return; + } + this.getMoreImages(); + } + } + + selectAuto(e) { + if (e.target.checked) { + this.autoView = false + } else { + this.autoView = true + } + } + + selectMyImages(e) { + if (e.target.checked) { + this.onlyMyImages = false + } else { + this.onlyMyImages = true + } + } + + render() { + console.log('this.groups', this.groups) + return html` + +
+
+ { + this.getGroups() + }} style="color: var(--black); cursor:pointer;">refresh +
+
+ + this.selectAuto(e)} ?checked=${this.autoView}> +
+
+ + this.selectMyImages(e)} ?checked=${this.onlyMyImages}> +
+
+ + +
+
+
+ + `; + } +} + +customElements.define('chat-groups-manager', ChatGroupsManager); + diff --git a/plugins/plugins/core/components/ChatGroupsModal.js b/plugins/plugins/core/components/ChatGroupsModal.js new file mode 100644 index 00000000..f6a299d0 --- /dev/null +++ b/plugins/plugins/core/components/ChatGroupsModal.js @@ -0,0 +1,98 @@ +import { LitElement, html, css } from 'lit'; +import { + translate, +} from 'lit-translate'; +import '@material/mwc-menu'; +import '@material/mwc-list/mwc-list-item.js'; +import '@material/mwc-dialog' +import './ChatGroupManager' + +export class ChatGroupsModal extends LitElement { + static get properties() { + return { + openDialogGroupsModal: { type: Boolean }, + setOpenDialogGroupsModal: { attribute: false} + }; + } + + static get styles() { + return css` + * { + --mdc-theme-text-primary-on-background: var(--black); + --mdc-dialog-max-width: 85vw; + --mdc-dialog-max-height: 95vh; + } + + .imageContainer { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } + + @-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + `; + } + + constructor() { + super(); + this.openDialogGroupsModal = false + + } + + + + firstUpdated() { + } + + + render() { + console.log('hello') + return html` + + { + this.setOpenDialogGroupsModal(false) + }}> +
+
+ +
+ { + this.setOpenDialogGroupsModal(false) + }} + > + ${translate('general.close')} + +
+ `; + } +} + +customElements.define('chat-groups-modal', ChatGroupsModal); diff --git a/plugins/plugins/core/components/ChatImage.js b/plugins/plugins/core/components/ChatImage.js new file mode 100644 index 00000000..35a3fde2 --- /dev/null +++ b/plugins/plugins/core/components/ChatImage.js @@ -0,0 +1,353 @@ +import { LitElement, html, css } from 'lit'; +import { + get, + translate, +} from 'lit-translate'; +import axios from 'axios' +import { RequestQueueWithPromise } from '../../utils/queue'; +import '@material/mwc-menu'; +import '@material/mwc-list/mwc-list-item.js' +import { Epml } from '../../../epml'; +const requestQueue = new RequestQueueWithPromise(5); +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + +export class ChatImage extends LitElement { + static get properties() { + return { + resource: { type: Object }, + isReady: { type: Boolean}, + status: {type: Object}, + setOpenDialogImage: { attribute: false} + }; + } + + static get styles() { + return css` + * { + --mdc-theme-text-primary-on-background: var(--black); + } + img { + max-width:45vh; + max-height:40vh; + border-radius: 5px; + cursor: pointer; + position: relative; + } + .smallLoading, + .smallLoading:after { + border-radius: 50%; + width: 2px; + height: 2px; + } + + .smallLoading { + border-width: 0.8em; + border-style: solid; + border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) + rgba(3, 169, 244, 0.2) rgb(3, 169, 244); + font-size: 30px; + position: relative; + text-indent: -9999em; + transform: translateZ(0px); + animation: 1.1s linear 0s infinite normal none running loadingAnimation; + } + + .defaultSize { + width: 45vh; + height: 40vh; + } + + mwc-menu { + position: absolute; + } + + + + + @-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + `; + } + + constructor() { + super(); + this.resource = { + identifier: "", + name: "", + service: "" + } + this.status = { + status: '' + } + this.url = "" + this.isReady = false + this.nodeUrl = this.getNodeUrl() + this.myNode = this.getMyNode() + this.hasCalledWhenDownloaded = false + this.isFetching = false + + this.observer = new IntersectionObserver(entries => { + for (const entry of entries) { + if (entry.isIntersecting && this.status.status !== 'READY') { + this._fetchImage(); + // Stop observing after the image has started loading + this.observer.unobserve(this); + } + } + }); + } + getNodeUrl(){ + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + + const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port + return nodeUrl +} +getMyNode(){ + const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + + return myNode +} + + getApiKey() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + let apiKey = myNode.apiKey; + return apiKey; + } + + async fetchResource() { + try { + if(this.isFetching) return + this.isFetching = true + await axios.get(`${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`) + this.isFetching = false + + } catch (error) { + this.isFetching = false + } + } + + async fetchVideoUrl() { + + this.fetchResource() + this.url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?async=true&apiKey=${this.myNode.apiKey}` + + } + + async fetchStatus(){ + let isCalling = false + let percentLoaded = 0 + let timer = 24 + const response = await axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`) + if(response && response.data && response.data.status === 'READY'){ + this.status = response.data + return + } + const intervalId = setInterval(async () => { + if (isCalling) return + isCalling = true + + const data = await requestQueue.enqueue(() => { + return axios.get(`${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}`) + }); + const res = data.data + + isCalling = false + if (res.localChunkCount) { + if (res.percentLoaded) { + if ( + res.percentLoaded === percentLoaded && + res.percentLoaded !== 100 + ) { + timer = timer - 5 + } else { + timer = 24 + } + if (timer < 0) { + timer = 24 + isCalling = true + this.status = { + ...res, + status: 'REFETCHING' + } + + setTimeout(() => { + isCalling = false + this.fetchResource() + }, 25000) + return + } + percentLoaded = res.percentLoaded + } + + this.status = res + if(this.status.status === 'DOWNLOADED'){ + this.fetchResource() + } + } + + // check if progress is 100% and clear interval if true + if (res.status === 'READY') { + clearInterval(intervalId) + this.status = res + this.isReady = true + } + }, 5000) // 1 second interval + } + + async _fetchImage() { + try { + this.fetchVideoUrl({ + name: this.resource.name, + service: this.resource.service, + identifier: this.resource.identifier + }) + this.fetchStatus() + } catch (error) { /* empty */ } + } + + firstUpdated(){ + this.observer.observe(this); + + } + + shouldUpdate(changedProperties) { + if (changedProperties.has('setOpenDialogImage') && changedProperties.size === 1) { + return false; + } + + return true + } + + + + showContextMenu(e) { + e.preventDefault(); + e.stopPropagation(); + + const contextMenu = this.shadowRoot.getElementById('contextMenu'); + const containerRect = e.currentTarget.getBoundingClientRect(); + + // Adjusting the positions + const adjustedX = e.clientX - containerRect.left; + const adjustedY = e.clientY - containerRect.top; + + contextMenu.style.top = `${adjustedY}px`; + contextMenu.style.left = `${adjustedX}px`; + + contextMenu.open = true; + } + + + + + + async handleCopy(e) { + e.stopPropagation(); + const image = this.shadowRoot.querySelector('img'); + + // Create a canvas and draw the image on it. + const canvas = document.createElement('canvas'); + canvas.width = image.naturalWidth; + canvas.height = image.naturalHeight; + const ctx = canvas.getContext('2d'); + ctx.drawImage(image, 0, 0); + + // Convert canvas image to blob + canvas.toBlob(blob => { + try { + const clipboardItem = new ClipboardItem({ 'image/png': blob }); + navigator.clipboard.write([clipboardItem]).then(() => { + const msg = get("chatpage.cchange93") + parentEpml.request('showSnackBar', msg) + console.log('Image copied to clipboard'); + }).catch(err => { + console.error('Failed to copy image: ', err); + }); + } catch (error) { + console.error('Error copying the image: ', error); + } + }, 'image/png'); + } + + + + + handleMenuBlur() { + setTimeout(() => { + if (!this.isMenuItemClicked) { + const contextMenu = this.shadowRoot.getElementById('contextMenu'); + contextMenu.open = false; + } + }, 100); +} + + + render() { + + return html` +
+ ${ + this.status.status !== 'READY' + ? html` +
+
+

${`${Math.round(this.status.percentLoaded || 0 + ).toFixed(0)}% `}${translate('chatpage.cchange94')}

+
+ ` + : '' + } + ${this.status.status === 'READY' ? html` +
+ this.setOpenDialogImage(true)} src=${this.url} /> + + Copy + +
+ ` : ''} + +
+ + ` + + + } +} + +customElements.define('chat-image', ChatImage); diff --git a/plugins/plugins/core/components/ChatModals.js b/plugins/plugins/core/components/ChatModals.js index 2074c911..1da17e7c 100644 --- a/plugins/plugins/core/components/ChatModals.js +++ b/plugins/plugins/core/components/ChatModals.js @@ -9,29 +9,29 @@ import '@material/mwc-dialog' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) class ChatModals extends LitElement { - static get properties() { - return { - openDialogPrivateMessage: {type: Boolean}, - openDialogBlockUser: {type: Boolean}, - isLoading: { type: Boolean }, - nametodialog: { type: String, attribute: true }, - hidePrivateMessageModal: {type: Function}, - hideBlockUserModal: {type: Function}, - toblockaddress: { type: String, attribute: true }, - chatBlockedAdresses: { type: Array } + static get properties() { + return { + openDialogPrivateMessage: { type: Boolean }, + openDialogBlockUser: { type: Boolean }, + isLoading: { type: Boolean }, + nametodialog: { type: String, attribute: true }, + hidePrivateMessageModal: { type: Function }, + hideBlockUserModal: { type: Function }, + toblockaddress: { type: String, attribute: true }, + chatBlockedAdresses: { type: Array } + } } - } - constructor() { - super() - this.isLoading = false - this.hidePrivateMessageModal = () => {} - this.hideBlockUserModal = () => {} - this.chatBlockedAdresses = [] - } + constructor() { + super() + this.isLoading = false + this.hidePrivateMessageModal = () => { } + this.hideBlockUserModal = () => { } + this.chatBlockedAdresses = [] + } - static get styles() { - return css` + static get styles() { + return css` .input { width: 90%; border: none; @@ -60,52 +60,48 @@ class ChatModals extends LitElement { --mdc-theme-primary: red; } ` - } - - firstUpdated() { - - const stopKeyEventPropagation = (e) => { - e.stopPropagation(); - return false; } - this.shadowRoot.getElementById('sendTo').addEventListener('keydown', stopKeyEventPropagation); - this.shadowRoot.getElementById('messageBox').addEventListener('keydown', stopKeyEventPropagation); + firstUpdated() { + + const stopKeyEventPropagation = (e) => { + e.stopPropagation(); + return false; + } + + this.shadowRoot.getElementById('sendTo').addEventListener('keydown', stopKeyEventPropagation); + this.shadowRoot.getElementById('messageBox').addEventListener('keydown', stopKeyEventPropagation); + + parentEpml.ready().then(() => { + parentEpml.subscribe('selected_address', async selectedAddress => { + this.selectedAddress = {} + selectedAddress = JSON.parse(selectedAddress) + if (!selectedAddress || Object.entries(selectedAddress).length === 0) return + this.selectedAddress = selectedAddress + }) - parentEpml.ready().then(() => { - parentEpml.subscribe('selected_address', async selectedAddress => { - this.selectedAddress = {} - selectedAddress = JSON.parse(selectedAddress) - if (!selectedAddress || Object.entries(selectedAddress).length === 0) return - this.selectedAddress = selectedAddress }) - parentEpml.request('apiCall', { - url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}` - }).then(res => { - this.balance = res - }) - }) - parentEpml.imReady() + parentEpml.imReady() } - // Send Private Message + // Send Private Message - _sendMessage() { - this.isLoading = true; + _sendMessage() { + this.isLoading = true; - const recipient = this.shadowRoot.getElementById('sendTo').value; - const messageBox = this.shadowRoot.getElementById('messageBox'); - const messageText = messageBox.value; + const recipient = this.shadowRoot.getElementById('sendTo').value; + const messageBox = this.shadowRoot.getElementById('messageBox'); + const messageText = messageBox.value; - if (recipient.length === 0) { - this.isLoading = false - } else if (messageText.length === 0) { - this.isLoading = false - } else { - this.sendMessage() + if (recipient.length === 0) { + this.isLoading = false + } else if (messageText.length === 0) { + this.isLoading = false + } else { + this.sendMessage() + } } - } async sendMessage() { this.isLoading = true @@ -172,77 +168,77 @@ class ChatModals extends LitElement { } }; - const sendMessageRequest = async (isEncrypted, _publicKey) => { - const messageObject = { - messageText, - images: [''], - repliedTo: '', - version: 1 - } - const stringifyMessageObject = JSON.stringify(messageObject) - let chatResponse = await parentEpml.request('chat', { - type: 18, - nonce: this.selectedAddress.nonce, - params: { - timestamp: sendTimestamp, - recipient: recipient, - recipientPublicKey: _publicKey, - hasChatReference: 0, - message: stringifyMessageObject, - lastReference: reference, - proofOfWorkNonce: 0, - isEncrypted: isEncrypted, - isText: 1 + const sendMessageRequest = async (isEncrypted, _publicKey) => { + const messageObject = { + messageText, + images: [''], + repliedTo: '', + version: 1 } - }) - _computePow(chatResponse) - } + const stringifyMessageObject = JSON.stringify(messageObject) + let chatResponse = await parentEpml.request('chat', { + type: 18, + nonce: this.selectedAddress.nonce, + params: { + timestamp: sendTimestamp, + recipient: recipient, + recipientPublicKey: _publicKey, + hasChatReference: 0, + message: stringifyMessageObject, + lastReference: reference, + proofOfWorkNonce: 0, + isEncrypted: isEncrypted, + isText: 1 + } + }) + _computePow(chatResponse) + } - const _computePow = async (chatBytes) => { + const _computePow = async (chatBytes) => { - const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }) - const chatBytesArray = new Uint8Array(_chatBytesArray) - const chatBytesHash = new window.parent.Sha256().process(chatBytesArray).finish().result - const hashPtr = window.parent.sbrk(32, window.parent.heap) - const hashAry = new Uint8Array(window.parent.memory.buffer, hashPtr, 32) - hashAry.set(chatBytesHash) + const _chatBytesArray = Object.keys(chatBytes).map(function (key) { return chatBytes[key]; }) + const chatBytesArray = new Uint8Array(_chatBytesArray) + const chatBytesHash = new window.parent.Sha256().process(chatBytesArray).finish().result + const hashPtr = window.parent.sbrk(32, window.parent.heap) + const hashAry = new Uint8Array(window.parent.memory.buffer, hashPtr, 32) + hashAry.set(chatBytesHash) - const difficulty = this.balance < 4 ? 18 : 8 + const difficulty = this.balance < 4 ? 18 : 8 - const workBufferLength = 8 * 1024 * 1024; - const workBufferPtr = window.parent.sbrk(workBufferLength, window.parent.heap) + const workBufferLength = 8 * 1024 * 1024; + const workBufferPtr = window.parent.sbrk(workBufferLength, window.parent.heap) - let nonce = window.parent.computePow(hashPtr, workBufferPtr, workBufferLength, difficulty) + let nonce = window.parent.computePow(hashPtr, workBufferPtr, workBufferLength, difficulty) - let _response = await parentEpml.request('sign_chat', { - nonce: this.selectedAddress.nonce, - chatBytesArray: chatBytesArray, - chatNonce: nonce - }) - getSendChatResponse(_response) - } + let _response = await parentEpml.request('sign_chat', { + nonce: this.selectedAddress.nonce, + chatBytesArray: chatBytesArray, + chatNonce: nonce + }) + getSendChatResponse(_response) + } - const getSendChatResponse = (response) => { + const getSendChatResponse = (response) => { - if (response === true) { - messageBox.value = '' - let err2string = get('welcomepage.wcchange8') - parentEpml.request('showSnackBar', `${err2string}`) - this.isLoading = false - this.shadowRoot.querySelector('#startPmDialog').close() - } else if (response.error) { - parentEpml.request('showSnackBar', response.message) - this.isLoading = false - this.shadowRoot.querySelector('#startPmDialog').close() - } else { - let err3string = get('welcomepage.wcchange9') - parentEpml.request('showSnackBar', `${err3string}`) - this.isLoading = false - this.shadowRoot.querySelector('#startPmDialog').close() - } + if (response === true) { + messageBox.value = '' + let err2string = get('welcomepage.wcchange8') + parentEpml.request('showSnackBar', `${err2string}`) + this.isLoading = false + this.shadowRoot.querySelector('#startPmDialog').close() + } else if (response.error) { + parentEpml.request('showSnackBar', response.message) + this.isLoading = false + this.shadowRoot.querySelector('#startPmDialog').close() + } else { + let err3string = get('welcomepage.wcchange9') + parentEpml.request('showSnackBar', `${err3string}`) + this.isLoading = false + this.shadowRoot.querySelector('#startPmDialog').close() + } - } - getAddressPublicKey() + } + getAddressPublicKey() } _textArea(e) { @@ -276,8 +272,8 @@ class ChatModals extends LitElement { fetch(`${nodeUrl}/names/address/${item}?limit=0&reverse=true`).then(res => { return res.json() }).then(jsonRes => { - if(jsonRes.length) { - jsonRes.map (item => { + if (jsonRes.length) { + jsonRes.map(item => { obj.push(item) }) } else { @@ -291,7 +287,7 @@ class ChatModals extends LitElement { relMessages() { setTimeout(() => { - window.location.href = window.location.href.split( '#' )[0] + window.location.href = window.location.href.split('#')[0] }, 500) } @@ -345,8 +341,8 @@ class ChatModals extends LitElement { return ret } - render() { - return html` + render() { + return html` this._textArea(e)} ?disabled=${this.isLoading} id='messageBox' placeholder='${translate('welcomepage.wcchange5')}' rows='1'>

{ - this._sendMessage(); - } - }>${translate('welcomepage.wcchange6')} + this._sendMessage(); + } + }>${translate('welcomepage.wcchange6')}
`; - } + } } customElements.define('chat-modals', ChatModals) \ No newline at end of file diff --git a/plugins/plugins/core/components/ChatPage-css.js b/plugins/plugins/core/components/ChatPage-css.js new file mode 100644 index 00000000..50d62389 --- /dev/null +++ b/plugins/plugins/core/components/ChatPage-css.js @@ -0,0 +1,1154 @@ +import { css } from 'lit' + +export const chatpageStyles = css` + html { + scroll-behavior: smooth; + } + + .chat-head-container { + display: flex; + justify-content: flex-start; + flex-direction: column; + height: 50vh; + overflow-y: auto; + overflow-x: hidden; + width: 100%; + } + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .senderName { + margin: 0; + color: var(--mdc-theme-primary); + font-weight: bold; + user-select: none; + } + + .original-message { + color: var(--chat-bubble-msg-color); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin: 0; + width: 800px; + } + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chat-text-area .typing-area .chatbar { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: auto; + padding: 5px 5px 5px 7px; + overflow: hidden; + } + + .chat-text-area .typing-area .emoji-button { + width: 45px; + height: 40px; + padding-top: 4px; + border: none; + outline: none; + background: transparent; + cursor: pointer; + max-height: 40px; + color: var(--black); + } + + .emoji-button-caption { + width: 45px; + height: 40px; + padding-top: 4px; + border: none; + outline: none; + background: transparent; + cursor: pointer; + max-height: 40px; + color: var(--black); + } + + .caption-container { + width: 100%; + display: flex; + height: auto; + overflow: hidden; + justify-content: center; + background-color: var(--white); + padding: 5px; + border-radius: 1px; + } + + .chatbar-caption { + font-family: Roboto, sans-serif; + width: 70%; + margin-right: 10px; + outline: none; + align-items: center; + font-size: 18px; + resize: none; + border-top: 0; + border-right: 0; + border-left: 0; + border-bottom: 1px solid #cac8c8; + padding: 3px; + } + + .message-size-container { + display: flex; + justify-content: flex-end; + width: 100%; + } + + .message-size { + font-family: Roboto, sans-serif; + font-size: 12px; + color: black; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + img { + border-radius: 25%; + } + + .dialogCustom { + position: fixed; + z-index: 10000; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + top: 10px; + right: 20px; + user-select: none; + } + + .dialogCustomInner { + min-width: 300px; + height: 40px; + background-color: var(--white); + box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; + padding: 10px; + border-radius: 4px; + } + + .dialogCustomInner ul { + padding-left: 0px + } + + .dialogCustomInner li { + margin-bottom: 10px; + } + + .marginLoader { + margin-right: 8px; + } + + .last-message-ref { + position: absolute; + font-size: 18px; + top: -40px; + right: 30px; + width: 50; + height: 50; + z-index: 5; + color: black; + background-color: white; + border-radius: 50%; + transition: all 0.1s ease-in-out; + } + + .last-message-ref:hover { + cursor: pointer; + transform: scale(1.1); + } + + .arrow-down-icon { + transform: scale(1.15); + } + + .chat-container { + display: grid; + max-height: 100%; + } + + .chat-text-area { + display: flex; + position: relative; + justify-content: center; + min-height: 60px; + max-height: 100%; + } + + .chat-text-area .typing-area { + display: flex; + flex-direction: column; + width: 98%; + box-sizing: border-box; + margin-bottom: 8px; + border: 1px solid var(--chat-bubble-bg); + border-radius: 10px; + background: var(--chat-bubble-bg); + } + + .chat-text-area .typing-area textarea { + display: none; + } + + .chat-text-area .typing-area .chat-editor { + display: flex; + max-height: -webkit-fill-available; + width: 100%; + border-color: transparent; + margin: 0; + padding: 0; + border: none; + } + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .repliedTo-subcontainer { + display: flex; + flex-direction: row; + align-items: center; + gap: 15px; + width: 100%; + } + + .repliedTo-message { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + word-break: break-all; + text-overflow: ellipsis; + overflow: hidden; + max-height: 60px; + } + .repliedTo-message p { + margin: 0px; + padding: 0px; + } + + .repliedTo-message pre { + white-space: pre-wrap; + } + + .repliedTo-message p mark { + background-color: #ffe066; + border-radius: 0.25em; + box-decoration-break: clone; + padding: 0.125em 0; + } + + .reply-icon { + width: 20px; + color: var(--mdc-theme-primary); + } + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chatbar-container { + width: 100%; + display: flex; + height: auto; + overflow: hidden; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + .lds-grid div { + position: absolute; + width: 34px; + height: 34px; + border-radius: 50%; + background: #03a9f4; + animation: lds-grid 1.2s linear infinite; + } + + .lds-grid div:nth-child(1) { + top: 4px; + left: 4px; + animation-delay: 0s; + } + + .lds-grid div:nth-child(2) { + top: 4px; + left: 48px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(3) { + top: 4px; + left: 90px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(4) { + top: 50px; + left: 4px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(5) { + top: 50px; + left: 48px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(6) { + top: 50px; + left: 90px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(7) { + top: 95px; + left: 4px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(8) { + top: 95px; + left: 48px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(9) { + top: 95px; + left: 90px; + animation-delay: -1.6s; + } + + @keyframes lds-grid { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } + } + + .float-left { + float: left; + } + + img { + border-radius: 25%; + } + + paper-dialog.warning { + width: 50%; + max-width: 50vw; + height: 30%; + max-height: 30vh; + text-align: center; + background-color: var(--white); + color: var(--black); + border: 1px solid var(--black); + border-radius: 15px; + line-height: 1.6; + overflow-y: auto; + overflow-x: hidden; + width: 100%; + } + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .senderName { + margin: 0; + color: var(--mdc-theme-primary); + font-weight: bold; + user-select: none; + } + + .original-message { + color: var(--chat-bubble-msg-color); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + margin: 0; + width: 800px; + } + + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chat-text-area .typing-area .chatbar { + position: relative; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + height: auto; + padding: 5px 5px 5px 7px; + overflow: hidden; + } + + .chat-text-area .typing-area .emoji-button { + width: 45px; + height: 40px; + padding-top: 4px; + border: none; + outline: none; + background: transparent; + cursor: pointer; + max-height: 40px; + color: var(--black); + } + + .emoji-button-caption { + width: 45px; + height: 40px; + padding-top: 4px; + border: none; + outline: none; + background: transparent; + cursor: pointer; + max-height: 40px; + color: var(--black); + } + + .caption-container { + width: 100%; + display: flex; + height: auto; + overflow: hidden; + justify-content: center; + background-color: var(--white); + padding: 5px; + border-radius: 1px; + } + + .chatbar-caption { + font-family: Roboto, sans-serif; + width: 70%; + margin-right: 10px; + outline: none; + align-items: center; + font-size: 18px; + resize: none; + border-top: 0; + border-right: 0; + border-left: 0; + border-bottom: 1px solid #cac8c8; + padding: 3px; + } + + .message-size-container { + display: flex; + justify-content: flex-end; + width: 100%; + } + + .message-size { + font-family: Roboto, sans-serif; + font-size: 12px; + color: black; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + img { + border-radius: 25%; + } + + .dialogCustom { + position: fixed; + z-index: 10000; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + top: 10px; + right: 20px; + user-select: none; + } + + .dialogCustomInner { + min-width: 300px; + height: 40px; + background-color: var(--white); + box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; + padding: 10px; + border-radius: 4px; + } + + .dialogCustomInner ul { + padding-left: 0px + } + + .dialogCustomInner li { + margin-bottom: 10px; + } + + .marginLoader { + margin-right: 8px; + } + + .last-message-ref { + position: absolute; + font-size: 18px; + top: -40px; + right: 30px; + width: 50; + height: 50; + z-index: 5; + color: black; + background-color: white; + border-radius: 50%; + transition: all 0.1s ease-in-out; + } + + .last-message-ref:hover { + cursor: pointer; + transform: scale(1.1); + } + + .arrow-down-icon { + transform: scale(1.15); + } + + .chat-container { + display: grid; + max-height: 100%; + } + + .chat-text-area { + display: flex; + position: relative; + justify-content: center; + min-height: 60px; + max-height: 100%; + } + + .chat-text-area .typing-area { + display: flex; + flex-direction: column; + width: 98%; + box-sizing: border-box; + margin-bottom: 8px; + border: 1px solid var(--chat-bubble-bg); + border-radius: 10px; + background: var(--chat-bubble-bg); + } + + .chat-text-area .typing-area textarea { + display: none; + } + + .chat-text-area .typing-area .chat-editor { + display: flex; + max-height: -webkit-fill-available; + width: 100%; + border-color: transparent; + margin: 0; + padding: 0; + border: none; + } + + .repliedTo-container { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 10px 10px 8px 10px; + } + + .repliedTo-subcontainer { + display: flex; + flex-direction: row; + align-items: center; + gap: 15px; + width: 100%; + } + + .repliedTo-message { + display: flex; + flex-direction: column; + gap: 5px; + width: 100%; + word-break: break-all; + text-overflow: ellipsis; + overflow: hidden; + max-height: 60px; + } + .repliedTo-message p { + margin: 0px; + padding: 0px; + } + + .repliedTo-message pre { + white-space: pre-wrap; + } + + .repliedTo-message p mark { + background-color: #ffe066; + border-radius: 0.25em; + box-decoration-break: clone; + padding: 0.125em 0; + } + + .reply-icon { + width: 20px; + color: var(--mdc-theme-primary); + } + + .close-icon { + color: #676b71; + width: 18px; + transition: all 0.1s ease-in-out; + } + + .close-icon:hover { + cursor: pointer; + color: #494c50; + } + + .chatbar-container { + width: 100%; + display: flex; + height: auto; + overflow: hidden; + } + + .lds-grid { + width: 120px; + height: 120px; + position: absolute; + left: 50%; + top: 40%; + } + + .lds-grid div { + position: absolute; + width: 34px; + height: 34px; + border-radius: 50%; + background: #03a9f4; + animation: lds-grid 1.2s linear infinite; + } + + .lds-grid div:nth-child(1) { + top: 4px; + left: 4px; + animation-delay: 0s; + } + + .lds-grid div:nth-child(2) { + top: 4px; + left: 48px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(3) { + top: 4px; + left: 90px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(4) { + top: 50px; + left: 4px; + animation-delay: -0.4s; + } + + .lds-grid div:nth-child(5) { + top: 50px; + left: 48px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(6) { + top: 50px; + left: 90px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(7) { + top: 95px; + left: 4px; + animation-delay: -0.8s; + } + + .lds-grid div:nth-child(8) { + top: 95px; + left: 48px; + animation-delay: -1.2s; + } + + .lds-grid div:nth-child(9) { + top: 95px; + left: 90px; + animation-delay: -1.6s; + } + + @keyframes lds-grid { + 0%, 100% { + opacity: 1; + } + 50% { + opacity: 0.5; + } + } + + .float-left { + float: left; + } + + img { + border-radius: 25%; + } + + paper-dialog.warning { + width: 50%; + max-width: 50vw; + height: 30%; + max-height: 30vh; + text-align: center; + background-color: var(--white); + color: var(--black); + border: 1px solid var(--black); + border-radius: 15px; + line-height: 1.6; + overflow-y: auto; + } + .buttons { + text-align:right; + } + + .dialogCustom { + position: fixed; + z-index: 10000; + display: flex; + justify-content: center; + flex-direction: column; + align-items: center; + top: 10px; + right: 20px; + user-select: none; + } + + .dialogCustom p { + color: var(--black) + } + + .dialogCustomInner { + min-width: 300px; + height: 40px; + background-color: var(--white); + box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; + padding: 10px; + border-radius: 4px; + } + + .dialogCustomInner ul { + padding-left: 0px + } + + .dialogCustomInner li { + margin-bottom: 10px; + } + + .marginLoader { + margin-right: 8px; + } + + .smallLoading, + .smallLoading:after { + border-radius: 50%; + width: 2px; + height: 2px; + } + + .smallLoading { + border-width: 0.8em; + border-style: solid; + border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) + rgba(3, 169, 244, 0.2) rgb(3, 169, 244); + font-size: 10px; + position: relative; + text-indent: -9999em; + transform: translateZ(0px); + animation: 1.1s linear 0s infinite normal none running loadingAnimation; + } + + @-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + /* Add Image Modal Dialog Styling */ + + .dialog-container { + position: relative; + display: flex; + align-items: center; + flex-direction: column; + padding: 0 10px; + gap: 10px; + height: 100%; + } + + .dialog-container-title { + font-family: Montserrat; + color: var(--black); + font-size: 20px; + margin: 15px 0 0 0; + } + + .divider { + height: 1px; + background-color: var(--chat-bubble-msg-color); + user-select: none; + width: 70%; + margin-bottom: 20px; + } + + .dialog-container-loader { + position: relative; + display: flex; + align-items: center; + padding: 0 10px; + gap: 10px; + height: 100%; + } + + .dialog-image { + width: 100%; + max-height: 300px; + border-radius: 0; + object-fit: contain; + } + + .chat-right-panel { + flex: 0; + border-left: 3px solid rgb(221, 221, 221); + height: 100%; + overflow-y: auto; + background: transparent; + } + + .movedin { + flex: 1 !important; + background: transparent; + } + + .main-container { + display: flex; + height: 100%; + } + + .group-nav-container { + display: flex; + height: 40px; + padding: 5px; + margin: 0px; + background-color: var(--chat-bubble-bg); + box-sizing: border-box; + align-items: center; + justify-content: space-between; + box-shadow: var(--group-drop-shadow); + z-index: 1; + } + + .top-bar-icon { + border-radius: 50%; + color: var(--chat-bubble-msg-color); + transition: 0.3s all ease-in-out; + padding: 5px; + background-color: transparent; + } + + .top-bar-icon:hover { + background-color: #e6e6e69b; + cursor: pointer; + color: var(--black) + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 16px; + color: var(--black); + margin:0px; + padding:0px; + } + + + .modal-button { + font-family: Roboto, sans-serif; + font-size: 16px; + color: var(--mdc-theme-primary); + background-color: transparent; + padding: 8px 10px; + border-radius: 5px; + border: none; + transition: all 0.3s ease-in-out; + } + + .modal-button-red { + font-family: Roboto, sans-serif; + font-size: 16px; + color: #F44336; + background-color: transparent; + padding: 8px 10px; + border-radius: 5px; + border: none; + transition: all 0.3s ease-in-out; + } + + .modal-button-red:hover { + cursor: pointer; + background-color: #f4433663; + } + + .modal-button:hover { + cursor: pointer; + background-color: #03a8f475; + } + + .name-input { + width: 100%; + margin-bottom: 15px; + outline: 0; + border-width: 0 0 2px; + border-color: var(--mdc-theme-primary); + background-color: transparent; + padding: 10px; + font-family: Roboto, sans-serif; + font-size: 15px; + color: var(--chat-bubble-msg-color); + box-sizing: border-box; + } + + .name-input::selection { + background-color: var(--mdc-theme-primary); + color: white; + } + + .name-input::placeholder { + opacity: 0.9; + color: var(--black); + } + + .search-results-div { + position: absolute; + top: 25px; + right: 25px; + } + + .search-field { + width: 100%; + position: relative; + margin-bottom: 5px; + } + + .search-icon { + position: absolute; + right: 3px; + top: 0; + color: var(--chat-bubble-msg-color); + transition: all 0.3s ease-in-out; + background: none; + border-radius: 50%; + padding: 6px 3px; + font-size: 21px; + } + + .search-icon:hover { + cursor: pointer; + background: #d7d7d75c; + } + + .user-verified { + position: absolute; + top: 0; + right: 5px; + display: flex; + align-items: center; + gap: 10px; + color: #04aa2e; + font-size: 13px; + } + + .user-selected { + display: flex; + justify-content: space-between; + align-items: center; + margin: 0; + box-shadow: rgb(0 0 0 / 16%) 0px 3px 6px, rgb(0 0 0 / 23%) 0px 3px 6px; + padding: 18px 20px; + color: var(--chat-bubble-msg-color); + border-radius: 5px; + background-color: #ececec96; + } + + .user-selected-name { + font-family: Roboto, sans-serif; + margin: 0; + font-size: 16px; + } + + .forwarding-container { + display: flex; + gap: 15px; + } + + .user-selected-forwarding { + font-family: Livvic, sans-serif; + margin: 0; + font-size: 16px; + } + + .close-forwarding { + color: #676b71; + width: 14px; + transition: all 0.1s ease-in-out; + } + + .close-forwarding:hover { + cursor: pointer; + color: #4e5054; + } + + .chat-gifs { + position: absolute; + right: 15px; + bottom: 100px; + justify-self: flex-end; + width: fit-content; + height: auto; + transform: translateY(30%); + animation: smooth-appear 0.5s ease forwards; + z-index: 5; + } + + @keyframes smooth-appear { + to { + transform: translateY(0); + } + } + + .gifs-backdrop { + top: 0; + height: 100vh; + width: 100vw; + background: transparent; + position: fixed; + } + + .modal-button-row { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + } + + + .attachment-icon-container { + display: flex; + align-items: center; + justify-content: center; + height: 120px; + width: 120px; + border-radius: 50%; + border: none; + background-color: var(--mdc-theme-primary); + } + + .attachment-icon { + width: 70%; + } + + .attachment-name { + font-family: Work Sans, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + margin: 0px; + letter-spacing: 1px; + padding: 5px 0px; + } +` diff --git a/plugins/plugins/core/components/ChatPage.js b/plugins/plugins/core/components/ChatPage.js index 864d6745..b9a5ceb0 100644 --- a/plugins/plugins/core/components/ChatPage.js +++ b/plugins/plugins/core/components/ChatPage.js @@ -1,7 +1,7 @@ -import { LitElement, html, css } from 'lit' +import { LitElement, html } from 'lit' import { animate } from '@lit-labs/motion' import { Epml } from '../../../epml.js' -import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' +import { get, translate } from 'lit-translate' import { generateHTML } from '@tiptap/core' import { unsafeHTML } from 'lit/directives/unsafe-html.js' import { Editor, Extension } from '@tiptap/core' @@ -10,8 +10,8 @@ import { inputKeyCodes } from '../../utils/keyCodes.js' import { replaceMessagesEdited } from '../../utils/replace-messages-edited.js' import { publishData } from '../../utils/publish-image.js' import { EmojiPicker } from 'emoji-picker-js' +import {ifDefined} from 'lit/directives/if-defined.js'; -import * as zip from '@zip.js/zip.js' import localForage from 'localforage' import StarterKit from '@tiptap/starter-kit' @@ -20,6 +20,9 @@ import Placeholder from '@tiptap/extension-placeholder' import Highlight from '@tiptap/extension-highlight' import WebWorker from 'web-worker:./computePowWorker.js' import WebWorkerFile from 'web-worker:./computePowWorkerFile.js' +import WebWorkerSortMessages from 'web-worker:./webworkerSortMessages.js' +import WebWorkerDecodeMessages from 'web-worker:./webworkerDecodeMessages.js' + import ShortUniqueId from 'short-unique-id' import Compressor from 'compressorjs' @@ -35,13 +38,17 @@ import './ChatSideNavHeads.js' import './ChatLeaveGroup.js' import './ChatGroupSettings.js' import './ChatRightPanel.js' +import './ChatRightPanelResources.js' import './ChatSearchResults.js' import '@material/mwc-button' import '@material/mwc-dialog' import '@material/mwc-icon' import '@polymer/paper-dialog/paper-dialog.js' import '@polymer/paper-spinner/paper-spinner-lite.js' +import { RequestQueue } from '../../utils/queue.js' import { modalHelper } from '../../utils/publish-modal.js' +import { generateIdFromAddresses } from '../../utils/id-generation.js' +import { chatpageStyles } from './ChatPage-css.js' const chatLastSeen = localForage.createInstance({ name: "chat-last-seen", @@ -49,6 +56,12 @@ const chatLastSeen = localForage.createInstance({ const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) +export const queue = new RequestQueue(); + +export const chatLimit = 20 +export const chatLimitHalf = 10 + +export const totalMsgCount = 60 class ChatPage extends LitElement { static get properties() { return { @@ -74,7 +87,7 @@ class ChatPage extends LitElement { hideNewMessageBar: { attribute: false }, setOpenPrivateMessage: { attribute: false }, chatEditorPlaceholder: { type: String }, - messagesRendered: { type: Array }, + messagesRendered: { type: Object }, repliedToMessageObj: { type: Object }, editedMessageObj: { type: Object }, iframeHeight: { type: Number }, @@ -95,6 +108,7 @@ class ChatPage extends LitElement { groupAdmin: { type: Array }, groupMembers: { type: Array }, shifted: { type: Boolean }, + shiftedResources: {type: Boolean}, groupInfo: { type: Object }, setActiveChatHeadUrl: { attribute: false }, userFound: { type: Array }, @@ -112,1167 +126,26 @@ class ChatPage extends LitElement { openGifModal: { type: Boolean }, gifsLoading: { type: Boolean }, goToRepliedMessage: { attribute: false }, - isLoadingGoToRepliedMessage: { type: Object } + isLoadingGoToRepliedMessage: { type: Object }, + updateMessageHash: { type: Object}, + oldMessages: {type: Array}, + messageQueue: {type: Array}, + isInProcessQueue: {type: Boolean}, + loggedInUserName: {type: String}, + loggedInUserAddress: {type: String} } } static get styles() { - return css` - html { - scroll-behavior: smooth; - } - - .chat-head-container { - display: flex; - justify-content: flex-start; - flex-direction: column; - height: 50vh; - overflow-y: auto; - overflow-x: hidden; - width: 100%; - } - - .repliedTo-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px 8px 10px; - } - - .senderName { - margin: 0; - color: var(--mdc-theme-primary); - font-weight: bold; - user-select: none; - } - - .original-message { - color: var(--chat-bubble-msg-color); - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - margin: 0; - width: 800px; - } - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } - - .chat-text-area .typing-area .chatbar { - position: relative; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: auto; - padding: 5px 5px 5px 7px; - overflow: hidden; - } - - .chat-text-area .typing-area .emoji-button { - width: 45px; - height: 40px; - padding-top: 4px; - border: none; - outline: none; - background: transparent; - cursor: pointer; - max-height: 40px; - color: var(--black); - } - - .emoji-button-caption { - width: 45px; - height: 40px; - padding-top: 4px; - border: none; - outline: none; - background: transparent; - cursor: pointer; - max-height: 40px; - color: var(--black); - } - - .caption-container { - width: 100%; - display: flex; - height: auto; - overflow: hidden; - justify-content: center; - background-color: var(--white); - padding: 5px; - border-radius: 1px; - } - - .chatbar-caption { - font-family: Roboto, sans-serif; - width: 70%; - margin-right: 10px; - outline: none; - align-items: center; - font-size: 18px; - resize: none; - border-top: 0; - border-right: 0; - border-left: 0; - border-bottom: 1px solid #cac8c8; - padding: 3px; - } - - .message-size-container { - display: flex; - justify-content: flex-end; - width: 100%; - } - - .message-size { - font-family: Roboto, sans-serif; - font-size: 12px; - color: black; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - img { - border-radius: 25%; - } - - .dialogCustom { - position: fixed; - z-index: 10000; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - top: 10px; - right: 20px; - user-select: none; - } - - .dialogCustomInner { - min-width: 300px; - height: 40px; - background-color: var(--white); - box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; - padding: 10px; - border-radius: 4px; - } - - .dialogCustomInner ul { - padding-left: 0px - } - - .dialogCustomInner li { - margin-bottom: 10px; - } - - .marginLoader { - margin-right: 8px; - } - - .last-message-ref { - position: absolute; - font-size: 18px; - top: -40px; - right: 30px; - width: 50; - height: 50; - z-index: 5; - color: black; - background-color: white; - border-radius: 50%; - transition: all 0.1s ease-in-out; - } - - .last-message-ref:hover { - cursor: pointer; - transform: scale(1.1); - } - - .arrow-down-icon { - transform: scale(1.15); - } - - .chat-container { - display: grid; - max-height: 100%; - } - - .chat-text-area { - display: flex; - position: relative; - justify-content: center; - min-height: 60px; - max-height: 100%; - } - - .chat-text-area .typing-area { - display: flex; - flex-direction: column; - width: 98%; - box-sizing: border-box; - margin-bottom: 8px; - border: 1px solid var(--chat-bubble-bg); - border-radius: 10px; - background: var(--chat-bubble-bg); - } - - .chat-text-area .typing-area textarea { - display: none; - } - - .chat-text-area .typing-area .chat-editor { - display: flex; - max-height: -webkit-fill-available; - width: 100%; - border-color: transparent; - margin: 0; - padding: 0; - border: none; - } - - .repliedTo-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px 8px 10px; - } - - .repliedTo-subcontainer { - display: flex; - flex-direction: row; - align-items: center; - gap: 15px; - width: 100%; - } - - .repliedTo-message { - display: flex; - flex-direction: column; - gap: 5px; - width: 100%; - word-break: break-all; - text-overflow: ellipsis; - overflow: hidden; - max-height: 60px; - } - .repliedTo-message p { - margin: 0px; - padding: 0px; - } - - .repliedTo-message pre { - white-space: pre-wrap; - } - - .repliedTo-message p mark { - background-color: #ffe066; - border-radius: 0.25em; - box-decoration-break: clone; - padding: 0.125em 0; - } - - .reply-icon { - width: 20px; - color: var(--mdc-theme-primary); - } - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } - - .chatbar-container { - width: 100%; - display: flex; - height: auto; - overflow: hidden; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - .lds-grid div { - position: absolute; - width: 34px; - height: 34px; - border-radius: 50%; - background: #03a9f4; - animation: lds-grid 1.2s linear infinite; - } - - .lds-grid div:nth-child(1) { - top: 4px; - left: 4px; - animation-delay: 0s; - } - - .lds-grid div:nth-child(2) { - top: 4px; - left: 48px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(3) { - top: 4px; - left: 90px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(4) { - top: 50px; - left: 4px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(5) { - top: 50px; - left: 48px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(6) { - top: 50px; - left: 90px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(7) { - top: 95px; - left: 4px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(8) { - top: 95px; - left: 48px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(9) { - top: 95px; - left: 90px; - animation-delay: -1.6s; - } - - @keyframes lds-grid { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } - } - - .float-left { - float: left; - } - - img { - border-radius: 25%; - } - - paper-dialog.warning { - width: 50%; - max-width: 50vw; - height: 30%; - max-height: 30vh; - text-align: center; - background-color: var(--white); - color: var(--black); - border: 1px solid var(--black); - border-radius: 15px; - line-height: 1.6; - overflow-y: auto; - overflow-x: hidden; - width: 100%; - } - - .repliedTo-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px 8px 10px; - } - - .senderName { - margin: 0; - color: var(--mdc-theme-primary); - font-weight: bold; - user-select: none; - } - - .original-message { - color: var(--chat-bubble-msg-color); - text-overflow: ellipsis; - overflow: hidden; - white-space: nowrap; - margin: 0; - width: 800px; - } - - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } - - .chat-text-area .typing-area .chatbar { - position: relative; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; - height: auto; - padding: 5px 5px 5px 7px; - overflow: hidden; - } - - .chat-text-area .typing-area .emoji-button { - width: 45px; - height: 40px; - padding-top: 4px; - border: none; - outline: none; - background: transparent; - cursor: pointer; - max-height: 40px; - color: var(--black); - } - - .emoji-button-caption { - width: 45px; - height: 40px; - padding-top: 4px; - border: none; - outline: none; - background: transparent; - cursor: pointer; - max-height: 40px; - color: var(--black); - } - - .caption-container { - width: 100%; - display: flex; - height: auto; - overflow: hidden; - justify-content: center; - background-color: var(--white); - padding: 5px; - border-radius: 1px; - } - - .chatbar-caption { - font-family: Roboto, sans-serif; - width: 70%; - margin-right: 10px; - outline: none; - align-items: center; - font-size: 18px; - resize: none; - border-top: 0; - border-right: 0; - border-left: 0; - border-bottom: 1px solid #cac8c8; - padding: 3px; - } - - .message-size-container { - display: flex; - justify-content: flex-end; - width: 100%; - } - - .message-size { - font-family: Roboto, sans-serif; - font-size: 12px; - color: black; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - img { - border-radius: 25%; - } - - .dialogCustom { - position: fixed; - z-index: 10000; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - top: 10px; - right: 20px; - user-select: none; - } - - .dialogCustomInner { - min-width: 300px; - height: 40px; - background-color: var(--white); - box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; - padding: 10px; - border-radius: 4px; - } - - .dialogCustomInner ul { - padding-left: 0px - } - - .dialogCustomInner li { - margin-bottom: 10px; - } - - .marginLoader { - margin-right: 8px; - } - - .last-message-ref { - position: absolute; - font-size: 18px; - top: -40px; - right: 30px; - width: 50; - height: 50; - z-index: 5; - color: black; - background-color: white; - border-radius: 50%; - transition: all 0.1s ease-in-out; - } - - .last-message-ref:hover { - cursor: pointer; - transform: scale(1.1); - } - - .arrow-down-icon { - transform: scale(1.15); - } - - .chat-container { - display: grid; - max-height: 100%; - } - - .chat-text-area { - display: flex; - position: relative; - justify-content: center; - min-height: 60px; - max-height: 100%; - } - - .chat-text-area .typing-area { - display: flex; - flex-direction: column; - width: 98%; - box-sizing: border-box; - margin-bottom: 8px; - border: 1px solid var(--chat-bubble-bg); - border-radius: 10px; - background: var(--chat-bubble-bg); - } - - .chat-text-area .typing-area textarea { - display: none; - } - - .chat-text-area .typing-area .chat-editor { - display: flex; - max-height: -webkit-fill-available; - width: 100%; - border-color: transparent; - margin: 0; - padding: 0; - border: none; - } - - .repliedTo-container { - display: flex; - flex-direction: row; - justify-content: space-between; - align-items: center; - padding: 10px 10px 8px 10px; - } - - .repliedTo-subcontainer { - display: flex; - flex-direction: row; - align-items: center; - gap: 15px; - width: 100%; - } - - .repliedTo-message { - display: flex; - flex-direction: column; - gap: 5px; - width: 100%; - word-break: break-all; - text-overflow: ellipsis; - overflow: hidden; - max-height: 60px; - } - .repliedTo-message p { - margin: 0px; - padding: 0px; - } - - .repliedTo-message pre { - white-space: pre-wrap; - } - - .repliedTo-message p mark { - background-color: #ffe066; - border-radius: 0.25em; - box-decoration-break: clone; - padding: 0.125em 0; - } - - .reply-icon { - width: 20px; - color: var(--mdc-theme-primary); - } - - .close-icon { - color: #676b71; - width: 18px; - transition: all 0.1s ease-in-out; - } - - .close-icon:hover { - cursor: pointer; - color: #494c50; - } - - .chatbar-container { - width: 100%; - display: flex; - height: auto; - overflow: hidden; - } - - .lds-grid { - width: 120px; - height: 120px; - position: absolute; - left: 50%; - top: 40%; - } - - .lds-grid div { - position: absolute; - width: 34px; - height: 34px; - border-radius: 50%; - background: #03a9f4; - animation: lds-grid 1.2s linear infinite; - } - - .lds-grid div:nth-child(1) { - top: 4px; - left: 4px; - animation-delay: 0s; - } - - .lds-grid div:nth-child(2) { - top: 4px; - left: 48px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(3) { - top: 4px; - left: 90px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(4) { - top: 50px; - left: 4px; - animation-delay: -0.4s; - } - - .lds-grid div:nth-child(5) { - top: 50px; - left: 48px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(6) { - top: 50px; - left: 90px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(7) { - top: 95px; - left: 4px; - animation-delay: -0.8s; - } - - .lds-grid div:nth-child(8) { - top: 95px; - left: 48px; - animation-delay: -1.2s; - } - - .lds-grid div:nth-child(9) { - top: 95px; - left: 90px; - animation-delay: -1.6s; - } - - @keyframes lds-grid { - 0%, 100% { - opacity: 1; - } - 50% { - opacity: 0.5; - } - } - - .float-left { - float: left; - } - - img { - border-radius: 25%; - } - - paper-dialog.warning { - width: 50%; - max-width: 50vw; - height: 30%; - max-height: 30vh; - text-align: center; - background-color: var(--white); - color: var(--black); - border: 1px solid var(--black); - border-radius: 15px; - line-height: 1.6; - overflow-y: auto; - } - .buttons { - text-align:right; - } - - .dialogCustom { - position: fixed; - z-index: 10000; - display: flex; - justify-content: center; - flex-direction: column; - align-items: center; - top: 10px; - right: 20px; - user-select: none; - } - - .dialogCustom p { - color: var(--black) - } - - .dialogCustomInner { - min-width: 300px; - height: 40px; - background-color: var(--white); - box-shadow: rgb(119 119 119 / 32%) 0px 4px 12px; - padding: 10px; - border-radius: 4px; - } - - .dialogCustomInner ul { - padding-left: 0px - } - - .dialogCustomInner li { - margin-bottom: 10px; - } - - .marginLoader { - margin-right: 8px; - } - - .smallLoading, - .smallLoading:after { - border-radius: 50%; - width: 2px; - height: 2px; - } - - .smallLoading { - border-width: 0.8em; - border-style: solid; - border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) - rgba(3, 169, 244, 0.2) rgb(3, 169, 244); - font-size: 10px; - position: relative; - text-indent: -9999em; - transform: translateZ(0px); - animation: 1.1s linear 0s infinite normal none running loadingAnimation; - } - - @-webkit-keyframes loadingAnimation { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - - @keyframes loadingAnimation { - 0% { - -webkit-transform: rotate(0deg); - transform: rotate(0deg); - } - 100% { - -webkit-transform: rotate(360deg); - transform: rotate(360deg); - } - } - - /* Add Image Modal Dialog Styling */ - - .dialog-container { - position: relative; - display: flex; - align-items: center; - flex-direction: column; - padding: 0 10px; - gap: 10px; - height: 100%; - } - - .dialog-container-title { - font-family: Montserrat; - color: var(--black); - font-size: 20px; - margin: 15px 0 0 0; - } - - .divider { - height: 1px; - background-color: var(--chat-bubble-msg-color); - user-select: none; - width: 70%; - margin-bottom: 20px; - } - - .dialog-container-loader { - position: relative; - display: flex; - align-items: center; - padding: 0 10px; - gap: 10px; - height: 100%; - } - - .dialog-image { - width: 100%; - max-height: 300px; - border-radius: 0; - object-fit: contain; - } - - .chat-right-panel { - flex: 0; - border-left: 3px solid rgb(221, 221, 221); - height: 100%; - overflow-y: auto; - background: transparent; - } - - .movedin { - flex: 1 !important; - background: transparent; - } - - .main-container { - display: flex; - height: 100%; - } - - .group-nav-container { - display: flex; - height: 40px; - padding: 25px 5px 25px 20px; - margin: 0px; - background-color: var(--chat-bubble-bg); - box-sizing: border-box; - align-items: center; - justify-content: space-between; - box-shadow: var(--group-drop-shadow); - } - - .top-bar-icon { - border-radius: 50%; - color: var(--chat-bubble-msg-color); - transition: 0.3s all ease-in-out; - padding: 5px; - background-color: transparent; - } - - .top-bar-icon:hover { - background-color: #e6e6e69b; - cursor: pointer; - color: var(--black) - } - - .group-name { - font-family: Raleway, sans-serif; - font-size: 16px; - color: var(--black); - margin:0px; - padding:0px; - } - - - .modal-button { - font-family: Roboto, sans-serif; - font-size: 16px; - color: var(--mdc-theme-primary); - background-color: transparent; - padding: 8px 10px; - border-radius: 5px; - border: none; - transition: all 0.3s ease-in-out; - } - - .modal-button-red { - font-family: Roboto, sans-serif; - font-size: 16px; - color: #F44336; - background-color: transparent; - padding: 8px 10px; - border-radius: 5px; - border: none; - transition: all 0.3s ease-in-out; - } - - .modal-button-red:hover { - cursor: pointer; - background-color: #f4433663; - } - - .modal-button:hover { - cursor: pointer; - background-color: #03a8f475; - } - - .name-input { - width: 100%; - margin-bottom: 15px; - outline: 0; - border-width: 0 0 2px; - border-color: var(--mdc-theme-primary); - background-color: transparent; - padding: 10px; - font-family: Roboto, sans-serif; - font-size: 15px; - color: var(--chat-bubble-msg-color); - box-sizing: border-box; - } - - .name-input::selection { - background-color: var(--mdc-theme-primary); - color: white; - } - - .name-input::placeholder { - opacity: 0.9; - color: var(--black); - } - - .search-results-div { - position: absolute; - top: 25px; - right: 25px; - } - - .search-field { - width: 100%; - position: relative; - margin-bottom: 5px; - } - - .search-icon { - position: absolute; - right: 3px; - top: 0; - color: var(--chat-bubble-msg-color); - transition: all 0.3s ease-in-out; - background: none; - border-radius: 50%; - padding: 6px 3px; - font-size: 21px; - } - - .search-icon:hover { - cursor: pointer; - background: #d7d7d75c; - } - - .user-verified { - position: absolute; - top: 0; - right: 5px; - display: flex; - align-items: center; - gap: 10px; - color: #04aa2e; - font-size: 13px; - } - - .user-selected { - display: flex; - justify-content: space-between; - align-items: center; - margin: 0; - box-shadow: rgb(0 0 0 / 16%) 0px 3px 6px, rgb(0 0 0 / 23%) 0px 3px 6px; - padding: 18px 20px; - color: var(--chat-bubble-msg-color); - border-radius: 5px; - background-color: #ececec96; - } - - .user-selected-name { - font-family: Roboto, sans-serif; - margin: 0; - font-size: 16px; - } - - .forwarding-container { - display: flex; - gap: 15px; - } - - .user-selected-forwarding { - font-family: Livvic, sans-serif; - margin: 0; - font-size: 16px; - } - - .close-forwarding { - color: #676b71; - width: 14px; - transition: all 0.1s ease-in-out; - } - - .close-forwarding:hover { - cursor: pointer; - color: #4e5054; - } - - .chat-gifs { - position: absolute; - right: 15px; - bottom: 100px; - justify-self: flex-end; - width: fit-content; - height: auto; - transform: translateY(30%); - animation: smooth-appear 0.5s ease forwards; - z-index: 5; - } - - @keyframes smooth-appear { - to { - transform: translateY(0); - } - } - - .gifs-backdrop { - top: 0; - height: 100vh; - width: 100vw; - background: transparent; - position: fixed; - } - - .modal-button-row { - display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - } + return [chatpageStyles]; + } - .attachment-icon-container { - display: flex; - align-items: center; - justify-content: center; - height: 120px; - width: 120px; - border-radius: 50%; - border: none; - background-color: var(--mdc-theme-primary); - } - - .attachment-icon { - width: 70%; - } - - .attachment-name { - font-family: Work Sans, sans-serif; - font-size: 20px; - color: var(--chat-bubble-msg-color); - margin: 0px; - letter-spacing: 1px; - padding: 5px 0px; - } -` - } - + constructor() { super() this.getOldMessage = this.getOldMessage.bind(this) + this.clearUpdateMessageHashmap = this.clearUpdateMessageHashmap.bind(this) this._sendMessage = this._sendMessage.bind(this) this.insertFile = this.insertFile.bind(this) this.pasteImage = this.pasteImage.bind(this) @@ -1283,7 +156,7 @@ class ChatPage extends LitElement { this.setUserName = this.setUserName.bind(this) this.setSelectedHead = this.setSelectedHead.bind(this) this.setGifsLoading = this.setGifsLoading.bind(this) - this.selectedAddress = {} + this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress this.userName = "" this.chatId = '' this.myAddress = '' @@ -1301,7 +174,10 @@ class ChatPage extends LitElement { this.isUserDown = false this.isPasteMenuOpen = false this.chatEditorPlaceholder = "" - this.messagesRendered = [] + this.messagesRendered = { + messages: [], + type: '' + } this.repliedToMessageObj = null this.editedMessageObj = null this.iframeHeight = 42 @@ -1324,6 +200,7 @@ class ChatPage extends LitElement { this.groupAdmin = [] this.groupMembers = [] this.shifted = false + this.shiftedResources = false this.groupInfo = {} this.pageNumber = 1 this.userFoundModalOpen = false @@ -1335,6 +212,8 @@ class ChatPage extends LitElement { } this.webWorker = null this.webWorkerFile = null + this.webWorkerSortMessages = null + this.webWorkerDecodeMessages = null this.currentEditor = '_chatEditorDOM' this.initialChat = this.initialChat.bind(this) this.setOpenGifModal = this.setOpenGifModal.bind(this) @@ -1347,8 +226,39 @@ class ChatPage extends LitElement { offsetHeight: 0 } this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' + this.updateMessageHash = {} + this.addToUpdateMessageHashmap = this.addToUpdateMessageHashmap.bind(this) + this.getAfterMessages = this.getAfterMessages.bind(this) + this.oldMessages = [] + this.lastReadMessageTimestamp = 0 + this.initUpdate = this.initUpdate.bind(this) + this.messageQueue = [] + this.addToQueue = this.addToQueue.bind(this) + this.processQueue = this.processQueue.bind(this) + this.isInProcessQueue = false + this.nodeUrl = this.getNodeUrl(); + this.myNode = this.getMyNode(); } + getNodeUrl() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + return nodeUrl; + } + getMyNode() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + return myNode; + } + setOpenGifModal(value) { this.openGifModal = value } @@ -1357,6 +267,10 @@ class ChatPage extends LitElement { this.shifted = value === (false || true) ? value : !this.shifted this.requestUpdate() } + _toggleResources(value) { + this.shiftedResources = value === (false || true) ? value : !this.shiftedResources + this.requestUpdate() + } setOpenTipUser(props) { this.openTipUser = props @@ -1392,23 +306,117 @@ class ChatPage extends LitElement { this.gifsLoading = props } + addToQueue(outSideMsg, messageQueue) { + // Push the new message object to the queue + + this.messageQueue = [...messageQueue, { ...outSideMsg, timestamp: Date.now()}]; + + // Start processing the queue only if the message we just added is the only one in the queue + // This ensures that the queue processing starts only once, even if this method is called multiple times + if (this.messageQueue.length === 1) { + this.processQueue(); + } + + // Notify Lit to update/render due to the property change + this.requestUpdate(); + } + + async processQueue() { + if (this.messageQueue.length === 0) return; + const currentMessage = this.messageQueue[0]; + try { + const res = await this.sendMessage(currentMessage); + if(res === true) { + this.messageQueue = this.messageQueue.slice(1); + } else { + throw new Error('failed') + } + + if (this.messageQueue.length > 0) { + setTimeout(() => this.processQueue(), 2000); // Wait for 10 seconds before retrying + // setTimeout(() => this.processQueue(), 0); // Process the next message immediately + } + } catch (error) { + console.error("Failed to send message:", error); + setTimeout(() => this.processQueue(), 10000); // Wait for 10 seconds before retrying + } + } + + async getLastestMessages(){ + try { + let getInitialMessages = [] + if (this.isReceipient) { + + + + getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` + }) + + + + } else { + getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` + }) + + } + this.processMessages(getInitialMessages, true, false) + } catch (error) { + console.log + } + } + + async copyJoinGroupLinkToClipboard() { + try { + const link = `qortal://use-group/action-join/groupid-${this.groupInfo.groupId}` + let copyString1 = get('chatpage.cchange97'); + await navigator.clipboard.writeText(link); + parentEpml.request('showSnackBar', `${copyString1}`); + } catch (err) { + let copyString2 = get('walletpage.wchange39'); + parentEpml.request('showSnackBar', `${copyString2}`); + console.error('Copy to clipboard error:', err); + } + } + + render() { return html`
- ${(!this.isReceipient && +this._chatId !== 0) ? - html` + style="grid-template-rows: minmax(40px, auto) minmax(6%, 92vh) minmax(40px, auto); flex: 3;"> +
-
+
{ + if(+this._chatId === 0 || this.isReceipient)return + this._toggle() + }} style=${`height: 100%; display: flex; align-items: center;flex-grow: 1; cursor: pointer; cursor: ${+this._chatId === 0 || this.isReceipient ? 'default': 'pointer'}; user-select: none`}> + ${this.isReceipient ? '' : +this._chatId === 0 ? html` +

Qortal General Chat

+ ` : html`

${this.groupInfo && this.groupInfo.groupName}

+ `} +
- + ${(!this.isReceipient && +this._chatId !== 0 && this.groupInfo.isOpen) ? + html` + link + ` + : ''} + photo_library + ${(!this.isReceipient && +this._chatId !== 0) ? + html` + groups + ` + : ''}
- ` : null} +
${this.isLoadingMessages ? html` @@ -1445,10 +453,16 @@ class ChatPage extends LitElement { class='last-message-ref' style=${(this.lastMessageRefVisible && !this.imageFile && !this.openGifModal) ? 'opacity: 1;' : 'opacity: 0;'}> { - this.shadowRoot.querySelector("chat-scroller").shadowRoot.getElementById("downObserver") + const chatScrollerElement = this.shadowRoot.querySelector('chat-scroller'); + if (chatScrollerElement && chatScrollerElement.disableAddingNewMessages) { + this.getLastestMessages() + } else { + this.shadowRoot.querySelector("chat-scroller").shadowRoot.getElementById("downObserver") .scrollIntoView({ behavior: 'smooth', }); + } + }}>
@@ -1527,6 +541,7 @@ class ChatPage extends LitElement { ?openGifModal=${this.openGifModal} .setOpenGifModal=${(val) => this.setOpenGifModal(val)} chatId=${this.chatId} + .messageQueue=${this.messageQueue} >
@@ -1569,7 +584,7 @@ class ChatPage extends LitElement {
${this.imageFile && html` - dialog-img + dialog-img `}
+
+ this.getMoreMembers(val)} + .toggle=${(val) => this._toggleResources(val)} + .selectedAddress=${this.selectedAddress} + .groupMembers=${this.groupMembers} + .groupAdmin=${this.groupAdmin} + .leaveGroupObj=${this.groupInfo} + .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} + .setOpenTipUser=${(val) => this.setOpenTipUser(val)} + .setOpenUserInfo=${(val) => this.setOpenUserInfo(val)} + .setUserName=${(val) => this.setUserName(val)} + _chatId=${ifDefined(this._chatId)} + chatId=${this.chatId} + ?isreceipient=${this.isReceipient} + .repost=${this.insertFile} + > + +
` } @@ -1864,23 +898,32 @@ class ChatPage extends LitElement { address: member.member, name: name ? name : undefined } - } catch (error) { - } + } catch (error) { /* empty */ } return memberItem }) const membersWithName = await Promise.all(getMembersWithName) this.groupMembers = [...this.groupMembers, ...membersWithName] this.pageNumber = this.pageNumber + 1 - } catch (error) { - } + } catch (error) { /* empty */ } } async connectedCallback() { super.connectedCallback() await this.initUpdate() - this.webWorker = new WebWorker() - this.webWorkerFile = new WebWorkerFile() + if(!this.webWorker){ + this.webWorker = new WebWorker() + } + if(!this.webWorkerFile){ + this.webWorkerFile = new WebWorkerFile() + } + if(!this.webWorkerSortMessages){ + this.webWorkerSortMessages = new WebWorkerSortMessages() + + } + if(!this.webWorkerDecodeMessages){ + this.webWorkerDecodeMessages = new WebWorkerDecodeMessages() + } await this.getUpdateCompleteTextEditor() const elementChatId = this.shadowRoot.getElementById('_chatEditorDOM').shadowRoot.getElementById('_chatEditorDOM') @@ -1984,14 +1027,7 @@ class ChatPage extends LitElement { document.addEventListener('keydown', this.initialChat) document.addEventListener('paste', this.pasteImage) - if (this.chatId) { - window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ - key: this.chatId, - timestamp: Date.now() - })) - } - - let callback = (entries, observer) => { + let callback = (entries) => { entries.forEach(entry => { if (entry.isIntersecting) { @@ -2034,6 +1070,9 @@ class ChatPage extends LitElement { if (this.webWorkerFile) { this.webWorkerFile.terminate() } + if(this.webWorkerSortMessages){ + this.webWorkerSortMessages.terminate() + } if (this.editor) { this.editor.destroy() } @@ -2046,20 +1085,12 @@ class ChatPage extends LitElement { document.removeEventListener('keydown', this.initialChat) document.removeEventListener('paste', this.pasteImage) - - if (this.chatId) { - window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ - key: this.chatId, - timestamp: Date.now() - })) - } } initialChat(e) { if (this.editor && !this.editor.isFocused && this.currentEditor === '_chatEditorDOM' && !this.openForwardOpen && !this.openTipUser && !this.openGifModal) { // WARNING: Deprecated methods from KeyBoard Event - if (e.code === "Space" || e.keyCode === 32 || e.which === 32) { - } else if (inputKeyCodes.includes(e.keyCode)) { + if (e.code === "Space" || e.keyCode === 32 || e.which === 32) { /* empty */ } else if (inputKeyCodes.includes(e.keyCode)) { this.editor.commands.insertContent(e.key) this.editor.commands.focus('end') } else { @@ -2067,6 +1098,7 @@ class ChatPage extends LitElement { } } } + async pasteImage(e) { const event = e @@ -2075,13 +1107,13 @@ class ChatPage extends LitElement { const [firstItem] = dataTransfer.items const blob = firstItem.getAsFile() return blob - } catch (error) { - } + } catch (error) { /* empty */ } } if (event.clipboardData) { const blobFound = handleTransferIntoURL(event.clipboardData) if (blobFound) { - this.insertImage(blobFound) + this.insertFile(blobFound) + e.preventDefault(); return } else { const item_list = await navigator.clipboard.read() @@ -2100,7 +1132,8 @@ class ChatPage extends LitElement { let file = new File([blob], "name", { type: image_type }) - this.insertImage(file) + this.insertFile(file) + e.preventDefault(); } catch (error) { console.error(error) let errorMsg = get("chatpage.cchange81") @@ -2118,7 +1151,7 @@ class ChatPage extends LitElement { const findMessage = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById(message.signature) if (findMessage) { - findMessage.scrollIntoView({ behavior: 'smooth', block: 'center' }) + findMessage.scrollIntoView({ behavior: 'smooth', block: 'nearest' }) const findElement = findMessage.shadowRoot.querySelector('.message-parent') if (findElement) { findElement.classList.add('blink-bg') @@ -2126,16 +1159,14 @@ class ChatPage extends LitElement { findElement.classList.remove('blink-bg') }, 2000) } + const chatScrollerElement = this.shadowRoot.querySelector('chat-scroller'); + if (chatScrollerElement && chatScrollerElement.disableFetching) { + chatScrollerElement.disableFetching = false + } return } - if ((message.timestamp - this.messagesRendered[0].timestamp) > 86400000) { - let errorMsg = get("chatpage.cchange66") - parentEpml.request('showSnackBar', `${errorMsg}`) - return - } - - if ((message.timestamp - this.messagesRendered[0].timestamp) < 86400000) { + const findOriginalMessage = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById(clickedOnMessage.signature) if (findOriginalMessage) { const messageClientRect = findOriginalMessage.getBoundingClientRect() @@ -2146,16 +1177,21 @@ class ChatPage extends LitElement { top: messageClientRect.top, offsetHeight: findOriginalMessage.offsetHeight } + + await this.getOldMessageDynamic(0, clickedOnMessage.timestamp, message) + await this.getUpdateComplete() + + const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) + const findMessage2 = marginElements.find((item) => item.messageObj.signature === message.signature) || marginElements.find((item) => item.messageObj.originalSignature === message.signature) || marginElements.find((item) => item.messageObj.signature === message.originalSignature) || marginElements.find((item) => item.messageObj.originalSignature === message.originalSignature) + if (findMessage2) { + findMessage2.scrollIntoView({ block: 'center' }) } - await this.getOldMessageDynamic(0, this.messagesRendered[0].timestamp, message.timestamp - 7200000) - const findMessage = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById(message.signature) - if (findMessage) { + if (findMessage2) { this.isLoadingGoToRepliedMessage = { ...this.isLoadingGoToRepliedMessage, loading: false } - findMessage.scrollIntoView({ block: 'center' }) - const findElement = findMessage.shadowRoot.querySelector('.message-parent') + const findElement = findMessage2.shadowRoot.querySelector('.message-parent') if (findElement) { findElement.classList.add('blink-bg') setTimeout(() => { @@ -2226,9 +1262,8 @@ class ChatPage extends LitElement { } delete message.reactions const stringifyMessageObject = JSON.stringify(message) - this.sendMessage(stringifyMessageObject, undefined, '', true) - } catch (error) { - } + this.sendMessage({messageText: stringifyMessageObject, chatReference: undefined, isForward: true}) + } catch (error) { /* empty */ } } showLastMessageRefScroller(props) { @@ -2236,6 +1271,11 @@ class ChatPage extends LitElement { } insertFile(file) { + if(file.identifier){ + this.imageFile = file + this.currentEditor = 'newChat' + return + }else if (file.type.includes('image')) { this.imageFile = file this.currentEditor = 'newChat' @@ -2265,6 +1305,10 @@ class ChatPage extends LitElement { } async initUpdate() { + if (this.webSocket) { + this.webSocket.close(1000, 'switch chat') + this.webSocket = '' + } this.pageNumber = 1 const getAddressPublicKey = () => { @@ -2336,8 +1380,7 @@ class ChatPage extends LitElement { address: member.member, name: name ? name : undefined } - } catch (error) { - } + } catch (error) { /* empty */ } return memberItem }) @@ -2351,22 +1394,22 @@ class ChatPage extends LitElement { address: member.member, name: name ? name : undefined } - } catch (error) { - } + } catch (error) { /* empty */ } return memberItem }) const membersWithName = await Promise.all(getMembersWithName) this.groupAdmin = membersAdminsWithName this.groupMembers = membersWithName this.groupInfo = getGroupInfo - } catch (error) { - } + } catch (error) { /* empty */ } } } async firstUpdated() { this.changeTheme() - + + // this.processQueue(); + window.addEventListener('storage', () => { const checkLanguage = localStorage.getItem('qortalLanguage') const checkTheme = localStorage.getItem('qortalTheme') @@ -2381,19 +1424,9 @@ class ChatPage extends LitElement { document.querySelector('html').setAttribute('theme', this.theme) }) - parentEpml.ready().then(() => { - parentEpml.subscribe('selected_address', async selectedAddress => { - this.selectedAddress = {} - selectedAddress = JSON.parse(selectedAddress) - if (!selectedAddress || Object.entries(selectedAddress).length === 0) return - this.selectedAddress = selectedAddress - }) - parentEpml.request('apiCall', { - url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}` - }).then(res => { - this.balance = res - }) - }) + + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 + parentEpml.imReady() const isEnabledChatEnter = localStorage.getItem('isEnabledChatEnter') @@ -2431,6 +1464,36 @@ class ChatPage extends LitElement { this.editor.setEditable(true) } } + if(changedProperties && changedProperties.has('chatId') && this.webSocket){ + const previousChatId = changedProperties.get('chatId'); + + this.isLoadingMessages = true + this.initUpdate() + + + if (previousChatId) { + window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + key: previousChatId, + timestamp: Date.now() + })) + } + } + } + + + shouldUpdate(changedProperties) { + if(changedProperties.has('chatId')){ + return true + } + if (changedProperties.has('setActiveChatHeadUrl')) { + return false + } + if (changedProperties.has('setOpenPrivateMessage')) { + return false + } + + return true + } async getName(recipient) { @@ -2483,8 +1546,10 @@ class ChatPage extends LitElement { this.setRepliedToMessageObj(val)} .setEditedMessageObj=${(val) => this.setEditedMessageObj(val)} .sendMessage=${(val) => this._sendMessage(val)} @@ -2502,7 +1567,11 @@ class ChatPage extends LitElement { ?openTipUser=${this.openTipUser} .selectedHead=${this.selectedHead} .goToRepliedMessage=${(val, val2) => this.goToRepliedMessage(val, val2)} - .getOldMessageAfter=${(val) => this.getOldMessageAfter(val)} + .updateMessageHash=${this.updateMessageHash} + .clearUpdateMessageHashmap=${this.clearUpdateMessageHashmap} + .messageQueue=${this.messageQueue} + loggedInUserName=${this.loggedInUserName} + loggedInUserAddress=${this.loggedInUserAddress} > ` @@ -2519,6 +1588,13 @@ class ChatPage extends LitElement { return true } + async getUpdateCompleteMessages() { + await super.getUpdateComplete() + const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) + await Promise.all(marginElements.map(el => el.updateComplete)) + return true + } + async getUpdateCompleteTextEditor() { await super.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelectorAll('chat-text-editor')) @@ -2537,94 +1613,179 @@ class ChatPage extends LitElement { }) } - async getOldMessageDynamic(limit, before, after) { - + async getOldMessageDynamic(limit, timestampClickedOnMessage, messageToGoTo) { + const findMsg = await parentEpml.request("apiCall", { + type: "api", + url: `/chat/message/${messageToGoTo.originalSignature || messageToGoTo.signature}?encoding=BASE64`, + }) + if(!findMsg) return null if (this.isReceipient) { - const getInitialMessages = await parentEpml.request('apiCall', { + const getInitialMessagesBefore = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${limit}&reverse=true&before=${before}&after=${after}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${20}&reverse=true&before=${findMsg.timestamp}&haschatreference=false&encoding=BASE64` }) - - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${20}&reverse=false&after=${findMsg.timestamp - 1000}&haschatreference=false&encoding=BASE64` }) + const getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = () => { + rej() + + } + }) - const replacedMessages = await replaceMessagesEdited({ + + queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, parentEpml, isReceipient: this.isReceipient, decodeMessageFunc: this.decodeMessage, - _publicKey: this._publicKey - }) + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); - this.messagesRendered = [...replacedMessages, ...this.messagesRendered].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + + let list = [...decodeMsgs] + + await new Promise((res) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + + list = e.data + res() + + } + }) + + this.messagesRendered = { + messages: list, + type: 'inBetween', + message: messageToGoTo + } this.isLoadingOldMessages = false - await this.getUpdateComplete() - const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') - - if (viewElement) { - viewElement.scrollTop = 200 - } + } else { - const getInitialMessages = await parentEpml.request('apiCall', { + const getInitialMessagesBefore = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${limit}&reverse=true&before=${before}&after=${after}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${20}&reverse=true&before=${findMsg.timestamp}&haschatreference=false&encoding=BASE64` }) - - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${20}&reverse=false&after=${findMsg.timestamp - 1000}&haschatreference=false&encoding=BASE64` }) + const getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] - const replacedMessages = await replaceMessagesEdited({ + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = () => { + rej() + + } + }) + + queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, parentEpml, isReceipient: this.isReceipient, decodeMessageFunc: this.decodeMessage, - _publicKey: this._publicKey - }) + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + + + let list = [...decodeMsgs] + + await new Promise((res) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + + list = e.data + res() + + } + }) + + this.messagesRendered = { + messages: list, + type: 'inBetween', + signature: messageToGoTo.signature + } - this.messagesRendered = [...replacedMessages, ...this.messagesRendered].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) this.isLoadingOldMessages = false - await this.getUpdateComplete() - const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') - - if (viewElement) { - viewElement.scrollTop = 200 - } + } } async getOldMessage(scrollElement) { + if(!scrollElement || !scrollElement.messageObj || !scrollElement.messageObj.timestamp){ + this.messagesRendered = { + messages: [], + type: 'old', + el: scrollElement + } + return + } if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` }) - - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) - - const replacedMessages = await replaceMessagesEdited({ + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = () => { + rej() + + } + }) + + queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, parentEpml, isReceipient: this.isReceipient, decodeMessageFunc: this.decodeMessage, - _publicKey: this._publicKey - }) - - this.messagesRendered = [...replacedMessages, ...this.messagesRendered].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + + let list = [...decodeMsgs] + + + + this.messagesRendered = { + messages: list, + type: 'old', + el: scrollElement + } this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2638,27 +1799,45 @@ class ChatPage extends LitElement { } else { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` }) - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) + let decodeMsgs = [] - const replacedMessages = await replaceMessagesEdited({ + + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = () => { + rej() + + } + }) + + + queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, parentEpml, isReceipient: this.isReceipient, decodeMessageFunc: this.decodeMessage, - _publicKey: this._publicKey - }) - - this.messagesRendered = [...replacedMessages, ...this.messagesRendered].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) - - this.isLoadingOldMessages = false + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + let list = [...decodeMsgs] + + + + this.messagesRendered = { + messages: list, + type: 'old', + el: scrollElement + } + // this.isLoadingOldMessages = false await this.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) const findElement = marginElements.find((item) => item.messageObj.signature === scrollElement.messageObj.signature) @@ -2668,30 +1847,66 @@ class ChatPage extends LitElement { } } } - - async getOldMessageAfter(scrollElement) { + async getAfterMessages(scrollElement) { + if(!scrollElement || !scrollElement.messageObj || !scrollElement.messageObj.timestamp){ + this.messagesRendered = { + messages: [], + type: 'new', + } + return + } + const timestamp = scrollElement.messageObj.timestamp + if (this.isReceipient) { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=20&reverse=true&afer=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=false&after=${timestamp}&haschatreference=false&encoding=BASE64` }) - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = () => { + rej() + + } + }) - const replacedMessages = await replaceMessagesEdited({ + + queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, parentEpml, isReceipient: this.isReceipient, decodeMessageFunc: this.decodeMessage, - _publicKey: this._publicKey - }) - - this.messagesRendered = [...this.messagesRendered, ...replacedMessages].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + + let list = [ ...decodeMsgs] + + await new Promise((res) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + + list = e.data + res() + + } + }) + + this.messagesRendered = { + messages: list, + type: 'new' + } + this.isLoadingOldMessages = false await this.getUpdateComplete() @@ -2705,82 +1920,209 @@ class ChatPage extends LitElement { } else { const getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=20&reverse=true&after=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?txGroupId=${Number(this._chatId)}&limit=${chatLimit}&reverse=false&after=${timestamp}&haschatreference=false&encoding=BASE64` }) + let decodeMsgs = [] + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: getInitialMessages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodeMsgs = e.data + res() + + } + this.webWorkerDecodeMessages.onerror = () => { + rej() + + } + }) - const decodeMsgs = getInitialMessages.map((eachMessage) => { - return this.decodeMessage(eachMessage) - }) - - const replacedMessages = await replaceMessagesEdited({ + + queue.push(() => replaceMessagesEdited({ decodedMessages: decodeMsgs, parentEpml, isReceipient: this.isReceipient, decodeMessageFunc: this.decodeMessage, - _publicKey: this._publicKey - }) + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + + + + let list = [...decodeMsgs] + + await new Promise((res) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + + list = e.data + res() + + } + }) + + this.messagesRendered = { + messages: list, + type: 'new' + } - this.messagesRendered = [...this.messagesRendered, ...replacedMessages].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) this.isLoadingOldMessages = false await this.getUpdateComplete() const marginElements = Array.from(this.shadowRoot.querySelector('chat-scroller').shadowRoot.querySelectorAll('message-template')) const findElement = marginElements.find((item) => item.messageObj.signature === scrollElement.messageObj.signature) - if (findElement) { findElement.scrollIntoView({ behavior: 'auto', block: 'center' }) } } } - async processMessages(messages, isInitial) { + async addToUpdateMessageHashmap(array){ + + + + const newObj = {} + + array.forEach((item)=> { + const signature = item.originalSignature || item.signature + newObj[signature] = item + }) + this.updateMessageHash = { + ...this.updateMessageHash, + ...newObj + } + this.requestUpdate() + await this.getUpdateComplete() + + } + + async clearUpdateMessageHashmap(){ + this.updateMessageHash = {} + this.requestUpdate() + } + + findContent(identifier, data) { + const [type, id] = identifier.split('/'); + + if (type === 'group') { + for (let group of data.groups) { + if (group.groupId === parseInt(id, 10)) { + return group; + } + } + } else if (type === 'direct') { + for (let direct of data.direct) { + if (direct.address === id) { + return direct; + } + } + } + return null; + } + + + + async processMessages(messages, isInitial, isUnread, count) { const isReceipient = this.chatId.includes('direct') - const decodedMessages = messages.map((eachMessage) => { - - if (eachMessage.isText === true) { - this.messageSignature = eachMessage.signature - let _eachMessage = this.decodeMessage(eachMessage) - return _eachMessage - } else { - this.messageSignature = eachMessage.signature - let _eachMessage = this.decodeMessage(eachMessage) - return _eachMessage + let decodedMessages = [] + if(!this.webWorkerDecodeMessages){ + this.webWorkerDecodeMessages = new WebWorkerDecodeMessages() + } + if(!this.webWorkerSortMessages){ + this.webWorkerSortMessages = new WebWorkerSortMessages() + } + await new Promise((res, rej) => { + this.webWorkerDecodeMessages.postMessage({messages: messages, isReceipient: this.isReceipient, _publicKey: this._publicKey, privateKey: window.parent.reduxStore.getState().app.selectedAddress.keyPair.privateKey }); + + this.webWorkerDecodeMessages.onmessage = e => { + decodedMessages = e.data + res() + } - }) + this.webWorkerDecodeMessages.onerror = () => { + rej() + + } + }) if (isInitial) { this.chatEditorPlaceholder = await this.renderPlaceholder() - const replacedMessages = await replaceMessagesEdited({ - decodedMessages: decodedMessages, - parentEpml, - isReceipient: isReceipient, - decodeMessageFunc: this.decodeMessage, - _publicKey: this._publicKey - }) + - this._messages = replacedMessages.sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + try { + queue.push(() => replaceMessagesEdited({ + decodedMessages: decodedMessages, + parentEpml, + isReceipient: isReceipient, + decodeMessageFunc: this.decodeMessage, + _publicKey: this._publicKey, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); + } catch (error) { + console.log({error}) + } + + + + + let list = decodedMessages + + await new Promise((res) => { + + this.webWorkerSortMessages.postMessage({list}); + + this.webWorkerSortMessages.onmessage = e => { + + list = e.data + res() + + } + }) + + this._messages = list // TODO: Determine number of initial messages by screen height... - this.messagesRendered = this._messages + // this.messagesRendered = this._messages + const lastReadMessageTimestamp = this.lastReadMessageTimestamp + + + if(isUnread){ + + this.messagesRendered = { + messages: this._messages, + type: 'initialLastSeen', + lastReadMessageTimestamp, + count + } + + window.parent.reduxStore.dispatch(window.parent.reduxAction.addChatLastSeen({ + key: this.chatId, + timestamp: Date.now() + })) + } else { + this.messagesRendered = { + messages: this._messages, + type: 'initial' + } + } + this.isLoadingMessages = false setTimeout(() => this.downElementObserver(), 500) } else { - const replacedMessages = await replaceMessagesEdited({ + + queue.push(() => replaceMessagesEdited({ decodedMessages: decodedMessages, parentEpml, isReceipient: isReceipient, decodeMessageFunc: this.decodeMessage, _publicKey: this._publicKey, - isNotInitial: true - }) + isNotInitial: true, + addToUpdateMessageHashmap: this.addToUpdateMessageHashmap + })); - const renderEachMessage = replacedMessages.map(async (msg) => { + const renderEachMessage = decodedMessages.map(async (msg) => { await this.renderNewMessage(msg) }) @@ -2793,11 +2135,7 @@ class ChatPage extends LitElement { })) } - // this.newMessages = this.newMessages.concat(_newMessages) - this.messagesRendered = [...this.messagesRendered].sort(function (a, b) { - return a.timestamp - - b.timestamp - }) + } } @@ -2839,37 +2177,49 @@ class ChatPage extends LitElement { async renderNewMessage(newMessage) { if (newMessage.chatReference) { - const findOriginalMessageIndex = this.messagesRendered.findIndex(msg => msg.signature === newMessage.chatReference || (msg.chatReference && msg.chatReference === newMessage.chatReference)) - if (findOriginalMessageIndex !== -1 && this.messagesRendered[findOriginalMessageIndex].sender === newMessage.sender) { - const newMessagesRendered = [...this.messagesRendered] - newMessagesRendered[findOriginalMessageIndex] = { - ...newMessage, timestamp: newMessagesRendered[findOriginalMessageIndex].timestamp, senderName: newMessagesRendered[findOriginalMessageIndex].senderName, - sender: newMessagesRendered[findOriginalMessageIndex].sender, editedTimestamp: newMessage.timestamp - } - this.messagesRendered = newMessagesRendered - await this.getUpdateComplete() + this.messagesRendered = { + messages: [newMessage], + type: 'update', } return } - const viewElement = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') + let viewElement = this.shadowRoot.querySelector('chat-scroller') + if(viewElement){ + viewElement = viewElement.shadowRoot.getElementById('viewElement') + } else { + viewElement = null + } if (newMessage.sender === this.selectedAddress.address) { - this.messagesRendered = [...this.messagesRendered, newMessage] + + this.messagesRendered = { + messages: [newMessage], + type: 'newComingInAuto', + } await this.getUpdateComplete() - viewElement.scrollTop = viewElement.scrollHeight + // viewElement.scrollTop = viewElement.scrollHeight } else if (this.isUserDown) { + this.messagesRendered = { + messages: [newMessage], + type: 'newComingInAuto', + } // Append the message and scroll to the bottom if user is down the page - this.messagesRendered = [...this.messagesRendered, newMessage] + // this.messagesRendered = [...this.messagesRendered, newMessage] await this.getUpdateComplete() - - viewElement.scrollTop = viewElement.scrollHeight + if(viewElement){ + viewElement.scrollTop = viewElement.scrollHeight + } + } else { - this.messagesRendered = [...this.messagesRendered, newMessage] + this.messagesRendered = { + messages: [newMessage], + type: 'newComingInAuto', + } await this.getUpdateComplete() this.showNewMessageBar() @@ -2931,7 +2281,9 @@ class ChatPage extends LitElement { } else { // Fallback to http directSocketLink = `ws://${nodeUrl}/websockets/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&encoding=BASE64&limit=1` - } + } + + this.webSocket = new WebSocket(directSocketLink) // Open Connection @@ -2946,25 +2298,52 @@ class ChatPage extends LitElement { directSocketTimeout = setTimeout(pingDirectSocket, 45000) return } + if (initial === 0) { + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 if (noInitial) return - const cachedData = null let getInitialMessages = [] - if (cachedData && cachedData.length !== 0) { - const lastMessage = cachedData[cachedData.length - 1] - const newMessages = await parentEpml.request('apiCall', { + let count = 0 + let isUnread = false + + const chatId = this.chatId + const findContent = this.chatHeads.find((item)=> item.url === chatId) + const chatInfoTimestamp = findContent.timestamp || 0 + const lastReadMessageTimestamp = this.lastReadMessageTimestamp + + + if(lastReadMessageTimestamp && chatInfoTimestamp){ + if(lastReadMessageTimestamp < chatInfoTimestamp){ + isUnread = true + } + } + if(isUnread){ + const getInitialMessagesBefore = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimitHalf}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` }) - getInitialMessages = [...cachedData, ...newMessages].slice(-20) + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimitHalf}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` + }) + getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] + const lastMessage = getInitialMessagesAfter.at(-1) + if(lastMessage){ + count = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages/count?after=${lastMessage.timestamp}&involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=false` + }) + } } else { getInitialMessages = await parentEpml.request('apiCall', { type: 'api', - url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=20&reverse=true&haschatreference=false&encoding=BASE64` + url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${cid}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` }) } + + - this.processMessages(getInitialMessages, true) + this.processMessages(getInitialMessages, true, isUnread, count) initial = initial + 1 @@ -2973,15 +2352,16 @@ class ChatPage extends LitElement { if (e.data) { this.processMessages(JSON.parse(e.data), false) } - } catch (error) { - } + } catch (error) { /* empty */ } } } // Closed Event this.webSocket.onclose = (e) => { clearTimeout(directSocketTimeout) + if (e.reason === 'switch chat') return + restartDirectWebSocket() } @@ -3015,7 +2395,7 @@ class ChatPage extends LitElement { let groupId = Number(gId) let initial = 0 - + let count = 0 let groupSocketTimeout let myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] @@ -3029,7 +2409,7 @@ class ChatPage extends LitElement { // Fallback to http groupSocketLink = `ws://${nodeUrl}/websockets/chat/messages?txGroupId=${groupId}&encoding=BASE64&limit=1` } - + this.webSocket = new WebSocket(groupSocketLink) // Open Connection @@ -3045,26 +2425,53 @@ class ChatPage extends LitElement { return } if (initial === 0) { + this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatId) || 0 if (noInitial) return - const cachedData = null let getInitialMessages = [] - if (cachedData && cachedData.length !== 0) { + const lastReadMessageTimestamp = this.lastReadMessageTimestamp - const lastMessage = cachedData[cachedData.length - 1] + let isUnread = false + + const chatId = this.chatId + const findContent = this.chatHeads.find((item)=> item.url === chatId) + const chatInfoTimestamp = findContent.timestamp || 0 - const newMessages = await parentEpml.request('apiCall', { - type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true&after=${lastMessage.timestamp}&haschatreference=false&encoding=BASE64` - }) - getInitialMessages = [...cachedData, ...newMessages].slice(-20) - } else { - getInitialMessages = await parentEpml.request('apiCall', { - type: 'api', - url: `/chat/messages?txGroupId=${groupId}&limit=20&reverse=true&haschatreference=false&encoding=BASE64` - }) - } + if(lastReadMessageTimestamp && chatInfoTimestamp){ + if(lastReadMessageTimestamp < chatInfoTimestamp){ + isUnread = true + } + } + if(isUnread){ + - this.processMessages(getInitialMessages, true) + const getInitialMessagesBefore = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimitHalf}&reverse=true&before=${lastReadMessageTimestamp}&haschatreference=false&encoding=BASE64` + }) + const getInitialMessagesAfter = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimitHalf}&reverse=false&after=${lastReadMessageTimestamp - 1000}&haschatreference=false&encoding=BASE64` + }) + getInitialMessages = [...getInitialMessagesBefore, ...getInitialMessagesAfter] + const lastMessage = getInitialMessagesAfter.at(-1) + if(lastMessage){ + count = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages/count?after=${lastMessage.timestamp}&txGroupId=${groupId}&limit=20&reverse=false` + }) + } + + } else { + getInitialMessages = await parentEpml.request('apiCall', { + type: 'api', + url: `/chat/messages?txGroupId=${groupId}&limit=${chatLimit}&reverse=true&haschatreference=false&encoding=BASE64` + }) + } + + + + + this.processMessages(getInitialMessages, true, isUnread, count) initial = initial + 1 } else { @@ -3072,8 +2479,7 @@ class ChatPage extends LitElement { if (e.data) { this.processMessages(JSON.parse(e.data), false) } - } catch (error) { - } + } catch (error) { /* empty */ } } } @@ -3124,11 +2530,16 @@ class ChatPage extends LitElement { } } - async _sendMessage(outSideMsg, msg) { + async _sendMessage(outSideMsg, msg, messageQueue) { + const _chatId= this._chatId + const isReceipient= this.isReceipient + let _publicKey= this._publicKey + const attachment= this.attachment + try { if (this.isReceipient) { let hasPublicKey = true - if (!this._publicKey.hasPubKey) { + if (!_publicKey.hasPubKey) { hasPublicKey = false try { const res = await parentEpml.request('apiCall', { @@ -3136,20 +2547,19 @@ class ChatPage extends LitElement { url: `/addresses/publickey/${this.selectedAddress.address}` }) if (res.error === 102) { - this._publicKey.key = '' - this._publicKey.hasPubKey = false + _publicKey.key = '' + _publicKey.hasPubKey = false } else if (res !== false) { - this._publicKey.key = res - this._publicKey.hasPubKey = true + _publicKey.key = res + _publicKey.hasPubKey = true hasPublicKey = true } else { - this._publicKey.key = '' - this._publicKey.hasPubKey = false + _publicKey.key = '' + _publicKey.hasPubKey = false } - } catch (error) { - } + } catch (error) { /* empty */ } - if (!hasPublicKey || !this._publicKey.hasPubKey) { + if (!hasPublicKey || !_publicKey.hasPubKey) { let err4string = get("chatpage.cchange39") parentEpml.request('showSnackBar', `${err4string}`) return @@ -3164,7 +2574,7 @@ class ChatPage extends LitElement { // create new var called repliedToData and use that to modify the UI // find specific object property in local let typeMessage = 'regular' - this.isLoading = true + // this.isLoading = true const trimmedMessage = msg const getName = async (recipient) => { @@ -3231,7 +2641,7 @@ class ChatPage extends LitElement { compressedFile = file resolve() }, - error(err) { + error() { }, }) }) @@ -3280,11 +2690,11 @@ class ChatPage extends LitElement { isImageDeleted: true } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage, chatReference) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'deleteAttachment') { this.isDeletingAttachment = true let compressedFile = '' - var str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg==" + const str = "iVBORw0KGgoAAAANSUhEUgAAAsAAAAGMAQMAAADuk4YmAAAAA1BMVEX///+nxBvIAAAAAXRSTlMAQObYZgAAADlJREFUeF7twDEBAAAAwiD7p7bGDlgYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAGJrAABgPqdWQAAAABJRU5ErkJggg==" const userName = outSideMsg.name const identifier = outSideMsg.identifier @@ -3328,7 +2738,7 @@ class ChatPage extends LitElement { compressedFile = file resolve() }, - error(err) { + error() { }, }) }) @@ -3378,9 +2788,11 @@ class ChatPage extends LitElement { isAttachmentDeleted: true } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage, chatReference) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'image') { - this.isUploadingImage = true + if(!this.imageFile.identifier){ + this.isUploadingImage = true + } const userName = await getName(this.selectedAddress.address) if (!userName) { parentEpml.request('showSnackBar', get("chatpage.cchange27")) @@ -3389,84 +2801,103 @@ class ChatPage extends LitElement { this.imageFile = null return } - const arbitraryFeeData = await modalHelper.getArbitraryFee() - const res = await modalHelper.showModalAndWaitPublish( - { - feeAmount: arbitraryFeeData.feeToShow + + + let service = "QCHAT_IMAGE" + let name = userName + let identifier + if(this.imageFile.identifier){ + identifier = this.imageFile.identifier + name = this.imageFile.name + service = this.imageFile.service + } else { + const arbitraryFeeData = await modalHelper.getArbitraryFee() + const res = await modalHelper.showModalAndWaitPublish( + { + feeAmount: arbitraryFeeData.feeToShow + } + ); + if (res.action !== 'accept') throw new Error('User declined publish') + + if (this.webWorkerFile) { + this.webWorkerFile.terminate() + this.webWorkerFile = null } - ); - if (res.action !== 'accept') throw new Error('User declined publish') - - if (this.webWorkerFile) { - this.webWorkerFile.terminate() - this.webWorkerFile = null - } - - this.webWorkerFile = new WebWorkerFile() - - const image = this.imageFile - const id = this.uid.rnd() - const identifier = `qchat_${id}` - let compressedFile = '' - await new Promise(resolve => { - new Compressor(image, { - quality: .6, - maxWidth: 1200, - success(result) { - const file = new File([result], "name", { - type: image.type - }) - compressedFile = file - resolve() - }, - error(err) { - }, + + this.webWorkerFile = new WebWorkerFile() + const image = this.imageFile + const id = this.uid.rnd() + let groupPart + if(this.isReceipient){ + groupPart = `direct_${generateIdFromAddresses(this._chatId, this.selectedAddress.address)}` + } else { + groupPart = `group_${this._chatId}` + } + identifier = `qchat_${groupPart}_${id}` + let compressedFile = '' + await new Promise(resolve => { + new Compressor(image, { + quality: .6, + maxWidth: 1200, + mimeType: 'image/webp', + success(result) { + const file = new File([result], "name", { + type: 'image/webp' + }) + compressedFile = file + resolve() + }, + error() { + }, + }) }) - }) - const fileSize = compressedFile.size - if (fileSize > 500000) { - parentEpml.request('showSnackBar', get("chatpage.cchange26")) - this.isLoading = false - this.isUploadingImage = false - return + const fileSize = compressedFile.size + if (fileSize > 500000) { + parentEpml.request('showSnackBar', get("chatpage.cchange26")) + this.isLoading = false + this.isUploadingImage = false + return + } + + try { + + await publishData({ + registeredName: userName, + file: compressedFile, + service: 'QCHAT_IMAGE', + identifier: identifier, + parentEpml, + metaData: undefined, + uploadType: 'file', + selectedAddress: this.selectedAddress, + worker: this.webWorkerFile, + withFee: true, + feeAmount: arbitraryFeeData.fee + }) + this.isUploadingImage = false + this.removeImage() + } catch (error) { + this.isLoading = false + this.isUploadingImage = false + return + } + } - - try { - - await publishData({ - registeredName: userName, - file: compressedFile, - service: 'QCHAT_IMAGE', - identifier: identifier, - parentEpml, - metaData: undefined, - uploadType: 'file', - selectedAddress: this.selectedAddress, - worker: this.webWorkerFile, - withFee: true, - feeAmount: arbitraryFeeData.fee - }) - this.isUploadingImage = false - this.removeImage() - } catch (error) { - this.isLoading = false - this.isUploadingImage = false - return - } - + const messageObject = { messageText: trimmedMessage, images: [{ - service: "QCHAT_IMAGE", - name: userName, - identifier: identifier + service: service, + name: name, + identifier: identifier, }], isImageDeleted: false, repliedTo: '', version: 3 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage) + this.removeImage() + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'gif') { const userName = await getName(this.selectedAddress.address) if (!userName) { @@ -3487,7 +2918,7 @@ class ChatPage extends LitElement { version: 3 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'attachment') { this.isUploadingAttachment = true const userName = await getName(this.selectedAddress.address) @@ -3504,8 +2935,8 @@ class ChatPage extends LitElement { this.webWorkerFile = new WebWorkerFile() - const attachment = this.attachment - const id = this.uid.rnd() + // const attachment = attachment + const id = this.uid() const identifier = `qchat_${id}` const fileSize = attachment.size if (fileSize > 1000000) { @@ -3556,7 +2987,7 @@ class ChatPage extends LitElement { version: 3 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (outSideMsg && outSideMsg.type === 'reaction') { const userName = await getName(this.selectedAddress.address) typeMessage = 'edit' @@ -3611,7 +3042,7 @@ class ChatPage extends LitElement { reactions } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage, chatReference) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (/^\s*$/.test(trimmedMessage)) { this.isLoading = false } else if (this.repliedToMessageObj) { @@ -3627,7 +3058,7 @@ class ChatPage extends LitElement { version: 3 } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else if (this.editedMessageObj) { typeMessage = 'edit' let chatReference = this.editedMessageObj.signature @@ -3650,7 +3081,7 @@ class ChatPage extends LitElement { isEdited: true } const stringifyMessageObject = JSON.stringify(messageObject) - this.sendMessage(stringifyMessageObject, typeMessage, chatReference) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } else { const messageObject = { messageText: trimmedMessage, @@ -3665,7 +3096,7 @@ class ChatPage extends LitElement { this.myTrimmedMeassage = stringifyMessageObject this.shadowRoot.getElementById('confirmDialog').open() } else { - this.sendMessage(stringifyMessageObject, typeMessage) + return this.sendMessage({messageText: stringifyMessageObject, typeMessage, chatReference: undefined, isForward: false, isReceipient, _chatId, _publicKey, messageQueue}) } } } catch (error) { @@ -3676,21 +3107,31 @@ class ChatPage extends LitElement { } - async sendMessage(messageText, typeMessage, chatReference, isForward) { - this.isLoading = true + async sendMessage({messageText, typeMessage, chatReference, isForward,isReceipient, _chatId, _publicKey, messageQueue}) { + if(messageQueue){ + this.addToQueue({messageText, typeMessage, chatReference, isForward, isReceipient, _chatId, _publicKey}, messageQueue); + this.resetChatEditor() + this.closeEditMessageContainer() + this.closeRepliedToContainer() + return + } + if(isForward){ + this.isLoading = true + } + let _reference = new Uint8Array(64) window.crypto.getRandomValues(_reference) let reference = window.parent.Base58.encode(_reference) const sendMessageRequest = async () => { - if (this.isReceipient === true) { + if (isReceipient === true) { let chatResponse = await parentEpml.request('chat', { type: 18, nonce: this.selectedAddress.nonce, params: { timestamp: Date.now(), - recipient: this._chatId, - recipientPublicKey: this._publicKey.key, + recipient: _chatId, + recipientPublicKey: _publicKey.key, hasChatReference: typeMessage === 'edit' ? 1 : 0, chatReference: chatReference, message: messageText, @@ -3700,14 +3141,14 @@ class ChatPage extends LitElement { isText: 1 } }) - _computePow(chatResponse) + return _computePow(chatResponse) } else { let groupResponse = await parentEpml.request('chat', { type: 181, nonce: this.selectedAddress.nonce, params: { timestamp: Date.now(), - groupID: Number(this._chatId), + groupID: Number(_chatId), hasReceipient: 0, hasChatReference: typeMessage === 'edit' ? 1 : 0, chatReference: chatReference, @@ -3718,7 +3159,7 @@ class ChatPage extends LitElement { isText: 1 } }) - _computePow(groupResponse) + return _computePow(groupResponse) } } @@ -3753,8 +3194,7 @@ class ChatPage extends LitElement { publicKey.key = '' publicKey.hasPubKey = false } - } catch (error) { - } + } catch (error) { /* empty */ } } if (!this.forwardActiveChatHeadUrl.selected && this.shadowRoot.getElementById("sendTo").value !== "") { @@ -3822,8 +3262,7 @@ class ChatPage extends LitElement { publicKey.key = '' publicKey.hasPubKey = false } - } catch (error) { - } + } catch (error) { /* empty */ } } const isRecipient = this.forwardActiveChatHeadUrl.url.includes('direct') === true ? true : false @@ -3907,29 +3346,40 @@ class ChatPage extends LitElement { }) getSendChatResponse(_response, isForward) + return _response } - const getSendChatResponse = (response, isForward, customErrorMessage) => { + const getSendChatResponse = (response, isForward) => { if (response === true) { - this.resetChatEditor() + // this.resetChatEditor() if (isForward) { let successString = get("blockpage.bcchange15") parentEpml.request('showSnackBar', `${successString}`) + this.resetChatEditor() + this.closeEditMessageContainer() + this.closeRepliedToContainer() + this.openForwardOpen = false + this.forwardActiveChatHeadUrl = { + url: "", + name: "", + selected: false + } + this.isLoading = false } - this.closeEditMessageContainer() - this.closeRepliedToContainer() - this.openForwardOpen = false - this.forwardActiveChatHeadUrl = { - url: "", - name: "", - selected: false - } + // this.closeEditMessageContainer() + // this.closeRepliedToContainer() + // this.openForwardOpen = false + // this.forwardActiveChatHeadUrl = { + // url: "", + // name: "", + // selected: false + // } } else if (response.error) { - parentEpml.request('showSnackBar', response.message) + // parentEpml.request('showSnackBar', response.message) } else { - let err2string = get("chatpage.cchange21") - parentEpml.request('showSnackBar', `${customErrorMessage || err2string}`) + // let err2string = get("chatpage.cchange21") + // parentEpml.request('showSnackBar', `${customErrorMessage || err2string}`) } if (isForward && response !== true) { this.isLoading = false @@ -3943,7 +3393,7 @@ class ChatPage extends LitElement { sendForwardRequest() return } - sendMessageRequest() + return sendMessageRequest() } /** @@ -3966,6 +3416,8 @@ class ChatPage extends LitElement { } downElementObserver() { + const chatscrollerEl = this.shadowRoot.querySelector('chat-scroller') + if(!chatscrollerEl) return const downObserver = this.shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('downObserver') const options = { diff --git a/plugins/plugins/core/components/ChatRightPanelResources.js b/plugins/plugins/core/components/ChatRightPanelResources.js new file mode 100644 index 00000000..23686aac --- /dev/null +++ b/plugins/plugins/core/components/ChatRightPanelResources.js @@ -0,0 +1,670 @@ +import { LitElement, html, css } from 'lit'; +import { Epml } from '../../../epml'; +import '@material/mwc-button'; +import '@material/mwc-dialog'; +import '@polymer/paper-spinner/paper-spinner-lite.js'; +import '@polymer/paper-progress/paper-progress.js'; +import '@material/mwc-icon'; +import '@vaadin/button'; +import './WrapperModal'; +import './TipUser'; +import './UserInfo/UserInfo'; +import './ChatImage'; +import './ReusableImage'; +import { + get, + translate, +} from 'lit-translate'; +import { generateIdFromAddresses } from '../../utils/id-generation'; + +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); + +class ChatRightPanelResources extends LitElement { + static get properties() { + return { + leaveGroupObj: { type: Object }, + error: { type: Boolean }, + chatHeads: { type: Array }, + groupAdmin: { attribute: false }, + groupMembers: { attribute: false }, + selectedHead: { type: Object }, + toggle: { attribute: false }, + getMoreMembers: { attribute: false }, + setOpenPrivateMessage: { attribute: false }, + userName: { type: String }, + walletBalance: { type: Number }, + sendMoneyLoading: { type: Boolean }, + btnDisable: { type: Boolean }, + errorMessage: { type: String }, + successMessage: { type: String }, + setOpenTipUser: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setUserName: { attribute: false }, + chatId: { type: String }, + _chatId: { type: String }, + isReceipient: { type: Boolean }, + images: { type: Array }, + viewImage: { type: Boolean }, + autoView: {type: Boolean}, + onlyMyImages: {type: Boolean}, + repost: {attribute: false} + }; + } + + constructor() { + super(); + this.leaveGroupObj = {}; + this.leaveFee = 0.001; + this.error = false; + this.chatHeads = []; + this.groupAdmin = []; + this.groupMembers = []; + this.observerHandler = this.observerHandler.bind(this); + this.getMoreImages = this.getMoreImages.bind(this); + this.viewElement = ''; + this.downObserverElement = ''; + + this.sendMoneyLoading = false; + this.btnDisable = false; + this.errorMessage = ''; + this.successMessage = ''; + + this.images = []; + this.viewImage = false; + this.myName = + window.parent.reduxStore.getState().app.accountInfo.names[0].name; + this.myAddress = + window.parent.reduxStore.getState().app.selectedAddress.address; + this.autoView =false + this.onlyMyImages = true + } + + static get styles() { + return css` + .top-bar-icon { + cursor: pointer; + height: 18px; + width: 18px; + transition: 0.2s all; + } + + .top-bar-icon:hover { + color: var(--black); + } + + .modal-button { + font-family: Roboto, sans-serif; + font-size: 16px; + color: var(--mdc-theme-primary); + background-color: transparent; + padding: 8px 10px; + border-radius: 5px; + border: none; + transition: all 0.3s ease-in-out; + } + + .close-row { + width: 100%; + display: flex; + justify-content: flex-end; + height: 50px; + flex: 0; + align-items: center; + } + + .container-body { + width: 100%; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: auto; + margin-top: 5px; + padding: 0px 6px; + box-sizing: border-box; + } + + .container-body::-webkit-scrollbar-track { + background-color: whitesmoke; + border-radius: 7px; + } + + .container-body::-webkit-scrollbar { + width: 6px; + border-radius: 7px; + background-color: whitesmoke; + } + + .container-body::-webkit-scrollbar-thumb { + background-color: rgb(180, 176, 176); + border-radius: 7px; + transition: all 0.3s ease-in-out; + } + + .container-body::-webkit-scrollbar-thumb:hover { + background-color: rgb(148, 146, 146); + cursor: pointer; + } + + p { + color: var(--black); + margin: 0px; + padding: 0px; + word-break: break-all; + } + + .container { + display: flex; + width: 100%; + flex-direction: column; + height: 100%; + } + + .chat-right-panel-label { + font-family: Montserrat, sans-serif; + color: var(--group-header); + padding: 5px; + font-size: 13px; + user-select: none; + } + + .group-info { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 10px; + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + text-align: center; + user-select: none; + } + + .group-description { + font-family: Roboto, sans-serif; + color: var(--chat-bubble-msg-color); + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + margin-top: 15px; + word-break: break-word; + user-select: none; + } + + .group-subheader { + font-family: Montserrat, sans-serif; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + + .group-data { + font-family: Roboto, sans-serif; + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + .message-myBg { + background-color: var(--chat-bubble-myBg) !important; + margin-bottom: 15px; + border-radius: 5px; + padding: 5px; + } + .message-data-name { + user-select: none; + color: #03a9f4; + margin-bottom: 5px; + } + .message-user-info { + display: flex; + justify-content: space-between; + width: 100%; + gap: 10px; + } + + .hideImg { + visibility: hidden; + } + .checkbox-row { + position: relative; + display: flex; + align-items: center; + align-content: center; + font-family: Montserrat, sans-serif; + font-weight: 600; + color: var(--black); + padding-left: 5px; + } + `; + } + + async getMoreImages(reset) { + try { + if(reset){ + this.images = [] + } + const groupPart = this.isReceipient + ? `direct_${generateIdFromAddresses(this._chatId, this.myAddress)}` + : `group_${this._chatId}`; + + let offset = reset ? 0 : this.images.length; + let endpoint = `/arbitrary/resources/search?service=QCHAT_IMAGE&identifier=qchat_${groupPart}&reverse=true&limit=20&reverse=true&offset=${offset}` + if(this.onlyMyImages){ + endpoint = endpoint + `&name=${this.myName}` + } + const qchatImages = await parentEpml.request('apiCall', { + type: 'api', + url: endpoint, + }); + + let list = [] + if(reset){ + list = qchatImages + } else { + list = [...this.images, ...qchatImages] + } + + this.images = list + } catch (error) { + console.log(error); + } + } + + firstUpdated() { + this.viewElement = this.shadowRoot.getElementById('viewElement'); + this.downObserverElement = + this.shadowRoot.getElementById('downObserver'); + this.elementObserver(); + } + + async updated(changedProperties) { + if (changedProperties && changedProperties.has('_chatId')) { + this.images = []; + this.getMoreImages(true); + } + + if (changedProperties && changedProperties.has('onlyMyImages')) { + this.getMoreImages(true) + } + } + + elementObserver() { + const options = { + root: this.viewElement, + rootMargin: '0px', + threshold: 1, + }; + // identify an element to observe + const elementToObserve = this.downObserverElement; + // passing it a callback function + const observer = new IntersectionObserver( + this.observerHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } + + observerHandler(entries) { + if (!entries[0].isIntersecting) { + return; + } else { + if (this.images.length < 20) { + return; + } + this.getMoreImages(); + } + } + + selectAuto(e) { + if (e.target.checked) { + this.autoView = false + } else { + this.autoView = true + } + } + + selectMyImages(e) { + if (e.target.checked) { + this.onlyMyImages = false + } else { + this.onlyMyImages = true + } + } + + render() { + return html` + +
+
+ { + this.getMoreImages(true) + }} style="color: var(--black); cursor:pointer;">refresh + + this.toggle( + false + )} style="margin: 0px 10px" icon="vaadin:close" slot="icon"> +
+
+ + this.selectAuto(e)} ?checked=${this.autoView}> +
+
+ + this.selectMyImages(e)} ?checked=${this.onlyMyImages}> +
+
+ + ${this.images.map((image) => { + return html``; + })} +
+
+
+
+ `; + } +} + +customElements.define('chat-right-panel-resources', ChatRightPanelResources); + +class ImageParent extends LitElement { + static get properties() { + return { + leaveGroupObj: { type: Object }, + error: { type: Boolean }, + chatHeads: { type: Array }, + groupAdmin: { attribute: false }, + groupMembers: { attribute: false }, + selectedHead: { type: Object }, + toggle: { attribute: false }, + getMoreMembers: { attribute: false }, + setOpenPrivateMessage: { attribute: false }, + userName: { type: String }, + walletBalance: { type: Number }, + sendMoneyLoading: { type: Boolean }, + btnDisable: { type: Boolean }, + errorMessage: { type: String }, + successMessage: { type: String }, + setOpenTipUser: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setUserName: { attribute: false }, + chatId: { type: String }, + _chatId: { type: String }, + isReceipient: { type: Boolean }, + images: { type: Array }, + viewImage: { type: Boolean }, + image: { type: Object }, + autoView: {type: Boolean}, + repost: {attribute: false}, + isImgLoaded: {type: Boolean} + }; + } + + constructor() { + super(); + this.leaveGroupObj = {}; + this.leaveFee = 0.001; + this.error = false; + this.chatHeads = []; + this.groupAdmin = []; + this.groupMembers = []; + this.viewElement = ''; + + this.sendMoneyLoading = false; + this.btnDisable = false; + this.errorMessage = ''; + this.successMessage = ''; + this.isImgLoaded = false + this.images = []; + this.viewImage = false; + this.myName = + window.parent.reduxStore.getState().app.accountInfo.names[0].name; + } + + static get styles() { + return css` + .top-bar-icon { + cursor: pointer; + height: 18px; + width: 18px; + transition: 0.2s all; + } + + .top-bar-icon:hover { + color: var(--black); + } + + .modal-button { + font-family: Roboto, sans-serif; + font-size: 16px; + color: var(--mdc-theme-primary); + background-color: transparent; + padding: 8px 10px; + border-radius: 5px; + border: none; + transition: all 0.3s ease-in-out; + } + + .close-row { + width: 100%; + display: flex; + justify-content: flex-end; + height: 50px; + flex: 0; + gap: 20px; + align-items: center; + } + + .container-body { + width: 100%; + display: flex; + flex-direction: column; + flex-grow: 1; + overflow: auto; + margin-top: 5px; + padding: 0px 6px; + box-sizing: border-box; + } + + .container-body::-webkit-scrollbar-track { + background-color: whitesmoke; + border-radius: 7px; + } + + .container-body::-webkit-scrollbar { + width: 6px; + border-radius: 7px; + background-color: whitesmoke; + } + + .container-body::-webkit-scrollbar-thumb { + background-color: rgb(180, 176, 176); + border-radius: 7px; + transition: all 0.3s ease-in-out; + } + + .container-body::-webkit-scrollbar-thumb:hover { + background-color: rgb(148, 146, 146); + cursor: pointer; + } + + p { + color: var(--black); + margin: 0px; + padding: 0px; + word-break: break-all; + } + + .container { + display: flex; + width: 100%; + flex-direction: column; + height: 100%; + } + + .chat-right-panel-label { + font-family: Montserrat, sans-serif; + color: var(--group-header); + padding: 5px; + font-size: 13px; + user-select: none; + } + + .group-info { + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 10px; + } + + .group-name { + font-family: Raleway, sans-serif; + font-size: 20px; + color: var(--chat-bubble-msg-color); + text-align: center; + user-select: none; + } + + .group-description { + font-family: Roboto, sans-serif; + color: var(--chat-bubble-msg-color); + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + margin-top: 15px; + word-break: break-word; + user-select: none; + } + + .group-subheader { + font-family: Montserrat, sans-serif; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + + .group-data { + font-family: Roboto, sans-serif; + letter-spacing: 0.3px; + font-weight: 300; + font-size: 14px; + color: var(--chat-bubble-msg-color); + } + .message-myBg { + background-color: var(--chat-bubble-myBg) !important; + margin-bottom: 15px; + border-radius: 5px; + padding: 5px; + } + .message-data-name { + user-select: none; + color: #03a9f4; + margin-bottom: 5px; + } + .message-user-info { + display: flex; + justify-content: space-between; + width: 100%; + gap: 10px; + } + + .hideImg { + visibility: hidden; + } + .image-container { + display: flex; + } + .repost-btn { + margin-top: 4px; + max-height: 28px; + padding: 5px 5px; + font-size: 14px; + background-color: #03a9f4; + color: white; + border: 1px solid transparent; + border-radius: 3px; + cursor: pointer; + } + `; + } + + firstUpdated() {} + + async updated(changedProperties) { + if (changedProperties && changedProperties.has('chatId')) { + // const autoSeeChatList = + // window.parent.reduxStore.getState().app.autoLoadImageChats; + // if (autoSeeChatList.includes(this.chatId)) { + // this.viewImage = true; + // } + } + } + + onLoad(){ + this.isImgLoaded = true + this.requestUpdate() + } + + render() { + return html` + ${!this.autoView && !this.viewImage && this.myName !== this.image.name + ? html` +
+ +
{ + this.viewImage = true; + }} + class=${[`image-container`].join(' ')} + style="height: 200px" + > +
+ ${translate('chatpage.cchange40')} +
+
+
+ ` + : html``} + ${this.autoView || this.viewImage || this.myName === this.image.name + ? html` +
+ + this.onLoad()} + > + ${this.isImgLoaded ? html` +
+ +
+ ` : ''} + +
+ ` + : ''} + `; + } +} + +customElements.define('image-parent', ImageParent); diff --git a/plugins/plugins/core/components/ChatScroller-css.js b/plugins/plugins/core/components/ChatScroller-css.js index 028b0a79..2aad505d 100644 --- a/plugins/plugins/core/components/ChatScroller-css.js +++ b/plugins/plugins/core/components/ChatScroller-css.js @@ -43,6 +43,10 @@ export const chatStyles = css` margin: 0; padding: 20px 17px; } + .message-sending { + opacity: 0.5; + cursor: progress; +} .chat-list { overflow-y: auto; @@ -256,7 +260,6 @@ export const chatStyles = css` .message-parent { padding: 3px; background: rgba(245, 245, 245, 0); - transition: all 0.1s ease-in-out; } .message-parent:hover { @@ -364,7 +367,6 @@ export const chatStyles = css` background:#fff; color: #000; text-align: center; - box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px; font-size: 12px; z-index: 5; white-space: nowrap; @@ -410,7 +412,6 @@ export const chatStyles = css` width: 150px; height: 32px; padding: 3px 8px; - box-shadow: rgba(77, 77, 82, 0.2) 0px 7px 29px 0px; } .block-user:hover { @@ -753,6 +754,17 @@ export const chatStyles = css` visibility: visible; } + .unread-divider { + width: 100%; + padding: 5px; + color: var(--black); + border-bottom: 1px solid var(--black); + display: flex; + justify-content: center; + border-radius: 2px; + margin-top: 5px; + } + .blink-bg{ border-radius: 8px; animation: blinkingBackground 3s; diff --git a/plugins/plugins/core/components/ChatScroller.js b/plugins/plugins/core/components/ChatScroller.js index 8421df12..9da8a1b2 100644 --- a/plugins/plugins/core/components/ChatScroller.js +++ b/plugins/plugins/core/components/ChatScroller.js @@ -1,1129 +1,2179 @@ -import { LitElement, html, css } from 'lit' -import { render } from 'lit/html.js' -import { repeat } from 'lit/directives/repeat.js' -import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' -import { unsafeHTML } from 'lit/directives/unsafe-html.js' -import { chatStyles } from './ChatScroller-css.js' -import { Epml } from '../../../epml' -import { cropAddress } from "../../utils/cropAddress" -import { roundToNearestDecimal } from '../../utils/roundToNearestDecimal.js' -import { EmojiPicker } from 'emoji-picker-js' -import { generateHTML } from '@tiptap/core' -import isElectron from 'is-electron' +import { LitElement, html, } from 'lit'; +import { repeat } from 'lit/directives/repeat.js'; +import { + get, + translate, +} from 'lit-translate'; +import { unsafeHTML } from 'lit/directives/unsafe-html.js'; +import { chatStyles } from './ChatScroller-css.js'; +import { Epml } from '../../../epml'; +import { cropAddress } from '../../utils/cropAddress'; +import { roundToNearestDecimal } from '../../utils/roundToNearestDecimal.js'; +import { generateHTML } from '@tiptap/core'; +import isElectron from 'is-electron'; -import axios from 'axios' -import Highlight from '@tiptap/extension-highlight' -import ShortUniqueId from 'short-unique-id' -import StarterKit from '@tiptap/starter-kit' -import Underline from '@tiptap/extension-underline' +import axios from 'axios'; +import Highlight from '@tiptap/extension-highlight'; +import ShortUniqueId from 'short-unique-id'; +import StarterKit from '@tiptap/starter-kit'; +import Underline from '@tiptap/extension-underline'; -import './ChatModals.js' -import './LevelFounder.js' -import './NameMenu.js' -import './UserInfo/UserInfo.js' -import './WrapperModal' +import './ChatModals.js'; +import './LevelFounder.js'; +import './NameMenu.js'; +import './UserInfo/UserInfo.js'; +import './WrapperModal'; +import './ChatImage'; -import '@material/mwc-button' -import '@material/mwc-dialog' -import '@material/mwc-icon' -import '@vaadin/icon' -import '@vaadin/icons' -import '@vaadin/tooltip' +import '@material/mwc-button'; +import '@material/mwc-dialog'; +import '@material/mwc-icon'; +import '@vaadin/icon'; +import '@vaadin/icons'; +import '@vaadin/tooltip'; +import { chatLimit, totalMsgCount } from './ChatPage.js'; -const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) +const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }); +let toggledMessage = {}; -let toggledMessage = {} - -const uid = new ShortUniqueId() +const uid = new ShortUniqueId(); const getApiKey = () => { - const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] - let apiKey = myNode.apiKey - return apiKey -} + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + let apiKey = myNode.apiKey; + return apiKey; +}; const extractComponents = async (url) => { - if (!url.startsWith("qortal://")) { - return null - } + if (!url.startsWith('qortal://')) { + return null; + } - url = url.replace(/^(qortal\:\/\/)/, "") - if (url.includes("/")) { - let parts = url.split("/") - const service = parts[0].toUpperCase() - parts.shift() - const name = parts[0] - parts.shift() - let identifier + url = url.replace(/^(qortal:\/\/)/, ''); + if (url.startsWith('use-')) { + // Handle the new 'use' format + let parts = url.split('/'); + const type = parts[0].split('-')[1]; // e.g., 'group' from 'use-group' + parts.shift(); + const action = parts.length > 0 ? parts[0].split('-')[1] : null; // e.g., 'invite' from 'action-invite' + parts.shift(); + const idPrefix = parts.length > 0 ? parts[0].split('-')[0] : null; // e.g., 'groupid' from 'groupid-321' + const id = parts.length > 0 ? parts[0].split('-')[1] : null; // e.g., '321' from 'groupid-321' + return { + type: type, + action: action, + [idPrefix]: id + } + } else if (url.includes('/')) { + let parts = url.split('/'); + const service = parts[0].toUpperCase(); + parts.shift(); + const name = parts[0]; + parts.shift(); + let identifier; - if (parts.length > 0) { - identifier = parts[0] // Do not shift yet - // Check if a resource exists with this service, name and identifier combination - let responseObj = await parentEpml.request('apiCall', { - url: `/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${getApiKey()}` - }) + if (parts.length > 0) { + identifier = parts[0]; // Do not shift yet + // Check if a resource exists with this service, name and identifier combination + let responseObj = await parentEpml.request('apiCall', { + url: `/arbitrary/resource/status/${service}/${name}/${identifier}?apiKey=${getApiKey()}`, + }); - if (responseObj.totalChunkCount > 0) { - // Identifier exists, so don't include it in the path - parts.shift() - } - else { - identifier = null - } - } + if (responseObj.totalChunkCount > 0) { + // Identifier exists, so don't include it in the path + parts.shift(); + } else { + identifier = null; + } + } - const path = parts.join("/") + const path = parts.join('/'); - const components = {} - components["service"] = service - components["name"] = name - components["identifier"] = identifier - components["path"] = path - return components - } + const components = {}; + components['service'] = service; + components['name'] = name; + components['identifier'] = identifier; + components['path'] = path; + return components; + } - return null -} + return null; +}; function processText(input) { - const linkRegex = /(qortal:\/\/\S+)/g + const linkRegex = /(qortal:\/\/\S+)/g; - function processNode(node) { - if (node.nodeType === Node.TEXT_NODE) { - const parts = node.textContent.split(linkRegex) + function processNode(node) { + if (node.nodeType === Node.TEXT_NODE) { + const parts = node.textContent.split(linkRegex); - if (parts.length > 1) { - const fragment = document.createDocumentFragment() + if (parts.length > 1) { + const fragment = document.createDocumentFragment(); - parts.forEach((part) => { - if (part.startsWith('qortal://')) { - const link = document.createElement('span') - // Store the URL in a data attribute - link.setAttribute('data-url', part) - link.textContent = part - link.style.color = 'var(--code-block-text-color)' - link.style.textDecoration = 'underline' - link.style.cursor = 'pointer' + parts.forEach((part) => { + if (part.startsWith('qortal://')) { + const link = document.createElement('span'); + // Store the URL in a data attribute + link.setAttribute('data-url', part); + link.textContent = part; + link.style.color = 'var(--code-block-text-color)'; + link.style.textDecoration = 'underline'; + link.style.cursor = 'pointer'; - link.addEventListener('click', async (e) => { - e.preventDefault() - try { - const res = await extractComponents(part) - if (!res) return - const { service, name, identifier, path } = res - let query = `?service=${service}` - if (name) { - query = query + `&name=${name}` - } - if (identifier) { - query = query + `&identifier=${identifier}` - } - if (path) { - query = query + `&path=${path}` - } - window.parent.reduxStore.dispatch(window.parent.reduxAction.setNewTab({ - url: `qdn/browser/index.html${query}`, - id: uid.rnd(), - myPlugObj: { - "url": "myapp", - "domain": "core", - "page": `qdn/browser/index.html${query}`, - "title": name, - "icon": service === 'WEBSITE' ? 'vaadin:desktop' : 'vaadin:external-browser', - "mwcicon": service === 'WEBSITE' ? 'desktop_mac' : 'open_in_browser', - "menus": [], - "parent": false - } - })) + link.addEventListener('click', async (e) => { + e.preventDefault(); + try { + const res = await extractComponents(part); + if (!res) return; + if(res.type && res.groupid && res.action === 'join'){ + window.parent.reduxStore.dispatch( + window.parent.reduxAction.setNewTab({ + url: `group-management`, + id: uid.rnd(), + myPlugObj: { + "url": "group-management", + "domain": "core", + "page": "group-management/index.html", + "title": "Group Management", + "icon": "vaadin:group", + "mwcicon": "group", + "pluginNumber": "plugin-fJZNpyLGTl", + "menus": [], + "parent": false + }, + openExisting: true + }) + ); + window.parent.reduxStore.dispatch( + window.parent.reduxAction.setSideEffectAction({ + type: 'openJoinGroupModal', + data: +res.groupid + }) + ); + return + } + const { service, name, identifier, path } = res; + let query = `?service=${service}`; + if (name) { + query = query + `&name=${name}`; + } + if (identifier) { + query = query + `&identifier=${identifier}`; + } + if (path) { + query = query + `&path=${path}`; + } + window.parent.reduxStore.dispatch( + window.parent.reduxAction.setNewTab({ + url: `qdn/browser/index.html${query}`, + id: uid.rnd(), + myPlugObj: { + url: 'myapp', + domain: 'core', + page: `qdn/browser/index.html${query}`, + title: name, + icon: + service === 'WEBSITE' + ? 'vaadin:desktop' + : 'vaadin:external-browser', + mwcicon: + service === 'WEBSITE' + ? 'desktop_mac' + : 'open_in_browser', + menus: [], + parent: false, + }, + }) + ); + } catch (error) { + console.log({ error }); + } + }); - } catch (error) { - console.log({ error }) - } + fragment.appendChild(link); + } else { + const textNode = document.createTextNode(part); + fragment.appendChild(textNode); + } + }); - }) + node.replaceWith(fragment); + } + } else { + for (const childNode of Array.from(node.childNodes)) { + processNode(childNode); + } + } + } - fragment.appendChild(link) - } else { - const textNode = document.createTextNode(part) - fragment.appendChild(textNode) - } - }) + const wrapper = document.createElement('div'); + wrapper.innerHTML = input; - node.replaceWith(fragment) - } - } else { - for (const childNode of Array.from(node.childNodes)) { - processNode(childNode) - } - } - } + processNode(wrapper); - const wrapper = document.createElement('div') - wrapper.innerHTML = input - - processNode(wrapper) - - return wrapper + return wrapper; } + + class ChatScroller extends LitElement { - static get properties() { - return { - theme: { type: String, reflect: true }, - getNewMessage: { attribute: false }, - getOldMessage: { attribute: false }, - escapeHTML: { attribute: false }, - messages: { type: Array }, - hideMessages: { type: Array }, - setRepliedToMessageObj: { attribute: false }, - setEditedMessageObj: { attribute: false }, - sendMessage: { attribute: false }, - sendMessageForward: { attribute: false }, - showLastMessageRefScroller: { attribute: false }, - emojiPicker: { attribute: false }, - isLoadingMessages: { type: Boolean }, - setIsLoadingMessages: { attribute: false }, - chatId: { type: String }, - setForwardProperties: { attribute: false }, - setOpenPrivateMessage: { attribute: false }, - setOpenUserInfo: { attribute: false }, - setOpenTipUser: { attribute: false }, - setUserName: { attribute: false }, - setSelectedHead: { attribute: false }, - openTipUser: { type: Boolean }, - openUserInfo: { type: Boolean }, - userName: { type: String }, - selectedHead: { type: Object }, - goToRepliedMessage: { attribute: false }, - getOldMessageAfter: { attribute: false }, - listSeenMessages: { type: Array } - } - } + static get properties() { + return { + theme: { type: String, reflect: true }, + getNewMessage: { attribute: false }, + getOldMessage: { attribute: false }, + getAfterMessages: { attribute: false }, + escapeHTML: { attribute: false }, + messages: { type: Object }, + hideMessages: { type: Array }, + setRepliedToMessageObj: { attribute: false }, + setEditedMessageObj: { attribute: false }, + sendMessage: { attribute: false }, + sendMessageForward: { attribute: false }, + showLastMessageRefScroller: { attribute: false }, + emojiPicker: { attribute: false }, + isLoadingMessages: { type: Boolean }, + setIsLoadingMessages: { attribute: false }, + chatId: { type: String }, + setForwardProperties: { attribute: false }, + setOpenPrivateMessage: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setOpenTipUser: { attribute: false }, + setUserName: { attribute: false }, + setSelectedHead: { attribute: false }, + openTipUser: { type: Boolean }, + openUserInfo: { type: Boolean }, + userName: { type: String }, + selectedHead: { type: Object }, + goToRepliedMessage: { attribute: false }, + listSeenMessages: { type: Array }, + updateMessageHash: { type: Object }, + messagesToRender: { type: Array }, + oldMessages: { type: Array }, + clearUpdateMessageHashmap: { attribute: false }, + disableFetching: { type: Boolean }, + isLoadingBefore: { type: Boolean }, + isLoadingAfter: { type: Boolean }, + messageQueue: { type: Array }, + loggedInUserName: { type: String }, + loggedInUserAddress: { type: String }, + }; + } - static get styles() { - return [chatStyles] - } + static get styles() { + return [chatStyles]; + } - constructor() { - super() - this.messages = [] - this._upObserverhandler = this._upObserverhandler.bind(this) - this._downObserverHandler = this._downObserverHandler.bind(this) - this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address - this.hideMessages = JSON.parse(localStorage.getItem("MessageBlockedAddresses") || "[]") - this.openTipUser = false - this.openUserInfo = false - this.listSeenMessages = [] - this.theme = localStorage.getItem('qortalTheme') ? localStorage.getItem('qortalTheme') : 'light' - } + constructor() { + super(); + this.messages = { + messages: [], + type: '', + }; + this.oldMessages = []; + this._upObserverhandler = this._upObserverhandler.bind(this); + this.newListMessages = this.newListMessages.bind(this); + this.newListMessagesUnreadMessages = + this.newListMessagesUnreadMessages.bind(this); + this._downObserverHandler = this._downObserverHandler.bind(this); + this.isLastMessageBeforeUnread = + this.isLastMessageBeforeUnread.bind(this); + this.replaceMessagesWithUpdate = + this.replaceMessagesWithUpdate.bind(this); + this.__bottomObserverForFetchingMessagesHandler = + this.__bottomObserverForFetchingMessagesHandler.bind(this); + this.myAddress = + window.parent.reduxStore.getState().app.selectedAddress.address; + this.hideMessages = JSON.parse( + localStorage.getItem('MessageBlockedAddresses') || '[]' + ); + this.openTipUser = false; + this.openUserInfo = false; + this.listSeenMessages = []; + this.theme = localStorage.getItem('qortalTheme') + ? localStorage.getItem('qortalTheme') + : 'light'; + this.messagesToRender = []; + this.disableFetching = false; + this.isLoadingBefore = false; + this.isLoadingAfter = false; + this.disableAddingNewMessages = false; + this.lastReadMessageTimestamp = null; + this.messageQueue = []; + } - addSeenMessage(val) { - this.listSeenMessages.push(val) - } + addSeenMessage(val) { + this.listSeenMessages.push(val); + } + goToRepliedMessageFunc(val, val2) { + this.disableFetching = true; + this.goToRepliedMessage(val, val2); + } - render() { - let formattedMessages = this.messages.reduce((messageArray, message, index) => { - const lastGroupedMessage = messageArray[messageArray.length - 1] - let timestamp - let sender - let repliedToData - let firstMessageInChat + shouldGroupWithLastMessage(newMessage, lastGroupedMessage) { + if (!lastGroupedMessage) return false; - if (index === 0) { - firstMessageInChat = true - } else { - firstMessageInChat = false - } + return ( + Math.abs(lastGroupedMessage.timestamp - newMessage.timestamp) < + 600000 && + lastGroupedMessage.sender === newMessage.sender && + !lastGroupedMessage.repliedToData + ); + } - message = { ...message, firstMessageInChat } + clearLoaders() { + this.isLoadingBefore = false; + this.isLoadingAfter = false; + this.disableFetching = false; + } + addNewMessage(newMessage) { + const lastGroupedMessage = + this.messagesToRender[this.messagesToRender.length - 1]; - if (lastGroupedMessage) { - timestamp = lastGroupedMessage.timestamp - sender = lastGroupedMessage.sender - repliedToData = lastGroupedMessage.repliedToData - } - const isSameGroup = Math.abs(timestamp - message.timestamp) < 600000 && sender === message.sender && !repliedToData + if (this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage)) { + lastGroupedMessage.messages.push(newMessage); + } else { + this.messagesToRender.push({ + messages: [newMessage], + ...newMessage, + }); + } + this.clearLoaders(); + this.requestUpdate(); + } - if (isSameGroup) { - messageArray[messageArray.length - 1].messages = [ - ...(messageArray[messageArray.length - 1].messages || []), - message - ] - } else { - messageArray.push({ - messages: [message], - ...message - }) - } - return messageArray - }, []) + async newListMessages(newMessages) { + let data = []; + const copy = [...newMessages]; + copy.forEach((newMessage) => { + const lastGroupedMessage = data[data.length - 1]; - return html` - ${this.isLoadingMessages ? html` -
- -
- ` : ''} -
    -
    - ${formattedMessages.map((formattedMessage) => { - return repeat( - formattedMessage.messages, - (message) => message.signature, - (message, indexMessage) => html` - 1} - ?isLastMessageInGroup=${indexMessage === formattedMessage.messages.length - 1} - .setToggledMessage=${this.setToggledMessage} - .setForwardProperties=${this.setForwardProperties} - .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} - .setOpenTipUser=${(val) => this.setOpenTipUser(val)} - .setOpenUserInfo=${(val) => this.setOpenUserInfo(val)} - .setUserName=${(val) => this.setUserName(val)} - id=${message.signature} - .goToRepliedMessage=${this.goToRepliedMessage} - .addSeenMessage=${(val) => this.addSeenMessage(val)} - .listSeenMessages=${this.listSeenMessages} - chatId=${this.chatId} - > - ` - ) - })} -
    -
- ` - } + if ( + this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage) + ) { + lastGroupedMessage.messages.push(newMessage); + } else { + data.push({ + messages: [newMessage], + ...newMessage, + }); + } + }); - shouldUpdate(changedProperties) { - if (changedProperties.has('isLoadingMessages')) { - return true - } - if (changedProperties.has('chatId') && changedProperties.get('chatId')) { - return true - } - if (changedProperties.has('openTipUser')) { - return true - } - if (changedProperties.has('openUserInfo')) { - return true - } - if (changedProperties.has('userName')) { - return true - } - // Only update element if prop1 changed. - return changedProperties.has('messages') - } + // const getCount = await parentEpml.request('apiCall', { + // type: 'api', + // url: `/chat/messages?involving=${window.parent.reduxStore.getState().app.selectedAddress.address}&involving=${this._chatId}&limit=${chatLimit}&reverse=true&before=${scrollElement.messageObj.timestamp}&haschatreference=false&encoding=BASE64` + // }) + this.messagesToRender = data; + this.clearLoaders(); + this.requestUpdate(); + await this.updateComplete; + } - async getUpdateComplete() { - await super.getUpdateComplete() - const marginElements = Array.from(this.shadowRoot.querySelectorAll('message-template')) - await Promise.all(marginElements.map(el => el.updateComplete)) - return true - } + async newListMessagesUnreadMessages( + newMessages, + message, + lastReadMessageTimestamp, + count + ) { + let data = []; + const copy = [...newMessages]; - setToggledMessage(message) { - toggledMessage = message - } + let dividerPlaced = false; // To ensure the divider is added only once - async firstUpdated() { - this.changeTheme() + // Start from the end of the list (newest messages) + for (let i = copy.length - 1; i >= 0; i--) { + let newMessage = copy[i]; - window.addEventListener('storage', () => { - const checkTheme = localStorage.getItem('qortalTheme') + // Initialize a property for the divider + newMessage.isDivider = false; - if (checkTheme === 'dark') { - this.theme = 'dark' - } else { - this.theme = 'light' - } - document.querySelector('html').setAttribute('theme', this.theme) - }) + // Check if this is the message before which the divider should be placed + if ( + !dividerPlaced && + newMessage.timestamp <= lastReadMessageTimestamp + ) { + newMessage.isDivider = true; + dividerPlaced = true; // Ensure the divider is only added once + break; // Exit once the divider is placed + } + } - this.emojiPicker.on('emoji', selection => { - this.sendMessage({ - type: 'reaction', - editedMessageObj: toggledMessage, - reaction: selection.emoji, - }) - }) - this.viewElement = this.shadowRoot.getElementById('viewElement') - this.upObserverElement = this.shadowRoot.getElementById('upObserver') - this.downObserverElement = this.shadowRoot.getElementById('downObserver') - // Intialize Observers - this.upElementObserver() - this.downElementObserver() - await this.getUpdateComplete() - this.viewElement.scrollTop = this.viewElement.scrollHeight + 50 + copy.forEach((newMessage) => { + const lastGroupedMessage = data[data.length - 1]; - this.clearConsole() - setInterval(() => { - this.clearConsole() - }, 60000) - } + if ( + this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage) + ) { + lastGroupedMessage.messages.push(newMessage); + } else { + data.push({ + messages: [newMessage], + ...newMessage, + }); + } + }); + if (count > 0) { + this.disableAddingNewMessages = true; + } + this.messagesToRender = data; + this.clearLoaders(); + this.requestUpdate(); + await this.updateComplete; + const findElement = this.shadowRoot.getElementById('unread-divider-id'); + if (findElement) { + findElement.scrollIntoView({ behavior: 'auto', block: 'center' }); + } + } - clearConsole() { - if (!isElectron()) { - } else { - console.clear() - window.parent.electronAPI.clearCache() - } - } + async addNewMessages(newMessages, type) { + if (this.disableAddingNewMessages && type === 'newComingInAuto') return; - changeTheme() { - const checkTheme = localStorage.getItem('qortalTheme') - if (checkTheme === 'dark') { - this.theme = 'dark' - } else { - this.theme = 'light' - } - document.querySelector('html').setAttribute('theme', this.theme) - } + const viewElement = this.shadowRoot.querySelector('#viewElement'); + const copy = type === 'initial' ? [] : [...this.messagesToRender]; - _getOldMessage(_scrollElement) { - this.getOldMessage(_scrollElement) - } + for (const newMessage of newMessages) { + const lastGroupedMessage = copy[copy.length - 1]; - _getOldMessageAfter(_scrollElement) { - this.getOldMessageAfter(_scrollElement) - } + if ( + this.shouldGroupWithLastMessage(newMessage, lastGroupedMessage) + ) { + lastGroupedMessage.messages.push(newMessage); + } else { + copy.push({ + messages: [newMessage], + ...newMessage, + }); + } + } - _upObserverhandler(entries) { - if (entries[0].isIntersecting) { - if (this.messages.length < 20) { - return - } - this.setIsLoadingMessages(true) - let _scrollElement = entries[0].target.nextElementSibling - this._getOldMessage(_scrollElement) - } - } + // Ensure that the total number of individual messages doesn't exceed totalMsgCount + let totalMessagesCount = copy.reduce( + (acc, group) => acc + group.messages.length, + 0 + ); + while (totalMessagesCount > totalMsgCount && copy.length) { + if ( + newMessages.length < chatLimit && + type !== 'newComingInAuto' && + type !== 'initial' + ) { + this.disableAddingNewMessages = false; + } + const firstGroup = copy[0]; + if ( + firstGroup.messages.length <= + totalMessagesCount - totalMsgCount + ) { + // If removing the whole first group achieves the goal, remove it + totalMessagesCount -= firstGroup.messages.length; + copy.shift(); + } else { + // Otherwise, trim individual messages from the first group + const messagesToRemove = totalMessagesCount - totalMsgCount; + firstGroup.messages.splice(0, messagesToRemove); + totalMessagesCount = totalMsgCount; + } + } + this.messagesToRender = copy; + this.requestUpdate(); + await this.updateComplete; - _downObserverHandler(entries) { - if (!entries[0].isIntersecting) { - let _scrollElement = entries[0].target.previousElementSibling - // this._getOldMessageAfter(_scrollElement) - this.showLastMessageRefScroller(true) - } else { - this.showLastMessageRefScroller(false) - } - } + if (type === 'initial') { + viewElement.scrollTop = viewElement.scrollHeight; + } - upElementObserver() { - const options = { - root: this.viewElement, - rootMargin: '0px', - threshold: 1 - } - const observer = new IntersectionObserver(this._upObserverhandler, options) - observer.observe(this.upObserverElement) - } + this.clearLoaders(); + } - downElementObserver() { - const options = { - root: this.viewElement, - rootMargin: '0px', - threshold: 1 - } - // identify an element to observe - const elementToObserve = this.downObserverElement - // passing it a callback function - const observer = new IntersectionObserver(this._downObserverHandler, options) - // call `observe()` on that MutationObserver instance, - // passing it the element to observe, and the options object - observer.observe(elementToObserve) - } + async prependOldMessages(oldMessages) { + if (!this.messagesToRender) this.messagesToRender = []; // Ensure it's initialized + + let currentMessageGroup = null; + let previousMessage = null; + + for (const message of oldMessages) { + if ( + !previousMessage || + !this.shouldGroupWithLastMessage(message, previousMessage) + ) { + + if (currentMessageGroup) { + this.messagesToRender.unshift(currentMessageGroup); + } + currentMessageGroup = { + id: message.signature, + messages: [message], + ...message, + }; + } else { + // Add to the current group + currentMessageGroup.messages.unshift(message); + } + previousMessage = message; + } + + // After processing all old messages, add the last group + if (currentMessageGroup) { + this.messagesToRender.unshift(currentMessageGroup); + } + + // Ensure that the total number of individual messages doesn't exceed totalMsgCount + let totalMessagesCount = this.messagesToRender.reduce( + (acc, group) => acc + group.messages.length, + 0 + ); + while ( + totalMessagesCount > totalMsgCount && + this.messagesToRender.length + ) { + this.disableAddingNewMessages = true; + const lastGroup = + this.messagesToRender[this.messagesToRender.length - 1]; + if ( + lastGroup.messages.length <= + totalMessagesCount - totalMsgCount + ) { + // If removing the whole last group achieves the goal, remove it + totalMessagesCount -= lastGroup.messages.length; + this.messagesToRender.pop(); + } else { + // Otherwise, trim individual messages from the last group + const messagesToRemove = totalMessagesCount - totalMsgCount; + lastGroup.messages.splice(-messagesToRemove, messagesToRemove); + totalMessagesCount = totalMsgCount; + } + } + this.clearLoaders(); + this.requestUpdate(); + } + + async replaceMessagesWithUpdate(updatedMessages) { + const viewElement = this.shadowRoot.querySelector('#viewElement'); + if (!viewElement) return; // Ensure the element exists + const isUserAtBottom = + viewElement.scrollTop + viewElement.clientHeight === + viewElement.scrollHeight; + + + // Using map to return a new array, rather than mutating the old one + const newMessagesToRender = this.messagesToRender.map((group) => { + // For each message, return the updated message if it exists, otherwise return the original message + const updatedGroupMessages = group.messages.map((message) => { + return updatedMessages[message.signature] + ? { ...message, ...updatedMessages[message.signature] } + : message; + }); + + // Return a new group object with updated messages + return { + ...group, + messages: updatedGroupMessages, + }; + }); + + this.messagesToRender = newMessagesToRender; + this.requestUpdate(); + await this.updateComplete; + + if (isUserAtBottom) { + viewElement.scrollTop = + viewElement.scrollHeight - viewElement.clientHeight; + } else { + // Adjust scroll position based on the difference in scroll heights + const newScrollHeight = viewElement.scrollHeight; + viewElement.scrollTop = + viewElement.scrollTop + + (newScrollHeight - viewElement.scrollHeight); + } + + this.clearUpdateMessageHashmap(); + this.clearLoaders(); + } + + async replaceMessagesWithUpdateByArray(updatedMessagesArray) { + let previousScrollTop; + let previousScrollHeight; + + const viewElement = this.shadowRoot.querySelector('#viewElement'); + previousScrollTop = viewElement.scrollTop; + previousScrollHeight = viewElement.scrollHeight; + for (let group of this.messagesToRender) { + for (let i = 0; i < group.messages.length; i++) { + const update = updatedMessagesArray.find( + (updatedMessage) => + updatedMessage.chatReference === + group.messages[i].signature || + updatedMessage.chatReference === + group.messages[i].originalSignature || + updatedMessage.chatReference === + group.messages[i].chatReference + ); + if (update) { + Object.assign(group.messages[i], update); + } + } + } + this.requestUpdate(); + const newScrollHeight = viewElement.scrollHeight; + viewElement.scrollTop = + previousScrollTop + (newScrollHeight - previousScrollHeight); + this.clearUpdateMessageHashmap(); + this.clearLoaders(); + } + + async updated(changedProperties) { + if (changedProperties && changedProperties.has('messages')) { + if (this.messages.type === 'initial') { + this.addNewMessages(this.messages.messages, 'initial'); + } else if (this.messages.type === 'initialLastSeen') { + this.newListMessagesUnreadMessages( + this.messages.messages, + 'initialLastSeen', + this.messages.lastReadMessageTimestamp, + this.messages.count + ); + } else if (this.messages.type === 'new') + this.addNewMessages(this.messages.messages); + else if (this.messages.type === 'newComingInAuto') + this.addNewMessages(this.messages.messages, 'newComingInAuto'); + else if (this.messages.type === 'old') + this.prependOldMessages(this.messages.messages); + else if (this.messages.type === 'inBetween') + this.newListMessages( + this.messages.messages, + this.messages.signature + ); + else if (this.messages.type === 'update') + this.replaceMessagesWithUpdateByArray(this.messages.messages); + } + if ( + changedProperties && + changedProperties.has('updateMessageHash') && + Object.keys(this.updateMessageHash).length > 0 + ) { + this.replaceMessagesWithUpdate(this.updateMessageHash); + } + if ( + changedProperties && + changedProperties.has('messageQueue') && + Object.keys(this.messageQueue).length > 0 + ) { + if (!this.disableAddingNewMessages) { + await new Promise((res) => { + setTimeout(() => { + res(); + }, 200); + }); + const viewElement = + this.shadowRoot.querySelector('#viewElement'); + viewElement.scrollTop = viewElement.scrollHeight + 200; + } + } + } + + isLastMessageBeforeUnread(message, formattedMessages) { + // if the message is the last one in the older messages list and its timestamp is before the user's last seen timestamp + if ( + message.timestamp < this.lastReadMessageTimestamp && + formattedMessages.indexOf(message) === formattedMessages.length - 21 + ) { + return true; + } + return false; + } + + render() { + + let formattedMessages = this.messagesToRender; + + return html` + ${this.isLoadingBefore + ? html` +
+ +
+ ` + : ''} +
    +
    + ${repeat( + formattedMessages, + (formattedMessage) => formattedMessage.reference, // Use .id as the unique key for formattedMessage. + (formattedMessage) => html` + ${repeat( + formattedMessage.messages, + (message) => message.signature, + (message, indexMessage) => html` + 1} + ?isLastMessageInGroup=${indexMessage === + formattedMessage.messages.length - 1} + .setToggledMessage=${this.setToggledMessage} + .setForwardProperties=${this + .setForwardProperties} + .setOpenPrivateMessage=${(val) => + this.setOpenPrivateMessage(val)} + .setOpenTipUser=${(val) => + this.setOpenTipUser(val)} + .setOpenUserInfo=${(val) => + this.setOpenUserInfo(val)} + .setUserName=${(val) => + this.setUserName(val)} + id=${message.signature} + .goToRepliedMessage=${(val, val2) => + this.goToRepliedMessageFunc(val, val2)} + .addSeenMessage=${(val) => + this.addSeenMessage(val)} + .listSeenMessages=${this.listSeenMessages} + chatId=${this.chatId} + > + ${message.isDivider + ? html`
    + ${translate('chatpage.cchange92')} +
    ` + : null} + ` + )} + ` + )} +
    this.chatId.includes(item._chatId)).length > 0 ? 'height: 1px' : 'height: 1px'} + id="bottomObserverForFetchingMessages" + >
    + +
    + + ${this.isLoadingAfter + ? html` +
    + +
    + ` + : ''} + ${!this.disableAddingNewMessages ? repeat( + this.messageQueue.filter((item) => + this.chatId.includes(item._chatId) + ), + (message) => message.messageText, + (message) => html` + + this.setOpenPrivateMessage(val)} + .setOpenTipUser=${(val) => this.setOpenTipUser(val)} + .setOpenUserInfo=${(val) => + this.setOpenUserInfo(val)} + .setUserName=${(val) => this.setUserName(val)} + id=${message.signature} + .goToRepliedMessage=${(val, val2) => + this.goToRepliedMessageFunc(val, val2)} + .addSeenMessage=${(val) => this.addSeenMessage(val)} + .listSeenMessages=${this.listSeenMessages} + chatId=${this.chatId} + ?isInProgress=${true} + > + ` + ): ''} +
+ `; + } + + shouldUpdate(changedProperties) { + if (changedProperties.has('isLoadingMessages')) { + return true; + } + if ( + changedProperties.has('chatId') && + changedProperties.get('chatId') + ) { + return true; + } + if (changedProperties.has('openTipUser')) { + return true; + } + if (changedProperties.has('openUserInfo')) { + return true; + } + if (changedProperties.has('userName')) { + return true; + } + if (changedProperties.has('loggedInUserName')) { + return true; + } + if (changedProperties.has('updateMessageHash')) { + return true; + } + if (changedProperties.has('messagesToRender')) { + return true; + } + if (changedProperties.has('isLoadingBefore')) { + return true; + } + if (changedProperties.has('isLoadingAfter')) { + return true; + } + if (changedProperties.has('messageQueue')) { + return true; + } + // Only update element if prop1 changed. + return changedProperties.has('messages'); + } + + async getUpdateComplete() { + await super.getUpdateComplete(); + const marginElements = Array.from( + this.shadowRoot.querySelectorAll('message-template') + ); + await Promise.all(marginElements.map((el) => el.updateComplete)); + return true; + } + + setToggledMessage(message) { + toggledMessage = message; + } + + async firstUpdated() { + this.changeTheme(); + window.addEventListener('storage', () => { + const checkTheme = localStorage.getItem('qortalTheme'); + + if (checkTheme === 'dark') { + this.theme = 'dark'; + } else { + this.theme = 'light'; + } + document.querySelector('html').setAttribute('theme', this.theme); + }); + + this.emojiPicker.on('emoji', (selection) => { + this.sendMessage({ + type: 'reaction', + editedMessageObj: toggledMessage, + reaction: selection.emoji, + }); + }); + this.viewElement = this.shadowRoot.getElementById('viewElement'); + this.upObserverElement = this.shadowRoot.getElementById('upObserver'); + this.downObserverElement = + this.shadowRoot.getElementById('downObserver'); + this.bottomObserverForFetchingMessages = this.shadowRoot.getElementById( + 'bottomObserverForFetchingMessages' + ); + // Intialize Observers + this.upElementObserver(); + this.downElementObserver(); + this.bottomObserver(); + + this.clearConsole(); + setInterval(() => { + this.clearConsole(); + }, 60000); + } + + clearConsole() { + if (!isElectron()) { /* empty */ } else { + console.clear(); + window.parent.electronAPI.clearCache(); + } + } + + changeTheme() { + const checkTheme = localStorage.getItem('qortalTheme'); + if (checkTheme === 'dark') { + this.theme = 'dark'; + } else { + this.theme = 'light'; + } + document.querySelector('html').setAttribute('theme', this.theme); + } + + _getOldMessage(_scrollElement) { + this.getOldMessage(_scrollElement); + } + _getAfterMessages(_scrollElement) { + this.getAfterMessages(_scrollElement); + } + + _upObserverhandler(entries) { + if (!entries[0].target || !entries[0].target.nextElementSibling) return; + if (entries[0].isIntersecting) { + if (this.disableFetching) { + return; + } + this.disableFetching = true; + this.isLoadingBefore = true; + let _scrollElement = entries[0].target.nextElementSibling; + this._getOldMessage(_scrollElement); + } + } + + _downObserverHandler(entries) { + if (!entries[0].isIntersecting) { + this.showLastMessageRefScroller(true); + } else { + this.showLastMessageRefScroller(false); + } + } + + __bottomObserverForFetchingMessagesHandler(entries) { + if (this.messagesToRender.length === 0 || this.disableFetching) { + return; + } + if(!this.disableAddingNewMessages) return + if ( + !entries[0].isIntersecting || + !entries[0].target || + !entries[0].target.previousElementSibling + ) { /* empty */ } else { + this.disableFetching = true; + this.isLoadingAfter = true; + let _scrollElement = entries[0].target.previousElementSibling; + this._getAfterMessages(_scrollElement); + } + } + + upElementObserver() { + const options = { + root: this.viewElement, + rootMargin: '0px', + threshold: 1, + }; + const observer = new IntersectionObserver( + this._upObserverhandler, + options + ); + observer.observe(this.upObserverElement); + } + + downElementObserver() { + const options = {}; + // identify an element to observe + const elementToObserve = this.downObserverElement; + // passing it a callback function + const observer = new IntersectionObserver( + this._downObserverHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } + bottomObserver() { + const options = {}; + // identify an element to observe + const elementToObserve = this.bottomObserverForFetchingMessages; + // passing it a callback function + const observer = new IntersectionObserver( + this.__bottomObserverForFetchingMessagesHandler, + options + ); + // call `observe()` on that MutationObserver instance, + // passing it the element to observe, and the options object + observer.observe(elementToObserve); + } } -window.customElements.define('chat-scroller', ChatScroller) - +window.customElements.define('chat-scroller', ChatScroller); class MessageTemplate extends LitElement { - static get properties() { - return { - messageObj: { type: Object }, - emojiPicker: { attribute: false }, - escapeHTML: { attribute: false }, - hideMessages: { type: Array }, - openDialogPrivateMessage: { type: Boolean }, - openDialogBlockUser: { type: Boolean }, - showBlockAddressIcon: { type: Boolean }, - setRepliedToMessageObj: { attribute: false }, - setEditedMessageObj: { attribute: false }, - sendMessage: { attribute: false }, - sendMessageForward: { attribute: false }, - openDialogImage: { type: Boolean }, - openDialogGif: { type: Boolean }, - openDeleteImage: { type: Boolean }, - openDeleteAttachment: { type: Boolean }, - isImageLoaded: { type: Boolean }, - isGifLoaded: { type: Boolean }, - isFirstMessage: { type: Boolean }, - isSingleMessageInGroup: { type: Boolean }, - isLastMessageInGroup: { type: Boolean }, - setToggledMessage: { attribute: false }, - setForwardProperties: { attribute: false }, - viewImage: { type: Boolean }, - setOpenPrivateMessage: { attribute: false }, - setOpenTipUser: { attribute: false }, - setOpenUserInfo: { attribute: false }, - setUserName: { attribute: false }, - openTipUser: { type: Boolean }, - goToRepliedMessage: { attribute: false }, - listSeenMessages: { type: Array }, - addSeenMessage: { attribute: false }, - chatId: { type: String } - } + static get properties() { + return { + messageObj: { type: Object }, + emojiPicker: { attribute: false }, + escapeHTML: { attribute: false }, + hideMessages: { type: Array }, + openDialogPrivateMessage: { type: Boolean }, + openDialogBlockUser: { type: Boolean }, + showBlockAddressIcon: { type: Boolean }, + setRepliedToMessageObj: { attribute: false }, + setEditedMessageObj: { attribute: false }, + sendMessage: { attribute: false }, + sendMessageForward: { attribute: false }, + openDialogImage: { type: Boolean }, + openDialogGif: { type: Boolean }, + openDeleteImage: { type: Boolean }, + openDeleteAttachment: { type: Boolean }, + isImageLoaded: { type: Boolean }, + isGifLoaded: { type: Boolean }, + isFirstMessage: { type: Boolean }, + isSingleMessageInGroup: { type: Boolean }, + isLastMessageInGroup: { type: Boolean }, + setToggledMessage: { attribute: false }, + setForwardProperties: { attribute: false }, + viewImage: { type: Boolean }, + setOpenPrivateMessage: { attribute: false }, + setOpenTipUser: { attribute: false }, + setOpenUserInfo: { attribute: false }, + setUserName: { attribute: false }, + openTipUser: { type: Boolean }, + goToRepliedMessage: { attribute: false }, + listSeenMessages: { type: Array }, + addSeenMessage: { attribute: false }, + chatId: { type: String }, + isInProgress: { type: Boolean }, + id: { type: String }, + }; + } + + constructor() { + super(); + this.messageObj = {}; + this.openDialogPrivateMessage = false; + this.openDialogBlockUser = false; + this.showBlockAddressIcon = false; + this.myAddress = + window.parent.reduxStore.getState().app.selectedAddress.address; + this.imageFetches = 0; + this.gifFetches = 0; + this.openDialogImage = false; + this.openDialogGif = false; + this.isImageLoaded = false; + this.isGifLoaded = false; + this.isFirstMessage = false; + this.isSingleMessageInGroup = false; + this.isLastMessageInGroup = false; + this.viewImage = false; + this.isInProgress = false; + } + + static get styles() { + return [chatStyles]; + } + + // Open & Close Private Message Chat Modal + showPrivateMessageModal() { + this.openDialogPrivateMessage = true; + } + + hidePrivateMessageModal() { + this.openDialogPrivateMessage = false; + } + + // Open & Close Block User Chat Modal + showBlockUserModal() { + this.openDialogBlockUser = true; + } + + hideBlockUserModal() { + this.openDialogBlockUser = false; + } + + showBlockIconFunc(bool) { + if (bool) { + this.showBlockAddressIcon = true; + } else { + this.showBlockAddressIcon = false; + } + } + + async downloadAttachment(attachment) { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + try { + axios + .get( + `${nodeUrl}/arbitrary/QCHAT_ATTACHMENT/${attachment.name}/${attachment.identifier}?apiKey=${myNode.apiKey}`, + { responseType: 'blob' } + ) + .then((response) => { + let filename = attachment.attachmentName; + let blob = new Blob([response.data], { + type: 'application/octet-stream', + }); + this.saveFileToDisk(blob, filename); + }); + } catch (error) { + console.error(error); + } + } + + async saveFileToDisk(blob, fileName) { + try { + const fileHandle = await self.showSaveFilePicker({ + suggestedName: fileName, + types: [ + { + description: 'File', + }, + ], + }); + const writeFile = async (fileHandle, contents) => { + const writable = await fileHandle.createWritable(); + await writable.write(contents); + await writable.close(); + }; + writeFile(fileHandle, blob).then(() => console.log('FILE SAVED')); + } catch (error) { + console.log(error); + } + } + + setOpenDialogImage(val){ + this.openDialogImage = val } - constructor() { - super() - this.messageObj = {} - this.openDialogPrivateMessage = false - this.openDialogBlockUser = false - this.showBlockAddressIcon = false - this.myAddress = window.parent.reduxStore.getState().app.selectedAddress.address - this.imageFetches = 0 - this.gifFetches = 0 - this.openDialogImage = false - this.openDialogGif = false - this.isImageLoaded = false - this.isGifLoaded = false - this.isFirstMessage = false - this.isSingleMessageInGroup = false - this.isLastMessageInGroup = false - this.viewImage = false - } + firstUpdated() { + const autoSeeChatList = + window.parent.reduxStore.getState().app.autoLoadImageChats; + if ( + autoSeeChatList.includes(this.chatId) || + this.listSeenMessages.includes(this.messageObj.signature) + ) { + this.viewImage = true; + } - static get styles() { - return [chatStyles] - } + const tooltips = this.shadowRoot.querySelectorAll('vaadin-tooltip'); + tooltips.forEach((tooltip) => { + const overlay = tooltip.shadowRoot.querySelector( + 'vaadin-tooltip-overlay' + ); + overlay.shadowRoot.getElementById('overlay').style.cssText = + 'background-color: transparent; box-shadow: rgb(50 50 93 / 25%) 0px 2px 5px -1px, rgb(0 0 0 / 30%) 0px 1px 3px -1px'; + overlay.shadowRoot.getElementById('content').style.cssText = + 'background-color: var(--reactions-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; border-radius: 8px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;'; + }); + this.clearConsole(); + setInterval(() => { + this.clearConsole(); + }, 60000); + } + + shouldUpdate(changedProperties) { + if (changedProperties.has('messageObj')) { + return true; + } + if (changedProperties.has('showBlockAddressIcon')) { + return true; + } + if (changedProperties.has('openDialogBlockUser')) { + return true; + } + if (changedProperties.has('viewImage')) { + return true; + } + if (changedProperties.has('isImageLoaded')) { + return true; + } + if (changedProperties.has('openDialogImage')) { + return true; + } + if (changedProperties.has('openDialogPrivateMessage')) { + return true; + } + if (changedProperties.has('openDialogGif')) { + return true; + } + if (changedProperties.has('isGifLoaded')) { + return true; + } + return false; + } + + clearConsole() { + if (!isElectron()) { /* empty */ } else { + console.clear(); + window.parent.electronAPI.clearCache(); + } + } + + render() { + const hidemsg = this.hideMessages; + let message = ''; + let messageVersion2 = ''; + let messageVersion2WithLink = null; + let reactions = []; + let repliedToData = null; + let image = null; + let gif = null; + let isImageDeleted = false; + let isAttachmentDeleted = false; + let version = 0; + let isForwarded = false; + let isEdited = false; + let attachment = null; + try { + const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage); + if (+parsedMessageObj.version > 1 && parsedMessageObj.messageText) { + messageVersion2 = generateHTML(parsedMessageObj.messageText, [ + StarterKit, + Underline, + Highlight, + // other extensions … + ]); + messageVersion2WithLink = processText(messageVersion2); + } + message = parsedMessageObj.messageText; + repliedToData = this.messageObj.repliedToData; + isImageDeleted = parsedMessageObj.isImageDeleted; + isAttachmentDeleted = parsedMessageObj.isAttachmentDeleted; + // reactions = parsedMessageObj.reactions || [] + version = parsedMessageObj.version; + isForwarded = parsedMessageObj.type === 'forward'; + isEdited = parsedMessageObj.isEdited && true; + if ( + parsedMessageObj.attachments && + Array.isArray(parsedMessageObj.attachments) && + parsedMessageObj.attachments.length > 0 + ) { + attachment = parsedMessageObj.attachments[0]; + } + if ( + parsedMessageObj.images && + Array.isArray(parsedMessageObj.images) && + parsedMessageObj.images.length > 0 + ) { + image = parsedMessageObj.images[0]; + } + if ( + parsedMessageObj.gifs && + Array.isArray(parsedMessageObj.gifs) && + parsedMessageObj.gifs.length > 0 + ) { + gif = parsedMessageObj.gifs[0]; + } + } catch (error) { + message = this.messageObj.decodedMessage; + } + let avatarImg = ''; + let imageHTML = ''; + let imageUrl = ''; + let gifHTML = ''; + let gifHTMLDialog = ''; + let gifUrl = ''; + let nameMenu = ''; + let levelFounder = ''; + let hideit = hidemsg.includes(this.messageObj.sender); + let forwarded = ''; + let edited = ''; + + levelFounder = html``; + + 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; + const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`; + avatarImg = html``; + } else { + avatarImg = html``; + } - // Open & Close Private Message Chat Modal - showPrivateMessageModal() { - this.openDialogPrivateMessage = true - } + const createGif = (gif) => { + const gifHTMLRes = new Image(); + gifHTMLRes.src = gif; + gifHTMLRes.style = + 'max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer;'; + gifHTMLRes.onclick = () => { + this.openDialogGif = true; + }; + gifHTMLRes.onload = () => { + this.isGifLoaded = true; + }; + gifHTMLRes.onerror = () => { + if (this.gifFetches < 4) { + setTimeout(() => { + this.gifFetches = this.gifFetches + 1; + gifHTMLRes.src = gif; + }, 10000); + } else { + gifHTMLRes.src = '/img/chain.png'; + gifHTMLRes.style = + 'max-width:45vh; max-height:20vh; border-radius: 5px; filter: opacity(0.5);'; + gifHTMLRes.onclick = () => {}; + this.isGifLoaded = true; + } + }; + return gifHTMLRes; + }; - hidePrivateMessageModal() { - this.openDialogPrivateMessage = false - } + if (image) { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + imageUrl = `${nodeUrl}/arbitrary/${image.service}/${image.name}/${image.identifier}?async=true&apiKey=${myNode.apiKey}`; - // Open & Close Block User Chat Modal - showBlockUserModal() { - this.openDialogBlockUser = true - } + if (this.viewImage || this.myAddress === this.messageObj.sender) { + imageHTML = html` this.setOpenDialogImage(val)} + >`; + // imageHTML = createImage(imageUrl) + // imageHTMLDialog = createImage(imageUrl) + // imageHTMLDialog.style = "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px;" + } + } - hideBlockUserModal() { - this.openDialogBlockUser = false - } + if (gif) { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + gifUrl = `${nodeUrl}/arbitrary/${gif.service}/${gif.name}/${gif.identifier}?filepath=${gif.filePath}&apiKey=${myNode.apiKey}`; + if (this.viewImage || this.myAddress === this.messageObj.sender) { + gifHTML = createGif(gifUrl); + gifHTMLDialog = createGif(gifUrl); + gifHTMLDialog.style = + 'height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px;'; + } + } - showBlockIconFunc(bool) { - if (bool) { - this.showBlockAddressIcon = true - } else { - this.showBlockAddressIcon = false - } - } + nameMenu = html` + + ${this.messageObj.senderName + ? this.messageObj.senderName + : cropAddress(this.messageObj.sender)} + + `; - async downloadAttachment(attachment) { + forwarded = html` + + ${translate('blockpage.bcchange17')} + + `; - const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] + edited = html` + + ${translate('chatpage.cchange68')} + + `; - const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port - try { - axios.get(`${nodeUrl}/arbitrary/QCHAT_ATTACHMENT/${attachment.name}/${attachment.identifier}?apiKey=${myNode.apiKey}`, { responseType: 'blob' }) - .then(response => { - let filename = attachment.attachmentName - let blob = new Blob([response.data], { type: "application/octet-stream" }) - this.saveFileToDisk(blob, filename) - }) - } catch (error) { - console.error(error) - } - } + if (repliedToData) { + try { + const parsedMsg = JSON.parse(repliedToData.decodedMessage); + repliedToData.decodedMessage = parsedMsg; + } catch (error) { /* empty */ } + } - async saveFileToDisk(blob, fileName) { - try { - const fileHandle = await self.showSaveFilePicker({ - suggestedName: fileName, - types: [{ - description: "File", - }] - }) - const writeFile = async (fileHandle, contents) => { - const writable = await fileHandle.createWritable() - await writable.write(contents) - await writable.close() - } - writeFile(fileHandle, blob).then(() => console.log("FILE SAVED")) - } catch (error) { - console.log(error) - } - } + let repliedToMessageText = ''; + if ( + repliedToData && + repliedToData.decodedMessage && + repliedToData.decodedMessage.messageText + ) { + try { + repliedToMessageText = generateHTML( + repliedToData.decodedMessage.messageText, + [ + StarterKit, + Underline, + Highlight, + // other extensions … + ] + ); + } catch (error) { /* empty */ } + } - firstUpdated() { - const autoSeeChatList = window.parent.reduxStore.getState().app.autoLoadImageChats - if (autoSeeChatList.includes(this.chatId) || this.listSeenMessages.includes(this.messageObj.signature)) { - this.viewImage = true - } + let replacedMessage = ''; + if (message && +version < 2) { + const escapedMessage = this.escapeHTML(message); + if (escapedMessage) { + replacedMessage = escapedMessage.replace( + // eslint-disable-next-line no-control-regex + new RegExp('\r?\n', 'g'), + '
' + ); + } + } - const tooltips = this.shadowRoot.querySelectorAll('vaadin-tooltip') - tooltips.forEach(tooltip => { - const overlay = tooltip.shadowRoot.querySelector('vaadin-tooltip-overlay') - overlay.shadowRoot.getElementById("overlay").style.cssText = "background-color: transparent; box-shadow: rgb(50 50 93 / 25%) 0px 2px 5px -1px, rgb(0 0 0 / 30%) 0px 1px 3px -1px" - overlay.shadowRoot.getElementById('content').style.cssText = "background-color: var(--reactions-tooltip-bg); color: var(--chat-bubble-msg-color); text-align: center; padding: 20px 10px; border-radius: 8px; font-family: Roboto, sans-serif; letter-spacing: 0.3px; font-weight: 300; font-size: 13.5px; transition: all 0.3s ease-in-out;" - }) - this.clearConsole() - setInterval(() => { - this.clearConsole() - }, 60000) - } - - clearConsole() { - if (!isElectron()) { - } else { - console.clear() - window.parent.electronAPI.clearCache() - } - } - - render() { - const hidemsg = this.hideMessages - let message = "" - let messageVersion2 = "" - let messageVersion2WithLink = null - let reactions = [] - let repliedToData = null - let image = null - let gif = null - let isImageDeleted = false - let isAttachmentDeleted = false - let version = 0 - let isForwarded = false - let isEdited = false - let attachment = null - try { - const parsedMessageObj = JSON.parse(this.messageObj.decodedMessage) - if (+parsedMessageObj.version > 1 && parsedMessageObj.messageText) { - messageVersion2 = generateHTML(parsedMessageObj.messageText, [ - StarterKit, - Underline, - Highlight - // other extensions … - ]) - messageVersion2WithLink = processText(messageVersion2) - - } - message = parsedMessageObj.messageText - repliedToData = this.messageObj.repliedToData - isImageDeleted = parsedMessageObj.isImageDeleted - isAttachmentDeleted = parsedMessageObj.isAttachmentDeleted - // reactions = parsedMessageObj.reactions || [] - version = parsedMessageObj.version - isForwarded = parsedMessageObj.type === 'forward' - isEdited = parsedMessageObj.isEdited && true - if (parsedMessageObj.attachments && Array.isArray(parsedMessageObj.attachments) && parsedMessageObj.attachments.length > 0) { - attachment = parsedMessageObj.attachments[0] - } - if (parsedMessageObj.images && Array.isArray(parsedMessageObj.images) && parsedMessageObj.images.length > 0) { - image = parsedMessageObj.images[0] - } - if (parsedMessageObj.gifs && Array.isArray(parsedMessageObj.gifs) && parsedMessageObj.gifs.length > 0) { - gif = parsedMessageObj.gifs[0] - } - } catch (error) { - message = this.messageObj.decodedMessage - } - let avatarImg = '' - let imageHTML = '' - let imageHTMLDialog = '' - let imageUrl = '' - let gifHTML = '' - let gifHTMLDialog = '' - let gifUrl = '' - let nameMenu = '' - let levelFounder = '' - let hideit = hidemsg.includes(this.messageObj.sender) - let forwarded = '' - let edited = '' - - levelFounder = html`` - - 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 - const avatarUrl = `${nodeUrl}/arbitrary/THUMBNAIL/${this.messageObj.senderName}/qortal_avatar?async=true&apiKey=${myNode.apiKey}` - avatarImg = html`` - } else { - avatarImg = html`` - } - - const createImage = (imageUrl) => { - const imageHTMLRes = new Image() - imageHTMLRes.src = imageUrl - imageHTMLRes.style = "max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer;" - imageHTMLRes.onclick = () => { - this.openDialogImage = true - } - imageHTMLRes.onload = () => { - this.isImageLoaded = true - } - imageHTMLRes.onerror = () => { - if (this.imageFetches < 4) { - setTimeout(() => { - this.imageFetches = this.imageFetches + 1 - imageHTMLRes.src = imageUrl - }, 10000) - } else { - setTimeout(() => { - this.imageFetches = this.imageFetches + 1 - imageHTMLRes.src = imageUrl - }, 15000) - } - } - return imageHTMLRes - } - - const createGif = (gif) => { - const gifHTMLRes = new Image() - gifHTMLRes.src = gif - gifHTMLRes.style = "max-width:45vh; max-height:40vh; border-radius: 5px; cursor: pointer;" - gifHTMLRes.onclick = () => { - this.openDialogGif = true - } - gifHTMLRes.onload = () => { - this.isGifLoaded = true - } - gifHTMLRes.onerror = () => { - if (this.gifFetches < 4) { - setTimeout(() => { - this.gifFetches = this.gifFetches + 1 - gifHTMLRes.src = gif - }, 10000) - } else { - gifHTMLRes.src = '/img/chain.png' - gifHTMLRes.style = "max-width:45vh; max-height:20vh; border-radius: 5px; filter: opacity(0.5);" - gifHTMLRes.onclick = () => { } - this.isGifLoaded = true - } - } - return gifHTMLRes - } - - if (image) { - const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] - const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port - imageUrl = `${nodeUrl}/arbitrary/${image.service}/${image.name}/${image.identifier}?async=true&apiKey=${myNode.apiKey}` - - if (this.viewImage || this.myAddress === this.messageObj.sender) { - imageHTML = createImage(imageUrl) - imageHTMLDialog = createImage(imageUrl) - imageHTMLDialog.style = "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px;" - } - } - - if (gif) { - const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] - const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port - gifUrl = `${nodeUrl}/arbitrary/${gif.service}/${gif.name}/${gif.identifier}?filepath=${gif.filePath}&apiKey=${myNode.apiKey}` - if (this.viewImage || this.myAddress === this.messageObj.sender) { - gifHTML = createGif(gifUrl) - gifHTMLDialog = createGif(gifUrl) - gifHTMLDialog.style = "height: auto; max-height: 80vh; width: auto; max-width: 80vw; object-fit: contain; border-radius: 5px;" - } - } - - nameMenu = html` - - ${this.messageObj.senderName ? this.messageObj.senderName : cropAddress(this.messageObj.sender)} - - ` - - forwarded = html` - - ${translate("blockpage.bcchange17")} - - ` - - edited = html` - - ${translate("chatpage.cchange68")} - - ` - - if (repliedToData) { - try { - const parsedMsg = JSON.parse(repliedToData.decodedMessage) - repliedToData.decodedMessage = parsedMsg - } catch (error) { - } - - } - - let repliedToMessageText = '' - if (repliedToData && repliedToData.decodedMessage && repliedToData.decodedMessage.messageText) { - try { - repliedToMessageText = generateHTML(repliedToData.decodedMessage.messageText, [ - StarterKit, - Underline, - Highlight - // other extensions … - ]) - } catch (error) { - - } - } - - let replacedMessage = '' - if (message && +version < 2) { - const escapedMessage = this.escapeHTML(message) - if (escapedMessage) { - replacedMessage = escapedMessage.replace(new RegExp('\r?\n', 'g'), '
') - } - - } - - return hideit ? html`
  • ` : html` + return hideit + ? html`
  • ` + : html`
  • -
    - ${(this.isSingleMessageInGroup === false || - (this.isSingleMessageInGroup === true && this.isLastMessageInGroup === true)) - ? ( - html` -
    { - if (this.myAddress === this.messageObj.sender) return - this.setOpenUserInfo(true) - this.setUserName(this.messageObj) - }} class="message-data-avatar"> - ${avatarImg} -
    - ` - ) : - html` -
    - `} + style="${ + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === false && + 'margin-bottom: 0' + }"> +
    + ${ + this.isSingleMessageInGroup === false || + (this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === true) + ? html` +
    { + if ( + this.myAddress === + this.messageObj.sender + ) + return; + this.setOpenUserInfo(true); + this.setUserName( + this.messageObj + ); + }} + class="message-data-avatar" + > + ${avatarImg} +
    + ` + : html` +
    + ` + }
    + ${ + this.myAddress === this.messageObj.sender && + 'message-myBg' + } + ${ + ((this.isFirstMessage === true && + this.isSingleMessageInGroup === false) || + (this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === true)) && + this.myAddress !== this.messageObj.sender + ? 'message-triangle' + : ((this.isFirstMessage === true && + this.isSingleMessageInGroup === + false) || + (this.isSingleMessageInGroup === + true && + this.isLastMessageInGroup === + true)) && + this.myAddress === this.messageObj.sender + ? 'message-myTriangle' + : null + }`}" + style="${ + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === false + ? 'margin-bottom: 0;' + : null + } + ${ + this.isFirstMessage === false && + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === false + ? 'border-radius: 8px 25px 25px 8px;' + : this.isFirstMessage === true && + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === false + ? 'border-radius: 27px 25px 25px 12px;' + : this.isFirstMessage === false && + this.isSingleMessageInGroup === true && + this.isLastMessageInGroup === true + ? 'border-radius: 10px 25px 25px 0;' + : this.isFirstMessage === true && + this.isSingleMessageInGroup === false && + this.isLastMessageInGroup === true + ? 'border-radius: 25px 25px 25px 0px;' + : null + }"> - ${repliedToData && html` -
    { - this.goToRepliedMessage(repliedToData, this.messageObj) - }}> -

    - ${repliedToData.senderName ? repliedToData.senderName : cropAddress(repliedToData.sender)} -

    -

    - ${version && version.toString() === '1' ? html` - ${repliedToData.decodedMessage.messageText} - ` : ''} - ${+version > 1 && repliedToMessageText ? html` - ${unsafeHTML(repliedToMessageText)} - ` - : ''} -

    -
    - `} - ${image && !isImageDeleted && !this.viewImage && this.myAddress !== this.messageObj.sender ? html` -
    { - this.viewImage = true - this.addSeenMessage(this.messageObj.signature) - }} - class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')} - style=${this.isFirstMessage && "margin-top: 10px;"}> -
    - ${translate("chatpage.cchange40")} -
    -
    - ` : html``} - ${!this.isImageLoaded && image && this.viewImage ? html` -
    -
    -
    - - `: ''} - ${image && !isImageDeleted && (this.viewImage || this.myAddress === this.messageObj.sender) ? html` -
    - ${imageHTML} - ${this.myAddress === this.messageObj.sender ? html` - { - this.openDeleteImage = true - }} - class="image-delete-icon" icon="vaadin:close" slot="icon"> - ` : ''} - -
    - ` : image && isImageDeleted ? html` -

    ${translate("chatpage.cchange80")}

    - ` : html``} - ${gif && !this.viewImage && this.myAddress !== this.messageObj.sender ? html` -
    { - this.viewImage = true - }} - class=${[`image-container`, !this.isImageLoaded ? 'defaultSize' : ''].join(' ')} - style=${this.isFirstMessage && "margin-top: 10px;"}> -
    - ${translate("gifs.gchange25")} -
    -
    - ` : html``} - ${gif && (this.viewImage || this.myAddress === this.messageObj.sender) ? html` -
    - ${gifHTML} -
    - ` : html``} - ${attachment && !isAttachmentDeleted ? - html` -
    await this.downloadAttachment(attachment)} class="attachment-container"> -
    - attachment-icon -
    -
    -

    - ${attachment && attachment.attachmentName} -

    -

    - ${roundToNearestDecimal(attachment.attachmentSize)} mb -

    -
    - - - ${this.myAddress === this.messageObj.sender - ? html` - { - e.stopPropagation() - this.openDeleteAttachment = true - }} - class="image-delete-icon" icon="vaadin:close" slot="icon"> - - ` : html``} -
    - ` - : attachment && isAttachmentDeleted ? - html` -
    -
    -

    - ${translate("chatpage.cchange82")} -

    -
    -
    - ` - : html``} + ${ + repliedToData && + html` +
    { + this.goToRepliedMessage( + repliedToData, + this.messageObj + ); + }} + > +

    + ${repliedToData.senderName + ? repliedToData.senderName + : cropAddress( + repliedToData.sender + )} +

    +

    + ${version && + version.toString() === '1' + ? html` + ${repliedToData + .decodedMessage + .messageText} + ` + : ''} + ${+version > 1 && + repliedToMessageText + ? html` + ${unsafeHTML( + repliedToMessageText + )} + ` + : ''} +

    +
    + ` + } + ${ + image && + !isImageDeleted && + !this.viewImage && + this.myAddress !== + this.messageObj.sender + ? html` +
    { + this.viewImage = true; + // this.addSeenMessage(this.messageObj.signature) + }} + class=${[ + `image-container`, + !this.isImageLoaded + ? 'defaultSize' + : '', + ].join(' ')} + style=${this + .isFirstMessage && + 'margin-top: 10px;'} + > +
    + ${translate( + 'chatpage.cchange40' + )} +
    +
    + ` + : html`` + } + + ${ + image && + !isImageDeleted && + (this.viewImage || + this.myAddress === + this.messageObj.sender) + ? html` +
    + ${imageHTML} + ${this.myAddress === + this.messageObj.sender + ? html` + { + this.openDeleteImage = true; + }} + class="image-delete-icon" + icon="vaadin:close" + slot="icon" + > + ` + : ''} +
    + ` + : image && isImageDeleted + ? html` +

    + ${translate( + 'chatpage.cchange80' + )} +

    + ` + : html`` + } + ${ + gif && + !this.viewImage && + this.myAddress !== + this.messageObj.sender + ? html` +
    { + this.viewImage = true; + }} + class=${[ + `image-container`, + !this.isImageLoaded + ? 'defaultSize' + : '', + ].join(' ')} + style=${this + .isFirstMessage && + 'margin-top: 10px;'} + > +
    + ${translate( + 'gifs.gchange25' + )} +
    +
    + ` + : html`` + } + ${ + gif && + (this.viewImage || + this.myAddress === + this.messageObj.sender) + ? html` +
    + ${gifHTML} +
    + ` + : html`` + } + ${ + attachment && !isAttachmentDeleted + ? html` +
    + await this.downloadAttachment( + attachment + )} + class="attachment-container" + > +
    + attachment-icon +
    +
    +

    + ${attachment && + attachment.attachmentName} +

    +

    + ${roundToNearestDecimal( + attachment.attachmentSize + )} + mb +

    +
    + + + ${this.myAddress === + this.messageObj.sender + ? html` + { + e.stopPropagation(); + this.openDeleteAttachment = true; + }} + class="image-delete-icon" + icon="vaadin:close" + slot="icon" + > + + ` + : html``} +
    + ` + : attachment && isAttachmentDeleted + ? html` +
    +
    +

    + ${translate( + 'chatpage.cchange82' + )} +

    +
    +
    + ` + : html`` + }
    - ${+version > 1 ? messageVersion2WithLink ? html`${messageVersion2WithLink}` : html` - ${unsafeHTML(messageVersion2)} - ` : ''} + style=${ + image && + replacedMessage !== '' && + 'margin-top: 15px;' + }> + ${ + +version > 1 + ? messageVersion2WithLink + ? html`${messageVersion2WithLink}` + : html` + ${unsafeHTML( + messageVersion2 + )} + ` + : '' + } - ${version && version.toString() === '1' ? html` - ${unsafeHTML(this.emojiPicker.parse(replacedMessage))} - ` : ''} - ${version && version.toString() === '0' ? html` - ${unsafeHTML(this.emojiPicker.parse(replacedMessage))} - ` : ''} + ${ + version && + version.toString() === '1' + ? html` + ${unsafeHTML( + this.emojiPicker.parse( + replacedMessage + ) + )} + ` + : '' + } + ${ + version && + version.toString() === '0' + ? html` + ${unsafeHTML( + this.emojiPicker.parse( + replacedMessage + ) + )} + ` + : '' + }
    - ${isEdited ? - html` - - ${edited} - - ` - : '' - } - + style=${ + isEdited + ? 'justify-content: space-between;' + : 'justify-content: flex-end;' + } + class="${ + (this.isFirstMessage === + false && + this + .isSingleMessageInGroup === + true && + this + .isLastMessageInGroup === + true) || + (this.isFirstMessage === true && + this + .isSingleMessageInGroup === + false && + this + .isLastMessageInGroup === + true) + ? 'message-data-time' + : 'message-data-time-hidden' + }"> + ${ + isEdited + ? html` + + ${edited} + + ` + : '' + } + ${ + this.isInProgress + ? html` +

    + ${translate( + 'chatpage.cchange91' + )} +

    + ` + : html` + + ` + } +
    - this.showPrivateMessageModal()} - .showBlockUserModal=${() => this.showBlockUserModal()} - .showBlockIconFunc=${(props) => this.showBlockIconFunc(props)} - .showBlockAddressIcon=${this.showBlockAddressIcon} - .originalMessage=${{ ...this.messageObj, message }} - .setRepliedToMessageObj=${this.setRepliedToMessageObj} - .setEditedMessageObj=${this.setEditedMessageObj} - .myAddress=${this.myAddress} - @blur=${() => this.showBlockIconFunc(false)} - .sendMessage=${this.sendMessage} - .sendMessageForward=${this.sendMessageForward} - version=${version} - .emojiPicker=${this.emojiPicker} - .setToggledMessage=${this.setToggledMessage} - .setForwardProperties=${this.setForwardProperties} - ?firstMessageInChat=${this.messageObj.firstMessageInChat} - .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} - .setOpenTipUser=${(val) => this.setOpenTipUser(val)} - .setUserName=${(val) => this.setUserName(val)} - .gif=${!!gif} - > - + ${ + this.isInProgress + ? '' + : html` + + this.showPrivateMessageModal()} + .showBlockUserModal=${() => + this.showBlockUserModal()} + .showBlockIconFunc=${(props) => + this.showBlockIconFunc( + props + )} + .showBlockAddressIcon=${this + .showBlockAddressIcon} + .originalMessage=${{ + ...this.messageObj, + message, + }} + .setRepliedToMessageObj=${this + .setRepliedToMessageObj} + .setEditedMessageObj=${this + .setEditedMessageObj} + .myAddress=${this.myAddress} + @blur=${() => + this.showBlockIconFunc( + false + )} + .sendMessage=${this.sendMessage} + .sendMessageForward=${this + .sendMessageForward} + version=${version} + .emojiPicker=${this.emojiPicker} + .setToggledMessage=${this + .setToggledMessage} + .setForwardProperties=${this + .setForwardProperties} + ?firstMessageInChat=${this + .messageObj + .firstMessageInChat} + .setOpenPrivateMessage=${( + val + ) => + this.setOpenPrivateMessage( + val + )} + .setOpenTipUser=${(val) => + this.setOpenTipUser(val)} + .setUserName=${(val) => + this.setUserName(val)} + .gif=${!!gif} + > + + ` + } +
    -
    +
    ${reactions.map((reaction, index) => { - return html` - this.sendMessage({ - type: 'reaction', - editedMessageObj: this.messageObj, - reaction: reaction.type, - })} - id=${`reactions-${index}`} - class="reactions-bg"> - ${reaction.type} - ${reaction.qty} - 3 ? - ( - `${reaction.users[0].name - ? reaction.users[0].name - : cropAddress(reaction.users[0].address)}, - ${reaction.users[1].name - ? reaction.users[1].name - : cropAddress(reaction.users[1].address)}, - ${reaction.users[2].name - ? reaction.users[2].name - : cropAddress(reaction.users[2].address)} - ${get("chatpage.cchange71")} ${reaction.users.length - 3} ${get("chatpage.cchange72")}${(reaction.users.length - 3) > 1 ? html`${get("chatpage.cchange73")}` : ""} ${get("chatpage.cchange74")} ${reaction.type}` - ) : reaction.users.length === 3 ? - ( - `${reaction.users[0].name - ? reaction.users[0].name - : cropAddress(reaction.users[0].address)}, - ${reaction.users[1].name - ? reaction.users[1].name - : cropAddress(reaction.users[1].address)} - ${get("chatpage.cchange71")} - ${reaction.users[2].name - ? reaction.users[2].name - : cropAddress(reaction.users[2].address)} ${get("chatpage.cchange74")} ${reaction.type}` - ) : reaction.users.length === 2 ? - ( - `${reaction.users[0].name - ? reaction.users[0].name - : cropAddress(reaction.users[0].address)} - ${get("chatpage.cchange71")} - ${reaction.users[1].name - ? reaction.users[1].name - : cropAddress(reaction.users[1].address)} ${get("chatpage.cchange74")} ${reaction.type}` - ) : reaction.users.length === 1 ? - ( - `${reaction.users[0].name - ? reaction.users[0].name - : cropAddress(reaction.users[0].address)} ${get("chatpage.cchange74")} ${reaction.type}` - ) - : ""}> - - - ` - })} + return html` + + this.sendMessage({ + type: 'reaction', + editedMessageObj: + this.messageObj, + reaction: reaction.type, + })} + id=${`reactions-${index}`} + class="reactions-bg" + > + ${reaction.type} ${reaction.qty} + 3 + ? `${ + reaction.users[0] + .name + ? reaction + .users[0] + .nameMessageTemplate + : cropAddress( + reaction + .users[0] + .address + ) + }, + ${ + reaction.users[1].name + ? reaction.users[1].name + : cropAddress( + reaction.users[1] + .address + ) + }, + ${ + reaction.users[2].name + ? reaction.users[2].name + : cropAddress( + reaction.users[2] + .address + ) + } + ${get('chatpage.cchange71')} ${ + reaction.users + .length - 3 + } ${get( + 'chatpage.cchange72' + )}${ + reaction.users + .length - + 3 > + 1 + ? html`${get( + 'chatpage.cchange73' + )}` + : '' + } ${get( + 'chatpage.cchange74' + )} ${reaction.type}` + : reaction.users.length === + 3 + ? `${ + reaction.users[0] + .name + ? reaction + .users[0] + .name + : cropAddress( + reaction + .users[0] + .address + ) + }, + ${ + reaction.users[1].name + ? reaction.users[1].name + : cropAddress( + reaction.users[1] + .address + ) + } + ${get('chatpage.cchange71')} + ${ + reaction.users[2].name + ? reaction.users[2].name + : cropAddress( + reaction.users[2] + .address + ) + } ${get('chatpage.cchange74')} ${ + reaction.type + }` + : reaction.users.length === + 2 + ? `${ + reaction.users[0] + .name + ? reaction + .users[0] + .name + : cropAddress( + reaction + .users[0] + .address + ) + } + ${get('chatpage.cchange71')} + ${ + reaction.users[1].name + ? reaction.users[1] + .namMessageTemplatee + : cropAddress( + reaction.users[1] + .address + ) + } ${get('chatpage.cchange74')} ${ + reaction.type + }` + : reaction.users.length === + 1 + ? `${ + reaction.users[0] + .name + ? reaction + .users[0] + .name + : cropAddress( + reaction + .users[0] + .address + ) + } ${get( + 'chatpage.cchange74' + )} ${reaction.type}` + : ''} + > + + + `; + })}
    @@ -1132,7 +2182,11 @@ class MessageTemplate extends LitElement { this.hidePrivateMessageModal()} .hideBlockUserModal=${() => this.hideBlockUserModal()} toblockaddress=${this.messageObj.sender} @@ -1142,30 +2196,32 @@ class MessageTemplate extends LitElement { id="showDialogPublicKey" ?open=${this.openDialogImage} @closed=${() => { - this.openDialogImage = false - }}> + this.openDialogImage = false; + }}>
    - ${imageHTMLDialog} + ${this.openDialogImage ? html` + + ` : ''} +
    { - - this.openDialogImage = false - }} + this.openDialogImage = false; + }} > - ${translate("general.close")} + ${translate('general.close')} { - this.openDialogGif = false - }}> + this.openDialogGif = false; + }}>MessageTemplate
    ${gifHTMLDialog} @@ -1175,34 +2231,35 @@ class MessageTemplate extends LitElement { dialogAction="cancel" class="red" @click=${() => { - - this.openDialogGif = false - }} + this.openDialogGif = false; + }} > - ${translate("general.close")} - + ${translate('general.close')} + MessageTemplate { - this.openDeleteImage = false - }}> + this.openDeleteImage = false; + }}>
    -

    ${translate("chatpage.cchange78")}

    +

    ${translate('chatpage.cchange78')}

    - - +
    @@ -515,7 +522,7 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b icon="vaadin:check" slot="icon" @click=${() => { - this.sendMessageFunc(); + this.sendMessageFunc(this.messageQueue); }} > @@ -538,7 +545,7 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b alt="send-icon" class="send-icon" @click=${() => { - this.sendMessageFunc(); + this.sendMessageFunc(this.messageQueue); }} /> ` : @@ -569,6 +576,29 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b } } + async handlePasteEvent(e) { + 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 + }); + this.insertFile(file); + } + } + } + async firstUpdated() { window.addEventListener('storage', () => { const checkTheme = localStorage.getItem('qortalTheme'); @@ -648,7 +678,7 @@ mwc-checkbox::shadow .mdc-checkbox::after, mwc-checkbox::shadow .mdc-checkbox::b return; } this.chatMessageSize = 0; - this._sendMessage(props, this.editor.getJSON()); + this._sendMessage(props, this.editor.getJSON(), this.messageQueue); } getMessageSize(message){ diff --git a/plugins/plugins/core/components/ChatWelcomePage.js b/plugins/plugins/core/components/ChatWelcomePage.js index 1deb25d1..b391f5d7 100644 --- a/plugins/plugins/core/components/ChatWelcomePage.js +++ b/plugins/plugins/core/components/ChatWelcomePage.js @@ -217,9 +217,9 @@ class ChatWelcomePage extends LitElement {
    + name: "", + open: true + })}"> ${translate("welcomepage.wcchange2")}
    @@ -240,9 +240,9 @@ class ChatWelcomePage extends LitElement {

    { - this._sendMessage(); - } - }> + this._sendMessage(); + } + }> ${translate("welcomepage.wcchange6")} { - this.balance = res - }) + }) parentEpml.imReady() diff --git a/plugins/plugins/core/components/ImageComponent.js b/plugins/plugins/core/components/ImageComponent.js index cb35702c..80bb6c26 100644 --- a/plugins/plugins/core/components/ImageComponent.js +++ b/plugins/plugins/core/components/ImageComponent.js @@ -1,126 +1,133 @@ -import { LitElement, html, css } from 'lit' -import { render } from 'lit/html.js' -import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' +import { LitElement, html, css } from 'lit'; +import { render } from 'lit/html.js'; +import { + use, + get, + translate, + translateUnsafeHTML, + registerTranslateConfig, +} from 'lit-translate'; export class ImageComponent extends LitElement { - -static get properties() { -return { -class: { type: String }, -gif: { type: Object }, -alt: { type: String }, -attempts: { type: Number }, -maxAttempts: { type: Number }, -error: { type: Boolean }, -sendMessage: { attribute: false }, -setOpenGifModal: { attribute: false } -} -} - -static get styles() { -return css` -.gif-error-msg { -margin: 0; -font-family: Roboto, sans-serif; -font-size: 17px; -letter-spacing: 0.3px; -color: var(--chat-bubble-msg-color); -font-weight: 300; -padding: 10px 10px; -} - -.gif-image { -border-radius: 15px; -background-color: transparent; -cursor: pointer; -width: 100%; -height: 150px; -object-fit: cover; -border: 1px solid transparent; -transition: all 0.2s cubic-bezier(0, 0.55, 0.45, 1); -box-shadow: rgb(50 50 93 / 25%) 0px 6px 12px -2px, rgb(0 0 0 / 30%) 0px 3px 7px -3px; -} - -.gif-image:hover { -border: 1px solid var(--mdc-theme-primary ); -} -` -} - -constructor() { - super() - this.attempts = 0 - this.maxAttempts = 5 -} -getApiKey() { - const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] - let apiKey = myNode.apiKey - return apiKey -} - -async _fetchImage() { - this.attempts++; - if (this.attempts > this.maxAttempts) return - await new Promise((res) => { - setTimeout(() => { - res() - }, 1000) - }) - try { - const response = await fetch(this.gif.url) - const data = await response.json() - if (data.ok) { - this.error = false - this.gif = { - ...this.gif, - url: data.src + static get properties() { + return { + class: { type: String }, + gif: { type: Object }, + alt: { type: String }, + attempts: { type: Number }, + maxAttempts: { type: Number }, + error: { type: Boolean }, + sendMessage: { attribute: false }, + setOpenGifModal: { attribute: false }, }; - this.requestUpdate(); - } else if (!data.ok || data.error) { - this.error = true - } else { - this.error = false + } + + static get styles() { + return css` + .gif-error-msg { + margin: 0; + font-family: Roboto, sans-serif; + font-size: 17px; + letter-spacing: 0.3px; + color: var(--chat-bubble-msg-color); + font-weight: 300; + padding: 10px 10px; + } + + .gif-image { + border-radius: 15px; + background-color: transparent; + cursor: pointer; + width: 100%; + height: 150px; + object-fit: cover; + border: 1px solid transparent; + transition: all 0.2s cubic-bezier(0, 0.55, 0.45, 1); + box-shadow: rgb(50 50 93 / 25%) 0px 6px 12px -2px, + rgb(0 0 0 / 30%) 0px 3px 7px -3px; + } + + .gif-image:hover { + border: 1px solid var(--mdc-theme-primary); + } + `; + } + + constructor() { + super(); + this.attempts = 0; + this.maxAttempts = 5; + } + getApiKey() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + let apiKey = myNode.apiKey; + return apiKey; + } + + async _fetchImage() { + this.attempts++; + if (this.attempts > this.maxAttempts) return; + await new Promise((res) => { + setTimeout(() => { + res(); + }, 1000); + }); + try { + const response = await fetch(this.gif.url); + const data = await response.json(); + if (data.ok) { + this.error = false; + this.gif = { + ...this.gif, + url: data.src, + }; + this.requestUpdate(); + } else if (!data.ok || data.error) { + this.error = true; + } else { + this.error = false; + } + } catch (error) { + this.error = true; + console.error(error); + this._fetchImage(); } - } catch (error) { - this.error = true - console.error(error) - this._fetchImage() + } + + render() { + if (this.error && this.attempts <= this.maxAttempts) { + setTimeout(() => { + this._fetchImage(); + }, 1000); + } + return html` ${this.gif && !this.error + ? html` ${this.alt} { + this.sendMessage({ + type: 'gif', + identifier: this.gif.identifier, + name: this.gif.name, + filePath: this.gif.filePath, + service: 'GIF_REPOSITORY', + }); + this.setOpenGifModal(false); + }} + @error=${this._fetchImage} + />` + : this.error && this.attempts <= this.maxAttempts + ? html` +

    ${translate('gifs.gchange15')}

    + ` + : html` +

    ${translate('gifs.gchange16')}

    + `}`; } } -render() { -if (this.error && this.attempts <= this.maxAttempts) { - setTimeout(() => { - this._fetchImage() - }, 1000) -} -return html` -${this.gif && !this.error - ? html` - { - this.sendMessage({ - type: 'gif', - identifier: this.gif.identifier, - name: this.gif.name, - filePath: this.gif.filePath, - service: "GIF_REPOSITORY" - }) - this.setOpenGifModal(false); -}} -@error=${this._fetchImage} -/>` - : this.error && this.attempts <= this.maxAttempts ? html` -

    ${translate('gifs.gchange15')}

    -` - : html` -

    ${translate('gifs.gchange16')}

    - ` -}` -} -} - -customElements.define('image-component', ImageComponent) +customElements.define('image-component', ImageComponent); diff --git a/plugins/plugins/core/components/LevelFounder.js b/plugins/plugins/core/components/LevelFounder.js index ade53a0f..635dda40 100644 --- a/plugins/plugins/core/components/LevelFounder.js +++ b/plugins/plugins/core/components/LevelFounder.js @@ -4,14 +4,17 @@ import { Epml } from '../../../epml.js' import snackbar from './snackbar.js' import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' import '@polymer/paper-tooltip/paper-tooltip.js' +import { RequestQueue } from '../../utils/queue.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) + const queue = new RequestQueue(3); + + class LevelFounder extends LitElement { static get properties() { return { checkleveladdress: { type: String }, - selectedAddress: { type: String }, config: { type: Object }, memberInfo: { type: Array } } @@ -39,7 +42,7 @@ class LevelFounder extends LitElement { } h2, h3, h4, h5 { - color:# var(--black); + color: var(--black); font-weight: 400; } @@ -88,7 +91,6 @@ class LevelFounder extends LitElement { constructor() { super() this.memberInfo = [] - this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress.address } render() { @@ -101,42 +103,37 @@ class LevelFounder extends LitElement { } firstUpdated() { - this.checkAddressInfo() - - parentEpml.ready().then(() => { - parentEpml.subscribe('selected_address', async selectedAddress => { - this.selectedAddress = {} - selectedAddress = JSON.parse(selectedAddress) - if (!selectedAddress || Object.entries(selectedAddress).length === 0) return - this.selectedAddress = selectedAddress - }) - }) - parentEpml.imReady() + queue.push(() => this.checkAddressInfo()); } async checkAddressInfo() { - let toCheck = this.checkleveladdress - const memberInfo = await parentEpml.request('apiCall', { - url: `/addresses/${toCheck}` - }) - this.memberInfo = memberInfo + try { + let toCheck = this.checkleveladdress + const memberInfo = await parentEpml.request('apiCall', { + url: `/addresses/${toCheck}` + }) + this.memberInfo = memberInfo + } catch (error) { + console.error(error) + } + } renderFounder() { let adressfounder = this.memberInfo.flags if (adressfounder === 1) { - return html ` + return html` F FOUNDER ` } else { - return html `` + return html`` } } renderLevel() { let adresslevel = this.memberInfo.level - return adresslevel ? html ` + return adresslevel ? html` ${`badge-${adresslevel}`} ${translate("mintingpage.mchange27")} ${adresslevel} diff --git a/plugins/plugins/core/components/NameMenu.js b/plugins/plugins/core/components/NameMenu.js index c4359637..9a0d2e5a 100644 --- a/plugins/plugins/core/components/NameMenu.js +++ b/plugins/plugins/core/components/NameMenu.js @@ -228,9 +228,9 @@ class NameMenu extends LitElement {

    { - this._sendMessage(); - } - }> + this._sendMessage(); + } + }> ${translate("welcomepage.wcchange6")} { - this.getChatBlockedAdresses() - }, 60000) + setInterval(() => { + this.getChatBlockedAdresses() + }, 60000) - window.onclick = function(event) { + window.onclick = function (event) { if (!event.target.matches('.block')) { var dropdowns = document.getElementsByClassName('dropdown-content'); var i; @@ -290,11 +290,7 @@ class NameMenu extends LitElement { if (!selectedAddress || Object.entries(selectedAddress).length === 0) return this.selectedAddress = selectedAddress }) - parentEpml.request('apiCall', { - url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}` - }).then(res => { - this.balance = res - }) + }) parentEpml.imReady() } @@ -333,7 +329,7 @@ class NameMenu extends LitElement { relMessages() { setTimeout(() => { - window.location.href = window.location.href.split( '#' )[0] + window.location.href = window.location.href.split('#')[0] }, 500) } @@ -407,8 +403,8 @@ class NameMenu extends LitElement { fetch(`${nodeUrl}/names/address/${item}?limit=0&reverse=true`).then(res => { return res.json() }).then(jsonRes => { - if(jsonRes.length) { - jsonRes.map (item => { + if (jsonRes.length) { + jsonRes.map(item => { obj.push(item) }) } else { diff --git a/plugins/plugins/core/components/ReusableImage.js b/plugins/plugins/core/components/ReusableImage.js new file mode 100644 index 00000000..8a61c411 --- /dev/null +++ b/plugins/plugins/core/components/ReusableImage.js @@ -0,0 +1,332 @@ +import { LitElement, html, css } from 'lit'; +import { + translate, +} from 'lit-translate'; +import axios from 'axios'; +import { RequestQueueWithPromise } from '../../utils/queue'; +import '@material/mwc-menu'; +import '@material/mwc-list/mwc-list-item.js'; +import '@material/mwc-dialog' + +const requestQueue = new RequestQueueWithPromise(5); +const requestQueue2 = new RequestQueueWithPromise(5); + + +export class ResuableImage extends LitElement { + static get properties() { + return { + resource: { type: Object }, + isReady: { type: Boolean }, + status: { type: Object }, + missingData: {type: Boolean}, + openDialogImage: { type: Boolean }, + onLoad: {attribute: false} + }; + } + + static get styles() { + return css` + * { + --mdc-theme-text-primary-on-background: var(--black); + --mdc-dialog-max-width: 85vw; + --mdc-dialog-max-height: 95vh; + } + img { + width: 100%; + height: auto; + object-fit: contain; + border-radius: 5px; + position: relative; + } + .smallLoading, + .smallLoading:after { + border-radius: 50%; + width: 2px; + height: 2px; + } + + .smallLoading { + border-width: 0.8em; + border-style: solid; + border-color: rgba(3, 169, 244, 0.2) rgba(3, 169, 244, 0.2) + rgba(3, 169, 244, 0.2) rgb(3, 169, 244); + font-size: 30px; + position: relative; + text-indent: -9999em; + transform: translateZ(0px); + animation: 1.1s linear 0s infinite normal none running + loadingAnimation; + } + .imageContainer { + display: flex; + justify-content: center; + align-items: center; + height: 100%; + } + + @-webkit-keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + + @keyframes loadingAnimation { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(360deg); + transform: rotate(360deg); + } + } + `; + } + + constructor() { + super(); + this.resource = { + identifier: '', + name: '', + service: '', + }; + this.status = { + status: '', + }; + this.url = ''; + this.isReady = false; + this.nodeUrl = this.getNodeUrl(); + this.myNode = this.getMyNode(); + this.hasCalledWhenDownloaded = false; + this.isFetching = false; + this.missingData = false + this.openDialogImage = false + + this.observer = new IntersectionObserver((entries) => { + for (const entry of entries) { + if (entry.isIntersecting && this.status.status !== 'READY') { + this._fetchImage(); + // Stop observing after the image has started loading + this.observer.unobserve(this); + } + } + }); + } + getNodeUrl() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + const nodeUrl = + myNode.protocol + '://' + myNode.domain + ':' + myNode.port; + return nodeUrl; + } + getMyNode() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + + return myNode; + } + + getApiKey() { + const myNode = + window.parent.reduxStore.getState().app.nodeConfig.knownNodes[ + window.parent.reduxStore.getState().app.nodeConfig.node + ]; + let apiKey = myNode.apiKey; + return apiKey; + } + + async fetchResource() { + try { + if (this.isFetching) return; + this.isFetching = true; + + await requestQueue2.enqueue(() => { + return axios.get( + `${this.nodeUrl}/arbitrary/resource/properties/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}` + ); + }); + this.isFetching = false; + } catch (error) { + this.isFetching = false; + } + } + + async fetchVideoUrl() { + this.fetchResource(); + this.url = `${this.nodeUrl}/arbitrary/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?async=true&apiKey=${this.myNode.apiKey}`; + } + + async fetchStatus() { + let isCalling = false; + let percentLoaded = 0; + let timer = 24; + const response = await axios.get( + `${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}` + ); + if (response && response.data && response.data.status === 'READY') { + this.status = response.data; + this.onLoad() + return; + } + const intervalId = setInterval(async () => { + if (isCalling) return; + isCalling = true; + + const data = await requestQueue.enqueue(() => { + return axios.get( + `${this.nodeUrl}/arbitrary/resource/status/${this.resource.service}/${this.resource.name}/${this.resource.identifier}?apiKey=${this.myNode.apiKey}` + ); + }); + const res = data.data; + + isCalling = false; + if (res.localChunkCount) { + if (res.percentLoaded) { + if ( + res.percentLoaded === percentLoaded && + res.percentLoaded !== 100 + ) { + timer = timer - 5; + } else { + timer = 24; + } + if (timer < 0) { + timer = 24; + isCalling = true; + this.status = { + ...res, + status: 'REFETCHING', + }; + + setTimeout(() => { + isCalling = false; + this.fetchResource(); + }, 25000); + return; + } + percentLoaded = res.percentLoaded; + } + + this.status = res; + if (this.status.status === 'DOWNLOADED') { + this.fetchResource(); + } + } + + // check if progress is 100% and clear interval if true + if (res.status === 'READY') { + this.onLoad() + clearInterval(intervalId); + this.status = res; + this.isReady = true; + } + + if(res.status === 'MISSING_DATA'){ + this.status = res + this.missingData = true + clearInterval(intervalId) + } + }, 5000); // 1 second interval + } + + async _fetchImage() { + try { + this.fetchVideoUrl({ + name: this.resource.name, + service: this.resource.service, + identifier: this.resource.identifier, + }); + this.fetchStatus(); + } catch (error) { /* empty */ } + } + + firstUpdated() { + this.observer.observe(this); + } + + showContextMenu(e) { + e.preventDefault(); + e.stopPropagation(); + + const contextMenu = this.shadowRoot.getElementById('contextMenu'); + const containerRect = e.currentTarget.getBoundingClientRect(); + + // Adjusting the positions + const adjustedX = e.clientX - containerRect.left; + const adjustedY = e.clientY - containerRect.top; + + contextMenu.style.top = `${adjustedY}px`; + contextMenu.style.left = `${adjustedX}px`; + + contextMenu.open = true; + } + + render() { + return html` +
    + ${this.status.status !== 'READY' + ? html` +
    +
    +

    + ${`${Math.round( + this.status.percentLoaded || 0 + ).toFixed(0)}% `}${translate( + 'chatpage.cchange94' + )} +

    +
    + ` + : ''} + ${this.status.status === 'READY' + ? html` +
    { + this.openDialogImage = true; + }}> + +
    + ` + : ''} +
    + + { + this.openDialogImage = false; + }}> +
    +
    + ${this.openDialogImage ? html` + + ` : ''} + +
    + { + this.openDialogImage = false; + }} + > + ${translate('general.close')} + +
    + `; + } +} + +customElements.define('reusable-image', ResuableImage); diff --git a/plugins/plugins/core/components/TipUser.js b/plugins/plugins/core/components/TipUser.js index 1a6bf2b4..51434378 100644 --- a/plugins/plugins/core/components/TipUser.js +++ b/plugins/plugins/core/components/TipUser.js @@ -24,7 +24,7 @@ export class TipUser extends LitElement { } constructor() { - super() + super() this.sendMoneyLoading = false this.btnDisable = false this.errorMessage = "" @@ -95,167 +95,140 @@ export class TipUser extends LitElement { } async fetchWalletDetails() { - await parentEpml.request('apiCall', { - url: `/addresses/balance/${this.myAddress.address}?apiKey=${this.getApiKey()}`, - }) - .then((res) => { - if (isNaN(Number(res))) { - let snack4string = get("chatpage.cchange48") - parentEpml.request('showSnackBar', `${snack4string}`) - } else { - this.walletBalance = Number(res).toFixed(8) - } - }) + } async sendQort() { - const amount = this.shadowRoot.getElementById("amountInput").value - let recipient = this.userName - this.sendMoneyLoading = true - this.btnDisable = true - - if (parseFloat(amount) + parseFloat(0.011) > parseFloat(this.walletBalance)) { - this.sendMoneyLoading = false - this.btnDisable = false - let snack1string = get("chatpage.cchange51") - parentEpml.request('showSnackBar', `${snack1string}`) - return false - } - - if (parseFloat(amount) <= 0) { - this.sendMoneyLoading = false - this.btnDisable = false - let snack2string = get("chatpage.cchange52") - parentEpml.request('showSnackBar', `${snack2string}`) - return false - } - - if (recipient.length === 0) { - this.sendMoneyLoading = false - this.btnDisable = false - let snack3string = get("chatpage.cchange53") - parentEpml.request('showSnackBar', `${snack3string}`) - return false - } - - const validateName = async (receiverName) => { - let myRes - let myNameRes = await parentEpml.request('apiCall', { - type: 'api', - url: `/names/${receiverName}`, - }) - - if (myNameRes.error === 401) { - myRes = false - } else { - myRes = myNameRes + const amount = this.shadowRoot.getElementById("amountInput").value; + const recipient = this.userName; + + this.sendMoneyLoading = true; + this.btnDisable = true; + + // Helper function to reset loading and button state + const resetState = () => { + this.sendMoneyLoading = false; + this.btnDisable = false; } - return myRes - } - - const validateAddress = async (receiverAddress) => { - let myAddress = await window.parent.validateAddress(receiverAddress) - return myAddress - } - - const validateReceiver = async (recipient) => { - let lastRef = await this.getLastRef() - let theFee = await this.getSendQortFee() - let isAddress - - try { - isAddress = await validateAddress(recipient) - } catch (err) { - isAddress = false + + if (parseFloat(amount) + parseFloat(0.011) > parseFloat(this.walletBalance)) { + resetState(); + const snack1string = get("chatpage.cchange51"); + parentEpml.request('showSnackBar', `${snack1string}`); + return false; } - - if (isAddress) { - let myTransaction = await makeTransactionRequest(recipient, lastRef, theFee) - getTxnRequestResponse(myTransaction) - } else { - let myNameRes = await validateName(recipient) - if (myNameRes !== false) { - let myNameAddress = myNameRes.owner - let myTransaction = await makeTransactionRequest(myNameAddress, lastRef, theFee) - getTxnRequestResponse(myTransaction) - } else { - console.error(this.renderReceiverText()) - this.errorMessage = this.renderReceiverText() - this.sendMoneyLoading = false - this.btnDisable = false - } + + if (parseFloat(amount) <= 0) { + resetState(); + const snack2string = get("chatpage.cchange52"); + parentEpml.request('showSnackBar', `${snack2string}`); + return false; } - } - - const getName = async (recipient)=> { - try { - const getNames = await parentEpml.request("apiCall", { - type: "api", - url: `/names/address/${recipient}`, + + if (recipient.length === 0) { + resetState(); + const snack3string = get("chatpage.cchange53"); + parentEpml.request('showSnackBar', `${snack3string}`); + return false; + } + + const validateName = async (receiverName) => { + const myNameRes = await parentEpml.request('apiCall', { + type: 'api', + url: `/names/${receiverName}` }); - - if (getNames?.length > 0 ) { - return getNames[0].name - } else { - return '' + return myNameRes.error === 401 ? false : myNameRes; + }; + + const validateAddress = async (receiverAddress) => { + return await window.parent.validateAddress(receiverAddress); + }; + + const getName = async (recipient) => { + try { + const getNames = await parentEpml.request("apiCall", { + type: "api", + url: `/names/address/${recipient}` + }); + return getNames?.length > 0 ? getNames[0].name : ''; + } catch (error) { + return ""; } - } catch (error) { - return "" - } - } - - const makeTransactionRequest = async (receiver, lastRef, theFee) => { - let myReceiver = receiver - let mylastRef = lastRef - let myFee = theFee - let dialogamount = get("transactions.amount") - let dialogAddress = get("login.address") - let dialogName = get("login.name") - let dialogto = get("transactions.to") - let recipientName = await getName(myReceiver) - let myTxnrequest = await parentEpml.request('transaction', { - type: 2, - nonce: this.myAddress.nonce, - params: { - recipient: myReceiver, - recipientName: recipientName, - amount: amount, - lastReference: mylastRef, - fee: myFee, - dialogamount: dialogamount, - dialogto: dialogto, - dialogAddress, - dialogName - }, - }) - return myTxnrequest - } - - const getTxnRequestResponse = (txnResponse) => { - if (txnResponse.success === false && txnResponse.message) { - this.errorMessage = txnResponse.message - this.sendMoneyLoading = false - this.btnDisable = false - throw new Error(txnResponse) - } else if (txnResponse.success === true && !txnResponse.data.error) { - this.shadowRoot.getElementById('amountInput').value = '' - this.errorMessage = '' - this.successMessage = this.renderSuccessText() - this.sendMoneyLoading = false - this.btnDisable = false - setTimeout(() => { - this.setOpenTipUser(false) - this.successMessage = "" - }, 3000) - } else { - this.errorMessage = txnResponse.data.message - this.sendMoneyLoading = false - this.btnDisable = false - throw new Error(txnResponse) - } - } - validateReceiver(recipient) + }; + + const makeTransactionRequest = async (receiver, lastRef) => { + const dialogAmount = get("transactions.amount"); + const dialogAddress = get("login.address"); + const dialogName = get("login.name"); + const dialogTo = get("transactions.to"); + const recipientName = await getName(receiver); + + return await parentEpml.request('transaction', { + type: 2, + nonce: this.myAddress.nonce, + params: { + recipient: receiver, + recipientName: recipientName, + amount: amount, + lastReference: lastRef, + fee: this.qortPaymentFee, + dialogAmount, + dialogTo, + dialogAddress, + dialogName + } + }); + }; + + const getTxnRequestResponse = (txnResponse) => { + if (txnResponse.success === false && txnResponse.message) { + this.errorMessage = txnResponse.message; + resetState(); + throw new Error(txnResponse); + } else if (txnResponse.success === true && !txnResponse.data.error) { + this.shadowRoot.getElementById('amountInput').value = ''; + this.errorMessage = ''; + this.successMessage = this.renderSuccessText(); + resetState(); + setTimeout(() => { + this.setOpenTipUser(false); + this.successMessage = ""; + }, 3000); + } else { + this.errorMessage = txnResponse.data.message; + resetState(); + throw new Error(txnResponse); + } + }; + + const validateReceiver = async (recipient) => { + let lastRef = await this.getLastRef(); + let isAddress; + + try { + isAddress = await validateAddress(recipient); + } catch (err) { + isAddress = false; + } + + if (isAddress) { + const myTransaction = await makeTransactionRequest(recipient, lastRef); + getTxnRequestResponse(myTransaction); + } else { + const myNameRes = await validateName(recipient); + if (myNameRes !== false) { + const myTransaction = await makeTransactionRequest(myNameRes.owner, lastRef); + getTxnRequestResponse(myTransaction); + } else { + this.errorMessage = this.renderReceiverText(); + resetState(); + } + } + }; + + await validateReceiver(recipient); } + render() { return html` @@ -270,8 +243,8 @@ export class TipUser extends LitElement { ${this.sendMoneyLoading ? html` - ` - : html` + ` + : html`
    `} - ${this.successMessage ? - html` + ${this.successMessage ? + html`

    ${this.successMessage}

    ` - : this.errorMessage ? - html` + : this.errorMessage ? + html`

    ${this.errorMessage}

    ` - : null} + : null}
    `; - } + } } customElements.define('tip-user', TipUser) diff --git a/plugins/plugins/core/components/webworkerDecodeMessages.js b/plugins/plugins/core/components/webworkerDecodeMessages.js new file mode 100644 index 00000000..c1fb5cb7 --- /dev/null +++ b/plugins/plugins/core/components/webworkerDecodeMessages.js @@ -0,0 +1,2871 @@ +import { Sha256 } from 'asmcrypto.js'; +const nacl = {} +//(function(nacl) { +'use strict'; + +// Ported in 2014 by Dmitry Chestnykh and Devi Mandiri. +// Public domain. +// +// Implementation derived from TweetNaCl version 20140427. +// See for details: http://tweetnacl.cr.yp.to/ + +var gf = function(init) { + var i, r = new Float64Array(16); + if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; + return r; +}; + +// Pluggable, initialized in high-level API below. +var randombytes = function(/* x, n */) { throw new Error('no PRNG'); }; + +var _0 = new Uint8Array(16); +var _9 = new Uint8Array(32); _9[0] = 9; + +var gf0 = gf(), + gf1 = gf([1]), + _121665 = gf([0xdb41, 1]), + D = gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]), + D2 = gf([0xf159, 0x26b2, 0x9b94, 0xebd6, 0xb156, 0x8283, 0x149a, 0x00e0, 0xd130, 0xeef3, 0x80f2, 0x198e, 0xfce7, 0x56df, 0xd9dc, 0x2406]), + X = gf([0xd51a, 0x8f25, 0x2d60, 0xc956, 0xa7b2, 0x9525, 0xc760, 0x692c, 0xdc5c, 0xfdd6, 0xe231, 0xc0a4, 0x53fe, 0xcd6e, 0x36d3, 0x2169]), + Y = gf([0x6658, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666, 0x6666]), + I = gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); + +function ts64(x, i, h, l) { + x[i] = (h >> 24) & 0xff; + x[i+1] = (h >> 16) & 0xff; + x[i+2] = (h >> 8) & 0xff; + x[i+3] = h & 0xff; + x[i+4] = (l >> 24) & 0xff; + x[i+5] = (l >> 16) & 0xff; + x[i+6] = (l >> 8) & 0xff; + x[i+7] = l & 0xff; +} + +function vn(x, xi, y, yi, n) { + var i,d = 0; + for (i = 0; i < n; i++) d |= x[xi+i]^y[yi+i]; + return (1 & ((d - 1) >>> 8)) - 1; +} + +function crypto_verify_16(x, xi, y, yi) { + return vn(x,xi,y,yi,16); +} + +function crypto_verify_32(x, xi, y, yi) { + return vn(x,xi,y,yi,32); +} + +function core_salsa20(o, p, k, c) { + var j0 = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24, + j1 = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24, + j2 = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24, + j3 = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24, + j4 = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24, + j5 = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24, + j6 = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24, + j7 = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24, + j8 = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24, + j9 = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24, + j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24, + j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24, + j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24, + j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24, + j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24, + j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24; + + var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7, + x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, + x15 = j15, u; + + for (var i = 0; i < 20; i += 2) { + u = x0 + x12 | 0; + x4 ^= u<<7 | u>>>(32-7); + u = x4 + x0 | 0; + x8 ^= u<<9 | u>>>(32-9); + u = x8 + x4 | 0; + x12 ^= u<<13 | u>>>(32-13); + u = x12 + x8 | 0; + x0 ^= u<<18 | u>>>(32-18); + + u = x5 + x1 | 0; + x9 ^= u<<7 | u>>>(32-7); + u = x9 + x5 | 0; + x13 ^= u<<9 | u>>>(32-9); + u = x13 + x9 | 0; + x1 ^= u<<13 | u>>>(32-13); + u = x1 + x13 | 0; + x5 ^= u<<18 | u>>>(32-18); + + u = x10 + x6 | 0; + x14 ^= u<<7 | u>>>(32-7); + u = x14 + x10 | 0; + x2 ^= u<<9 | u>>>(32-9); + u = x2 + x14 | 0; + x6 ^= u<<13 | u>>>(32-13); + u = x6 + x2 | 0; + x10 ^= u<<18 | u>>>(32-18); + + u = x15 + x11 | 0; + x3 ^= u<<7 | u>>>(32-7); + u = x3 + x15 | 0; + x7 ^= u<<9 | u>>>(32-9); + u = x7 + x3 | 0; + x11 ^= u<<13 | u>>>(32-13); + u = x11 + x7 | 0; + x15 ^= u<<18 | u>>>(32-18); + + u = x0 + x3 | 0; + x1 ^= u<<7 | u>>>(32-7); + u = x1 + x0 | 0; + x2 ^= u<<9 | u>>>(32-9); + u = x2 + x1 | 0; + x3 ^= u<<13 | u>>>(32-13); + u = x3 + x2 | 0; + x0 ^= u<<18 | u>>>(32-18); + + u = x5 + x4 | 0; + x6 ^= u<<7 | u>>>(32-7); + u = x6 + x5 | 0; + x7 ^= u<<9 | u>>>(32-9); + u = x7 + x6 | 0; + x4 ^= u<<13 | u>>>(32-13); + u = x4 + x7 | 0; + x5 ^= u<<18 | u>>>(32-18); + + u = x10 + x9 | 0; + x11 ^= u<<7 | u>>>(32-7); + u = x11 + x10 | 0; + x8 ^= u<<9 | u>>>(32-9); + u = x8 + x11 | 0; + x9 ^= u<<13 | u>>>(32-13); + u = x9 + x8 | 0; + x10 ^= u<<18 | u>>>(32-18); + + u = x15 + x14 | 0; + x12 ^= u<<7 | u>>>(32-7); + u = x12 + x15 | 0; + x13 ^= u<<9 | u>>>(32-9); + u = x13 + x12 | 0; + x14 ^= u<<13 | u>>>(32-13); + u = x14 + x13 | 0; + x15 ^= u<<18 | u>>>(32-18); + } + x0 = x0 + j0 | 0; + x1 = x1 + j1 | 0; + x2 = x2 + j2 | 0; + x3 = x3 + j3 | 0; + x4 = x4 + j4 | 0; + x5 = x5 + j5 | 0; + x6 = x6 + j6 | 0; + x7 = x7 + j7 | 0; + x8 = x8 + j8 | 0; + x9 = x9 + j9 | 0; + x10 = x10 + j10 | 0; + x11 = x11 + j11 | 0; + x12 = x12 + j12 | 0; + x13 = x13 + j13 | 0; + x14 = x14 + j14 | 0; + x15 = x15 + j15 | 0; + + o[ 0] = x0 >>> 0 & 0xff; + o[ 1] = x0 >>> 8 & 0xff; + o[ 2] = x0 >>> 16 & 0xff; + o[ 3] = x0 >>> 24 & 0xff; + + o[ 4] = x1 >>> 0 & 0xff; + o[ 5] = x1 >>> 8 & 0xff; + o[ 6] = x1 >>> 16 & 0xff; + o[ 7] = x1 >>> 24 & 0xff; + + o[ 8] = x2 >>> 0 & 0xff; + o[ 9] = x2 >>> 8 & 0xff; + o[10] = x2 >>> 16 & 0xff; + o[11] = x2 >>> 24 & 0xff; + + o[12] = x3 >>> 0 & 0xff; + o[13] = x3 >>> 8 & 0xff; + o[14] = x3 >>> 16 & 0xff; + o[15] = x3 >>> 24 & 0xff; + + o[16] = x4 >>> 0 & 0xff; + o[17] = x4 >>> 8 & 0xff; + o[18] = x4 >>> 16 & 0xff; + o[19] = x4 >>> 24 & 0xff; + + o[20] = x5 >>> 0 & 0xff; + o[21] = x5 >>> 8 & 0xff; + o[22] = x5 >>> 16 & 0xff; + o[23] = x5 >>> 24 & 0xff; + + o[24] = x6 >>> 0 & 0xff; + o[25] = x6 >>> 8 & 0xff; + o[26] = x6 >>> 16 & 0xff; + o[27] = x6 >>> 24 & 0xff; + + o[28] = x7 >>> 0 & 0xff; + o[29] = x7 >>> 8 & 0xff; + o[30] = x7 >>> 16 & 0xff; + o[31] = x7 >>> 24 & 0xff; + + o[32] = x8 >>> 0 & 0xff; + o[33] = x8 >>> 8 & 0xff; + o[34] = x8 >>> 16 & 0xff; + o[35] = x8 >>> 24 & 0xff; + + o[36] = x9 >>> 0 & 0xff; + o[37] = x9 >>> 8 & 0xff; + o[38] = x9 >>> 16 & 0xff; + o[39] = x9 >>> 24 & 0xff; + + o[40] = x10 >>> 0 & 0xff; + o[41] = x10 >>> 8 & 0xff; + o[42] = x10 >>> 16 & 0xff; + o[43] = x10 >>> 24 & 0xff; + + o[44] = x11 >>> 0 & 0xff; + o[45] = x11 >>> 8 & 0xff; + o[46] = x11 >>> 16 & 0xff; + o[47] = x11 >>> 24 & 0xff; + + o[48] = x12 >>> 0 & 0xff; + o[49] = x12 >>> 8 & 0xff; + o[50] = x12 >>> 16 & 0xff; + o[51] = x12 >>> 24 & 0xff; + + o[52] = x13 >>> 0 & 0xff; + o[53] = x13 >>> 8 & 0xff; + o[54] = x13 >>> 16 & 0xff; + o[55] = x13 >>> 24 & 0xff; + + o[56] = x14 >>> 0 & 0xff; + o[57] = x14 >>> 8 & 0xff; + o[58] = x14 >>> 16 & 0xff; + o[59] = x14 >>> 24 & 0xff; + + o[60] = x15 >>> 0 & 0xff; + o[61] = x15 >>> 8 & 0xff; + o[62] = x15 >>> 16 & 0xff; + o[63] = x15 >>> 24 & 0xff; +} + +function core_hsalsa20(o,p,k,c) { + var j0 = c[ 0] & 0xff | (c[ 1] & 0xff)<<8 | (c[ 2] & 0xff)<<16 | (c[ 3] & 0xff)<<24, + j1 = k[ 0] & 0xff | (k[ 1] & 0xff)<<8 | (k[ 2] & 0xff)<<16 | (k[ 3] & 0xff)<<24, + j2 = k[ 4] & 0xff | (k[ 5] & 0xff)<<8 | (k[ 6] & 0xff)<<16 | (k[ 7] & 0xff)<<24, + j3 = k[ 8] & 0xff | (k[ 9] & 0xff)<<8 | (k[10] & 0xff)<<16 | (k[11] & 0xff)<<24, + j4 = k[12] & 0xff | (k[13] & 0xff)<<8 | (k[14] & 0xff)<<16 | (k[15] & 0xff)<<24, + j5 = c[ 4] & 0xff | (c[ 5] & 0xff)<<8 | (c[ 6] & 0xff)<<16 | (c[ 7] & 0xff)<<24, + j6 = p[ 0] & 0xff | (p[ 1] & 0xff)<<8 | (p[ 2] & 0xff)<<16 | (p[ 3] & 0xff)<<24, + j7 = p[ 4] & 0xff | (p[ 5] & 0xff)<<8 | (p[ 6] & 0xff)<<16 | (p[ 7] & 0xff)<<24, + j8 = p[ 8] & 0xff | (p[ 9] & 0xff)<<8 | (p[10] & 0xff)<<16 | (p[11] & 0xff)<<24, + j9 = p[12] & 0xff | (p[13] & 0xff)<<8 | (p[14] & 0xff)<<16 | (p[15] & 0xff)<<24, + j10 = c[ 8] & 0xff | (c[ 9] & 0xff)<<8 | (c[10] & 0xff)<<16 | (c[11] & 0xff)<<24, + j11 = k[16] & 0xff | (k[17] & 0xff)<<8 | (k[18] & 0xff)<<16 | (k[19] & 0xff)<<24, + j12 = k[20] & 0xff | (k[21] & 0xff)<<8 | (k[22] & 0xff)<<16 | (k[23] & 0xff)<<24, + j13 = k[24] & 0xff | (k[25] & 0xff)<<8 | (k[26] & 0xff)<<16 | (k[27] & 0xff)<<24, + j14 = k[28] & 0xff | (k[29] & 0xff)<<8 | (k[30] & 0xff)<<16 | (k[31] & 0xff)<<24, + j15 = c[12] & 0xff | (c[13] & 0xff)<<8 | (c[14] & 0xff)<<16 | (c[15] & 0xff)<<24; + + var x0 = j0, x1 = j1, x2 = j2, x3 = j3, x4 = j4, x5 = j5, x6 = j6, x7 = j7, + x8 = j8, x9 = j9, x10 = j10, x11 = j11, x12 = j12, x13 = j13, x14 = j14, + x15 = j15, u; + + for (var i = 0; i < 20; i += 2) { + u = x0 + x12 | 0; + x4 ^= u<<7 | u>>>(32-7); + u = x4 + x0 | 0; + x8 ^= u<<9 | u>>>(32-9); + u = x8 + x4 | 0; + x12 ^= u<<13 | u>>>(32-13); + u = x12 + x8 | 0; + x0 ^= u<<18 | u>>>(32-18); + + u = x5 + x1 | 0; + x9 ^= u<<7 | u>>>(32-7); + u = x9 + x5 | 0; + x13 ^= u<<9 | u>>>(32-9); + u = x13 + x9 | 0; + x1 ^= u<<13 | u>>>(32-13); + u = x1 + x13 | 0; + x5 ^= u<<18 | u>>>(32-18); + + u = x10 + x6 | 0; + x14 ^= u<<7 | u>>>(32-7); + u = x14 + x10 | 0; + x2 ^= u<<9 | u>>>(32-9); + u = x2 + x14 | 0; + x6 ^= u<<13 | u>>>(32-13); + u = x6 + x2 | 0; + x10 ^= u<<18 | u>>>(32-18); + + u = x15 + x11 | 0; + x3 ^= u<<7 | u>>>(32-7); + u = x3 + x15 | 0; + x7 ^= u<<9 | u>>>(32-9); + u = x7 + x3 | 0; + x11 ^= u<<13 | u>>>(32-13); + u = x11 + x7 | 0; + x15 ^= u<<18 | u>>>(32-18); + + u = x0 + x3 | 0; + x1 ^= u<<7 | u>>>(32-7); + u = x1 + x0 | 0; + x2 ^= u<<9 | u>>>(32-9); + u = x2 + x1 | 0; + x3 ^= u<<13 | u>>>(32-13); + u = x3 + x2 | 0; + x0 ^= u<<18 | u>>>(32-18); + + u = x5 + x4 | 0; + x6 ^= u<<7 | u>>>(32-7); + u = x6 + x5 | 0; + x7 ^= u<<9 | u>>>(32-9); + u = x7 + x6 | 0; + x4 ^= u<<13 | u>>>(32-13); + u = x4 + x7 | 0; + x5 ^= u<<18 | u>>>(32-18); + + u = x10 + x9 | 0; + x11 ^= u<<7 | u>>>(32-7); + u = x11 + x10 | 0; + x8 ^= u<<9 | u>>>(32-9); + u = x8 + x11 | 0; + x9 ^= u<<13 | u>>>(32-13); + u = x9 + x8 | 0; + x10 ^= u<<18 | u>>>(32-18); + + u = x15 + x14 | 0; + x12 ^= u<<7 | u>>>(32-7); + u = x12 + x15 | 0; + x13 ^= u<<9 | u>>>(32-9); + u = x13 + x12 | 0; + x14 ^= u<<13 | u>>>(32-13); + u = x14 + x13 | 0; + x15 ^= u<<18 | u>>>(32-18); + } + + o[ 0] = x0 >>> 0 & 0xff; + o[ 1] = x0 >>> 8 & 0xff; + o[ 2] = x0 >>> 16 & 0xff; + o[ 3] = x0 >>> 24 & 0xff; + + o[ 4] = x5 >>> 0 & 0xff; + o[ 5] = x5 >>> 8 & 0xff; + o[ 6] = x5 >>> 16 & 0xff; + o[ 7] = x5 >>> 24 & 0xff; + + o[ 8] = x10 >>> 0 & 0xff; + o[ 9] = x10 >>> 8 & 0xff; + o[10] = x10 >>> 16 & 0xff; + o[11] = x10 >>> 24 & 0xff; + + o[12] = x15 >>> 0 & 0xff; + o[13] = x15 >>> 8 & 0xff; + o[14] = x15 >>> 16 & 0xff; + o[15] = x15 >>> 24 & 0xff; + + o[16] = x6 >>> 0 & 0xff; + o[17] = x6 >>> 8 & 0xff; + o[18] = x6 >>> 16 & 0xff; + o[19] = x6 >>> 24 & 0xff; + + o[20] = x7 >>> 0 & 0xff; + o[21] = x7 >>> 8 & 0xff; + o[22] = x7 >>> 16 & 0xff; + o[23] = x7 >>> 24 & 0xff; + + o[24] = x8 >>> 0 & 0xff; + o[25] = x8 >>> 8 & 0xff; + o[26] = x8 >>> 16 & 0xff; + o[27] = x8 >>> 24 & 0xff; + + o[28] = x9 >>> 0 & 0xff; + o[29] = x9 >>> 8 & 0xff; + o[30] = x9 >>> 16 & 0xff; + o[31] = x9 >>> 24 & 0xff; +} + +function crypto_core_salsa20(out,inp,k,c) { + core_salsa20(out,inp,k,c); +} + +function crypto_core_hsalsa20(out,inp,k,c) { + core_hsalsa20(out,inp,k,c); +} + +var sigma = new Uint8Array([101, 120, 112, 97, 110, 100, 32, 51, 50, 45, 98, 121, 116, 101, 32, 107]); + // "expand 32-byte k" + +function crypto_stream_salsa20_xor(c,cpos,m,mpos,b,n,k) { + var z = new Uint8Array(16), x = new Uint8Array(64); + var u, i; + for (i = 0; i < 16; i++) z[i] = 0; + for (i = 0; i < 8; i++) z[i] = n[i]; + while (b >= 64) { + crypto_core_salsa20(x,z,k,sigma); + for (i = 0; i < 64; i++) c[cpos+i] = m[mpos+i] ^ x[i]; + u = 1; + for (i = 8; i < 16; i++) { + u = u + (z[i] & 0xff) | 0; + z[i] = u & 0xff; + u >>>= 8; + } + b -= 64; + cpos += 64; + mpos += 64; + } + if (b > 0) { + crypto_core_salsa20(x,z,k,sigma); + for (i = 0; i < b; i++) c[cpos+i] = m[mpos+i] ^ x[i]; + } + return 0; +} + +function crypto_stream_salsa20(c,cpos,b,n,k) { + var z = new Uint8Array(16), x = new Uint8Array(64); + var u, i; + for (i = 0; i < 16; i++) z[i] = 0; + for (i = 0; i < 8; i++) z[i] = n[i]; + while (b >= 64) { + crypto_core_salsa20(x,z,k,sigma); + for (i = 0; i < 64; i++) c[cpos+i] = x[i]; + u = 1; + for (i = 8; i < 16; i++) { + u = u + (z[i] & 0xff) | 0; + z[i] = u & 0xff; + u >>>= 8; + } + b -= 64; + cpos += 64; + } + if (b > 0) { + crypto_core_salsa20(x,z,k,sigma); + for (i = 0; i < b; i++) c[cpos+i] = x[i]; + } + return 0; +} + +function crypto_stream(c,cpos,d,n,k) { + var s = new Uint8Array(32); + crypto_core_hsalsa20(s,n,k,sigma); + var sn = new Uint8Array(8); + for (var i = 0; i < 8; i++) sn[i] = n[i+16]; + return crypto_stream_salsa20(c,cpos,d,sn,s); +} + +function crypto_stream_xor(c,cpos,m,mpos,d,n,k) { + var s = new Uint8Array(32); + crypto_core_hsalsa20(s,n,k,sigma); + var sn = new Uint8Array(8); + for (var i = 0; i < 8; i++) sn[i] = n[i+16]; + return crypto_stream_salsa20_xor(c,cpos,m,mpos,d,sn,s); +} + +/* +* Port of Andrew Moon's Poly1305-donna-16. Public domain. +* https://github.com/floodyberry/poly1305-donna +*/ + +var poly1305 = function(key) { + this.buffer = new Uint8Array(16); + this.r = new Uint16Array(10); + this.h = new Uint16Array(10); + this.pad = new Uint16Array(8); + this.leftover = 0; + this.fin = 0; + + var t0, t1, t2, t3, t4, t5, t6, t7; + + t0 = key[ 0] & 0xff | (key[ 1] & 0xff) << 8; this.r[0] = ( t0 ) & 0x1fff; + t1 = key[ 2] & 0xff | (key[ 3] & 0xff) << 8; this.r[1] = ((t0 >>> 13) | (t1 << 3)) & 0x1fff; + t2 = key[ 4] & 0xff | (key[ 5] & 0xff) << 8; this.r[2] = ((t1 >>> 10) | (t2 << 6)) & 0x1f03; + t3 = key[ 6] & 0xff | (key[ 7] & 0xff) << 8; this.r[3] = ((t2 >>> 7) | (t3 << 9)) & 0x1fff; + t4 = key[ 8] & 0xff | (key[ 9] & 0xff) << 8; this.r[4] = ((t3 >>> 4) | (t4 << 12)) & 0x00ff; + this.r[5] = ((t4 >>> 1)) & 0x1ffe; + t5 = key[10] & 0xff | (key[11] & 0xff) << 8; this.r[6] = ((t4 >>> 14) | (t5 << 2)) & 0x1fff; + t6 = key[12] & 0xff | (key[13] & 0xff) << 8; this.r[7] = ((t5 >>> 11) | (t6 << 5)) & 0x1f81; + t7 = key[14] & 0xff | (key[15] & 0xff) << 8; this.r[8] = ((t6 >>> 8) | (t7 << 8)) & 0x1fff; + this.r[9] = ((t7 >>> 5)) & 0x007f; + + this.pad[0] = key[16] & 0xff | (key[17] & 0xff) << 8; + this.pad[1] = key[18] & 0xff | (key[19] & 0xff) << 8; + this.pad[2] = key[20] & 0xff | (key[21] & 0xff) << 8; + this.pad[3] = key[22] & 0xff | (key[23] & 0xff) << 8; + this.pad[4] = key[24] & 0xff | (key[25] & 0xff) << 8; + this.pad[5] = key[26] & 0xff | (key[27] & 0xff) << 8; + this.pad[6] = key[28] & 0xff | (key[29] & 0xff) << 8; + this.pad[7] = key[30] & 0xff | (key[31] & 0xff) << 8; +}; + +poly1305.prototype.blocks = function(m, mpos, bytes) { + var hibit = this.fin ? 0 : (1 << 11); + var t0, t1, t2, t3, t4, t5, t6, t7, c; + var d0, d1, d2, d3, d4, d5, d6, d7, d8, d9; + + var h0 = this.h[0], + h1 = this.h[1], + h2 = this.h[2], + h3 = this.h[3], + h4 = this.h[4], + h5 = this.h[5], + h6 = this.h[6], + h7 = this.h[7], + h8 = this.h[8], + h9 = this.h[9]; + + var r0 = this.r[0], + r1 = this.r[1], + r2 = this.r[2], + r3 = this.r[3], + r4 = this.r[4], + r5 = this.r[5], + r6 = this.r[6], + r7 = this.r[7], + r8 = this.r[8], + r9 = this.r[9]; + + while (bytes >= 16) { + t0 = m[mpos+ 0] & 0xff | (m[mpos+ 1] & 0xff) << 8; h0 += ( t0 ) & 0x1fff; + t1 = m[mpos+ 2] & 0xff | (m[mpos+ 3] & 0xff) << 8; h1 += ((t0 >>> 13) | (t1 << 3)) & 0x1fff; + t2 = m[mpos+ 4] & 0xff | (m[mpos+ 5] & 0xff) << 8; h2 += ((t1 >>> 10) | (t2 << 6)) & 0x1fff; + t3 = m[mpos+ 6] & 0xff | (m[mpos+ 7] & 0xff) << 8; h3 += ((t2 >>> 7) | (t3 << 9)) & 0x1fff; + t4 = m[mpos+ 8] & 0xff | (m[mpos+ 9] & 0xff) << 8; h4 += ((t3 >>> 4) | (t4 << 12)) & 0x1fff; + h5 += ((t4 >>> 1)) & 0x1fff; + t5 = m[mpos+10] & 0xff | (m[mpos+11] & 0xff) << 8; h6 += ((t4 >>> 14) | (t5 << 2)) & 0x1fff; + t6 = m[mpos+12] & 0xff | (m[mpos+13] & 0xff) << 8; h7 += ((t5 >>> 11) | (t6 << 5)) & 0x1fff; + t7 = m[mpos+14] & 0xff | (m[mpos+15] & 0xff) << 8; h8 += ((t6 >>> 8) | (t7 << 8)) & 0x1fff; + h9 += ((t7 >>> 5)) | hibit; + + c = 0; + + d0 = c; + d0 += h0 * r0; + d0 += h1 * (5 * r9); + d0 += h2 * (5 * r8); + d0 += h3 * (5 * r7); + d0 += h4 * (5 * r6); + c = (d0 >>> 13); d0 &= 0x1fff; + d0 += h5 * (5 * r5); + d0 += h6 * (5 * r4); + d0 += h7 * (5 * r3); + d0 += h8 * (5 * r2); + d0 += h9 * (5 * r1); + c += (d0 >>> 13); d0 &= 0x1fff; + + d1 = c; + d1 += h0 * r1; + d1 += h1 * r0; + d1 += h2 * (5 * r9); + d1 += h3 * (5 * r8); + d1 += h4 * (5 * r7); + c = (d1 >>> 13); d1 &= 0x1fff; + d1 += h5 * (5 * r6); + d1 += h6 * (5 * r5); + d1 += h7 * (5 * r4); + d1 += h8 * (5 * r3); + d1 += h9 * (5 * r2); + c += (d1 >>> 13); d1 &= 0x1fff; + + d2 = c; + d2 += h0 * r2; + d2 += h1 * r1; + d2 += h2 * r0; + d2 += h3 * (5 * r9); + d2 += h4 * (5 * r8); + c = (d2 >>> 13); d2 &= 0x1fff; + d2 += h5 * (5 * r7); + d2 += h6 * (5 * r6); + d2 += h7 * (5 * r5); + d2 += h8 * (5 * r4); + d2 += h9 * (5 * r3); + c += (d2 >>> 13); d2 &= 0x1fff; + + d3 = c; + d3 += h0 * r3; + d3 += h1 * r2; + d3 += h2 * r1; + d3 += h3 * r0; + d3 += h4 * (5 * r9); + c = (d3 >>> 13); d3 &= 0x1fff; + d3 += h5 * (5 * r8); + d3 += h6 * (5 * r7); + d3 += h7 * (5 * r6); + d3 += h8 * (5 * r5); + d3 += h9 * (5 * r4); + c += (d3 >>> 13); d3 &= 0x1fff; + + d4 = c; + d4 += h0 * r4; + d4 += h1 * r3; + d4 += h2 * r2; + d4 += h3 * r1; + d4 += h4 * r0; + c = (d4 >>> 13); d4 &= 0x1fff; + d4 += h5 * (5 * r9); + d4 += h6 * (5 * r8); + d4 += h7 * (5 * r7); + d4 += h8 * (5 * r6); + d4 += h9 * (5 * r5); + c += (d4 >>> 13); d4 &= 0x1fff; + + d5 = c; + d5 += h0 * r5; + d5 += h1 * r4; + d5 += h2 * r3; + d5 += h3 * r2; + d5 += h4 * r1; + c = (d5 >>> 13); d5 &= 0x1fff; + d5 += h5 * r0; + d5 += h6 * (5 * r9); + d5 += h7 * (5 * r8); + d5 += h8 * (5 * r7); + d5 += h9 * (5 * r6); + c += (d5 >>> 13); d5 &= 0x1fff; + + d6 = c; + d6 += h0 * r6; + d6 += h1 * r5; + d6 += h2 * r4; + d6 += h3 * r3; + d6 += h4 * r2; + c = (d6 >>> 13); d6 &= 0x1fff; + d6 += h5 * r1; + d6 += h6 * r0; + d6 += h7 * (5 * r9); + d6 += h8 * (5 * r8); + d6 += h9 * (5 * r7); + c += (d6 >>> 13); d6 &= 0x1fff; + + d7 = c; + d7 += h0 * r7; + d7 += h1 * r6; + d7 += h2 * r5; + d7 += h3 * r4; + d7 += h4 * r3; + c = (d7 >>> 13); d7 &= 0x1fff; + d7 += h5 * r2; + d7 += h6 * r1; + d7 += h7 * r0; + d7 += h8 * (5 * r9); + d7 += h9 * (5 * r8); + c += (d7 >>> 13); d7 &= 0x1fff; + + d8 = c; + d8 += h0 * r8; + d8 += h1 * r7; + d8 += h2 * r6; + d8 += h3 * r5; + d8 += h4 * r4; + c = (d8 >>> 13); d8 &= 0x1fff; + d8 += h5 * r3; + d8 += h6 * r2; + d8 += h7 * r1; + d8 += h8 * r0; + d8 += h9 * (5 * r9); + c += (d8 >>> 13); d8 &= 0x1fff; + + d9 = c; + d9 += h0 * r9; + d9 += h1 * r8; + d9 += h2 * r7; + d9 += h3 * r6; + d9 += h4 * r5; + c = (d9 >>> 13); d9 &= 0x1fff; + d9 += h5 * r4; + d9 += h6 * r3; + d9 += h7 * r2; + d9 += h8 * r1; + d9 += h9 * r0; + c += (d9 >>> 13); d9 &= 0x1fff; + + c = (((c << 2) + c)) | 0; + c = (c + d0) | 0; + d0 = c & 0x1fff; + c = (c >>> 13); + d1 += c; + + h0 = d0; + h1 = d1; + h2 = d2; + h3 = d3; + h4 = d4; + h5 = d5; + h6 = d6; + h7 = d7; + h8 = d8; + h9 = d9; + + mpos += 16; + bytes -= 16; + } + this.h[0] = h0; + this.h[1] = h1; + this.h[2] = h2; + this.h[3] = h3; + this.h[4] = h4; + this.h[5] = h5; + this.h[6] = h6; + this.h[7] = h7; + this.h[8] = h8; + this.h[9] = h9; +}; + +poly1305.prototype.finish = function(mac, macpos) { + var g = new Uint16Array(10); + var c, mask, f, i; + + if (this.leftover) { + i = this.leftover; + this.buffer[i++] = 1; + for (; i < 16; i++) this.buffer[i] = 0; + this.fin = 1; + this.blocks(this.buffer, 0, 16); + } + + c = this.h[1] >>> 13; + this.h[1] &= 0x1fff; + for (i = 2; i < 10; i++) { + this.h[i] += c; + c = this.h[i] >>> 13; + this.h[i] &= 0x1fff; + } + this.h[0] += (c * 5); + c = this.h[0] >>> 13; + this.h[0] &= 0x1fff; + this.h[1] += c; + c = this.h[1] >>> 13; + this.h[1] &= 0x1fff; + this.h[2] += c; + + g[0] = this.h[0] + 5; + c = g[0] >>> 13; + g[0] &= 0x1fff; + for (i = 1; i < 10; i++) { + g[i] = this.h[i] + c; + c = g[i] >>> 13; + g[i] &= 0x1fff; + } + g[9] -= (1 << 13); + + mask = (g[9] >>> ((2 * 8) - 1)) - 1; + for (i = 0; i < 10; i++) g[i] &= mask; + mask = ~mask; + for (i = 0; i < 10; i++) this.h[i] = (this.h[i] & mask) | g[i]; + + this.h[0] = ((this.h[0] ) | (this.h[1] << 13) ) & 0xffff; + this.h[1] = ((this.h[1] >>> 3) | (this.h[2] << 10) ) & 0xffff; + this.h[2] = ((this.h[2] >>> 6) | (this.h[3] << 7) ) & 0xffff; + this.h[3] = ((this.h[3] >>> 9) | (this.h[4] << 4) ) & 0xffff; + this.h[4] = ((this.h[4] >>> 12) | (this.h[5] << 1) | (this.h[6] << 14)) & 0xffff; + this.h[5] = ((this.h[6] >>> 2) | (this.h[7] << 11) ) & 0xffff; + this.h[6] = ((this.h[7] >>> 5) | (this.h[8] << 8) ) & 0xffff; + this.h[7] = ((this.h[8] >>> 8) | (this.h[9] << 5) ) & 0xffff; + + f = this.h[0] + this.pad[0]; + this.h[0] = f & 0xffff; + for (i = 1; i < 8; i++) { + f = (((this.h[i] + this.pad[i]) | 0) + (f >>> 16)) | 0; + this.h[i] = f & 0xffff; + } + + mac[macpos+ 0] = (this.h[0] >>> 0) & 0xff; + mac[macpos+ 1] = (this.h[0] >>> 8) & 0xff; + mac[macpos+ 2] = (this.h[1] >>> 0) & 0xff; + mac[macpos+ 3] = (this.h[1] >>> 8) & 0xff; + mac[macpos+ 4] = (this.h[2] >>> 0) & 0xff; + mac[macpos+ 5] = (this.h[2] >>> 8) & 0xff; + mac[macpos+ 6] = (this.h[3] >>> 0) & 0xff; + mac[macpos+ 7] = (this.h[3] >>> 8) & 0xff; + mac[macpos+ 8] = (this.h[4] >>> 0) & 0xff; + mac[macpos+ 9] = (this.h[4] >>> 8) & 0xff; + mac[macpos+10] = (this.h[5] >>> 0) & 0xff; + mac[macpos+11] = (this.h[5] >>> 8) & 0xff; + mac[macpos+12] = (this.h[6] >>> 0) & 0xff; + mac[macpos+13] = (this.h[6] >>> 8) & 0xff; + mac[macpos+14] = (this.h[7] >>> 0) & 0xff; + mac[macpos+15] = (this.h[7] >>> 8) & 0xff; +}; + +poly1305.prototype.update = function(m, mpos, bytes) { + var i, want; + + if (this.leftover) { + want = (16 - this.leftover); + if (want > bytes) + want = bytes; + for (i = 0; i < want; i++) + this.buffer[this.leftover + i] = m[mpos+i]; + bytes -= want; + mpos += want; + this.leftover += want; + if (this.leftover < 16) + return; + this.blocks(this.buffer, 0, 16); + this.leftover = 0; + } + + if (bytes >= 16) { + want = bytes - (bytes % 16); + this.blocks(m, mpos, want); + mpos += want; + bytes -= want; + } + + if (bytes) { + for (i = 0; i < bytes; i++) + this.buffer[this.leftover + i] = m[mpos+i]; + this.leftover += bytes; + } +}; + +function crypto_onetimeauth(out, outpos, m, mpos, n, k) { + var s = new poly1305(k); + s.update(m, mpos, n); + s.finish(out, outpos); + return 0; +} + +function crypto_onetimeauth_verify(h, hpos, m, mpos, n, k) { + var x = new Uint8Array(16); + crypto_onetimeauth(x,0,m,mpos,n,k); + return crypto_verify_16(h,hpos,x,0); +} + +function crypto_secretbox(c,m,d,n,k) { + var i; + if (d < 32) return -1; + crypto_stream_xor(c,0,m,0,d,n,k); + crypto_onetimeauth(c, 16, c, 32, d - 32, c); + for (i = 0; i < 16; i++) c[i] = 0; + return 0; +} + +function crypto_secretbox_open(m,c,d,n,k) { + var i; + var x = new Uint8Array(32); + if (d < 32) return -1; + crypto_stream(x,0,32,n,k); + if (crypto_onetimeauth_verify(c, 16,c, 32,d - 32,x) !== 0) return -1; + crypto_stream_xor(m,0,c,0,d,n,k); + for (i = 0; i < 32; i++) m[i] = 0; + return 0; +} + +function set25519(r, a) { + var i; + for (i = 0; i < 16; i++) r[i] = a[i]|0; +} + +function car25519(o) { + var i, v, c = 1; + for (i = 0; i < 16; i++) { + v = o[i] + c + 65535; + c = Math.floor(v / 65536); + o[i] = v - c * 65536; + } + o[0] += c-1 + 37 * (c-1); +} + +function sel25519(p, q, b) { + var t, c = ~(b-1); + for (var i = 0; i < 16; i++) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } +} + +function pack25519(o, n) { + var i, j, b; + var m = gf(), t = gf(); + for (i = 0; i < 16; i++) t[i] = n[i]; + car25519(t); + car25519(t); + car25519(t); + for (j = 0; j < 2; j++) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; i++) { + m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1); + m[i-1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1); + b = (m[15]>>16) & 1; + m[14] &= 0xffff; + sel25519(t, m, 1-b); + } + for (i = 0; i < 16; i++) { + o[2*i] = t[i] & 0xff; + o[2*i+1] = t[i]>>8; + } +} + +function neq25519(a, b) { + var c = new Uint8Array(32), d = new Uint8Array(32); + pack25519(c, a); + pack25519(d, b); + return crypto_verify_32(c, 0, d, 0); +} + +function par25519(a) { + var d = new Uint8Array(32); + pack25519(d, a); + return d[0] & 1; +} + +function unpack25519(o, n) { + var i; + for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8); + o[15] &= 0x7fff; +} + +function A(o, a, b) { + for (var i = 0; i < 16; i++) o[i] = a[i] + b[i]; +} + +function Z(o, a, b) { + for (var i = 0; i < 16; i++) o[i] = a[i] - b[i]; +} + +function M(o, a, b) { + var v, c, + t0 = 0, t1 = 0, t2 = 0, t3 = 0, t4 = 0, t5 = 0, t6 = 0, t7 = 0, + t8 = 0, t9 = 0, t10 = 0, t11 = 0, t12 = 0, t13 = 0, t14 = 0, t15 = 0, + t16 = 0, t17 = 0, t18 = 0, t19 = 0, t20 = 0, t21 = 0, t22 = 0, t23 = 0, + t24 = 0, t25 = 0, t26 = 0, t27 = 0, t28 = 0, t29 = 0, t30 = 0, + b0 = b[0], + b1 = b[1], + b2 = b[2], + b3 = b[3], + b4 = b[4], + b5 = b[5], + b6 = b[6], + b7 = b[7], + b8 = b[8], + b9 = b[9], + b10 = b[10], + b11 = b[11], + b12 = b[12], + b13 = b[13], + b14 = b[14], + b15 = b[15]; + + v = a[0]; + t0 += v * b0; + t1 += v * b1; + t2 += v * b2; + t3 += v * b3; + t4 += v * b4; + t5 += v * b5; + t6 += v * b6; + t7 += v * b7; + t8 += v * b8; + t9 += v * b9; + t10 += v * b10; + t11 += v * b11; + t12 += v * b12; + t13 += v * b13; + t14 += v * b14; + t15 += v * b15; + v = a[1]; + t1 += v * b0; + t2 += v * b1; + t3 += v * b2; + t4 += v * b3; + t5 += v * b4; + t6 += v * b5; + t7 += v * b6; + t8 += v * b7; + t9 += v * b8; + t10 += v * b9; + t11 += v * b10; + t12 += v * b11; + t13 += v * b12; + t14 += v * b13; + t15 += v * b14; + t16 += v * b15; + v = a[2]; + t2 += v * b0; + t3 += v * b1; + t4 += v * b2; + t5 += v * b3; + t6 += v * b4; + t7 += v * b5; + t8 += v * b6; + t9 += v * b7; + t10 += v * b8; + t11 += v * b9; + t12 += v * b10; + t13 += v * b11; + t14 += v * b12; + t15 += v * b13; + t16 += v * b14; + t17 += v * b15; + v = a[3]; + t3 += v * b0; + t4 += v * b1; + t5 += v * b2; + t6 += v * b3; + t7 += v * b4; + t8 += v * b5; + t9 += v * b6; + t10 += v * b7; + t11 += v * b8; + t12 += v * b9; + t13 += v * b10; + t14 += v * b11; + t15 += v * b12; + t16 += v * b13; + t17 += v * b14; + t18 += v * b15; + v = a[4]; + t4 += v * b0; + t5 += v * b1; + t6 += v * b2; + t7 += v * b3; + t8 += v * b4; + t9 += v * b5; + t10 += v * b6; + t11 += v * b7; + t12 += v * b8; + t13 += v * b9; + t14 += v * b10; + t15 += v * b11; + t16 += v * b12; + t17 += v * b13; + t18 += v * b14; + t19 += v * b15; + v = a[5]; + t5 += v * b0; + t6 += v * b1; + t7 += v * b2; + t8 += v * b3; + t9 += v * b4; + t10 += v * b5; + t11 += v * b6; + t12 += v * b7; + t13 += v * b8; + t14 += v * b9; + t15 += v * b10; + t16 += v * b11; + t17 += v * b12; + t18 += v * b13; + t19 += v * b14; + t20 += v * b15; + v = a[6]; + t6 += v * b0; + t7 += v * b1; + t8 += v * b2; + t9 += v * b3; + t10 += v * b4; + t11 += v * b5; + t12 += v * b6; + t13 += v * b7; + t14 += v * b8; + t15 += v * b9; + t16 += v * b10; + t17 += v * b11; + t18 += v * b12; + t19 += v * b13; + t20 += v * b14; + t21 += v * b15; + v = a[7]; + t7 += v * b0; + t8 += v * b1; + t9 += v * b2; + t10 += v * b3; + t11 += v * b4; + t12 += v * b5; + t13 += v * b6; + t14 += v * b7; + t15 += v * b8; + t16 += v * b9; + t17 += v * b10; + t18 += v * b11; + t19 += v * b12; + t20 += v * b13; + t21 += v * b14; + t22 += v * b15; + v = a[8]; + t8 += v * b0; + t9 += v * b1; + t10 += v * b2; + t11 += v * b3; + t12 += v * b4; + t13 += v * b5; + t14 += v * b6; + t15 += v * b7; + t16 += v * b8; + t17 += v * b9; + t18 += v * b10; + t19 += v * b11; + t20 += v * b12; + t21 += v * b13; + t22 += v * b14; + t23 += v * b15; + v = a[9]; + t9 += v * b0; + t10 += v * b1; + t11 += v * b2; + t12 += v * b3; + t13 += v * b4; + t14 += v * b5; + t15 += v * b6; + t16 += v * b7; + t17 += v * b8; + t18 += v * b9; + t19 += v * b10; + t20 += v * b11; + t21 += v * b12; + t22 += v * b13; + t23 += v * b14; + t24 += v * b15; + v = a[10]; + t10 += v * b0; + t11 += v * b1; + t12 += v * b2; + t13 += v * b3; + t14 += v * b4; + t15 += v * b5; + t16 += v * b6; + t17 += v * b7; + t18 += v * b8; + t19 += v * b9; + t20 += v * b10; + t21 += v * b11; + t22 += v * b12; + t23 += v * b13; + t24 += v * b14; + t25 += v * b15; + v = a[11]; + t11 += v * b0; + t12 += v * b1; + t13 += v * b2; + t14 += v * b3; + t15 += v * b4; + t16 += v * b5; + t17 += v * b6; + t18 += v * b7; + t19 += v * b8; + t20 += v * b9; + t21 += v * b10; + t22 += v * b11; + t23 += v * b12; + t24 += v * b13; + t25 += v * b14; + t26 += v * b15; + v = a[12]; + t12 += v * b0; + t13 += v * b1; + t14 += v * b2; + t15 += v * b3; + t16 += v * b4; + t17 += v * b5; + t18 += v * b6; + t19 += v * b7; + t20 += v * b8; + t21 += v * b9; + t22 += v * b10; + t23 += v * b11; + t24 += v * b12; + t25 += v * b13; + t26 += v * b14; + t27 += v * b15; + v = a[13]; + t13 += v * b0; + t14 += v * b1; + t15 += v * b2; + t16 += v * b3; + t17 += v * b4; + t18 += v * b5; + t19 += v * b6; + t20 += v * b7; + t21 += v * b8; + t22 += v * b9; + t23 += v * b10; + t24 += v * b11; + t25 += v * b12; + t26 += v * b13; + t27 += v * b14; + t28 += v * b15; + v = a[14]; + t14 += v * b0; + t15 += v * b1; + t16 += v * b2; + t17 += v * b3; + t18 += v * b4; + t19 += v * b5; + t20 += v * b6; + t21 += v * b7; + t22 += v * b8; + t23 += v * b9; + t24 += v * b10; + t25 += v * b11; + t26 += v * b12; + t27 += v * b13; + t28 += v * b14; + t29 += v * b15; + v = a[15]; + t15 += v * b0; + t16 += v * b1; + t17 += v * b2; + t18 += v * b3; + t19 += v * b4; + t20 += v * b5; + t21 += v * b6; + t22 += v * b7; + t23 += v * b8; + t24 += v * b9; + t25 += v * b10; + t26 += v * b11; + t27 += v * b12; + t28 += v * b13; + t29 += v * b14; + t30 += v * b15; + + t0 += 38 * t16; + t1 += 38 * t17; + t2 += 38 * t18; + t3 += 38 * t19; + t4 += 38 * t20; + t5 += 38 * t21; + t6 += 38 * t22; + t7 += 38 * t23; + t8 += 38 * t24; + t9 += 38 * t25; + t10 += 38 * t26; + t11 += 38 * t27; + t12 += 38 * t28; + t13 += 38 * t29; + t14 += 38 * t30; + // t15 left as is + + // first car + c = 1; + v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; + v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; + v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; + v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; + v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; + v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; + v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; + v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; + v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; + v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; + v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; + v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; + v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; + v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; + v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; + v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; + t0 += c-1 + 37 * (c-1); + + // second car + c = 1; + v = t0 + c + 65535; c = Math.floor(v / 65536); t0 = v - c * 65536; + v = t1 + c + 65535; c = Math.floor(v / 65536); t1 = v - c * 65536; + v = t2 + c + 65535; c = Math.floor(v / 65536); t2 = v - c * 65536; + v = t3 + c + 65535; c = Math.floor(v / 65536); t3 = v - c * 65536; + v = t4 + c + 65535; c = Math.floor(v / 65536); t4 = v - c * 65536; + v = t5 + c + 65535; c = Math.floor(v / 65536); t5 = v - c * 65536; + v = t6 + c + 65535; c = Math.floor(v / 65536); t6 = v - c * 65536; + v = t7 + c + 65535; c = Math.floor(v / 65536); t7 = v - c * 65536; + v = t8 + c + 65535; c = Math.floor(v / 65536); t8 = v - c * 65536; + v = t9 + c + 65535; c = Math.floor(v / 65536); t9 = v - c * 65536; + v = t10 + c + 65535; c = Math.floor(v / 65536); t10 = v - c * 65536; + v = t11 + c + 65535; c = Math.floor(v / 65536); t11 = v - c * 65536; + v = t12 + c + 65535; c = Math.floor(v / 65536); t12 = v - c * 65536; + v = t13 + c + 65535; c = Math.floor(v / 65536); t13 = v - c * 65536; + v = t14 + c + 65535; c = Math.floor(v / 65536); t14 = v - c * 65536; + v = t15 + c + 65535; c = Math.floor(v / 65536); t15 = v - c * 65536; + t0 += c-1 + 37 * (c-1); + + o[ 0] = t0; + o[ 1] = t1; + o[ 2] = t2; + o[ 3] = t3; + o[ 4] = t4; + o[ 5] = t5; + o[ 6] = t6; + o[ 7] = t7; + o[ 8] = t8; + o[ 9] = t9; + o[10] = t10; + o[11] = t11; + o[12] = t12; + o[13] = t13; + o[14] = t14; + o[15] = t15; +} + +function S(o, a) { + M(o, a, a); +} + +function inv25519(o, i) { + var c = gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 253; a >= 0; a--) { + S(c, c); + if(a !== 2 && a !== 4) M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; +} + +function pow2523(o, i) { + var c = gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 250; a >= 0; a--) { + S(c, c); + if(a !== 1) M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; +} + +function crypto_scalarmult(q, n, p) { + var z = new Uint8Array(32); + var x = new Float64Array(80), r, i; + var a = gf(), b = gf(), c = gf(), + d = gf(), e = gf(), f = gf(); + for (i = 0; i < 31; i++) z[i] = n[i]; + z[31]=(n[31]&127)|64; + z[0]&=248; + unpack25519(x,p); + for (i = 0; i < 16; i++) { + b[i]=x[i]; + d[i]=a[i]=c[i]=0; + } + a[0]=d[0]=1; + for (i=254; i>=0; --i) { + r=(z[i>>>3]>>>(i&7))&1; + sel25519(a,b,r); + sel25519(c,d,r); + A(e,a,c); + Z(a,a,c); + A(c,b,d); + Z(b,b,d); + S(d,e); + S(f,a); + M(a,c,a); + M(c,b,e); + A(e,a,c); + Z(a,a,c); + S(b,a); + Z(c,d,f); + M(a,c,_121665); + A(a,a,d); + M(c,c,a); + M(a,d,f); + M(d,b,x); + S(b,e); + sel25519(a,b,r); + sel25519(c,d,r); + } + for (i = 0; i < 16; i++) { + x[i+16]=a[i]; + x[i+32]=c[i]; + x[i+48]=b[i]; + x[i+64]=d[i]; + } + var x32 = x.subarray(32); + var x16 = x.subarray(16); + inv25519(x32,x32); + M(x16,x16,x32); + pack25519(q,x16); + return 0; +} + +function crypto_scalarmult_base(q, n) { + return crypto_scalarmult(q, n, _9); +} + +function crypto_box_keypair(y, x) { + randombytes(x, 32); + return crypto_scalarmult_base(y, x); +} + +function crypto_box_beforenm(k, y, x) { + var s = new Uint8Array(32); + crypto_scalarmult(s, x, y); + return crypto_core_hsalsa20(k, _0, s, sigma); +} + +var crypto_box_afternm = crypto_secretbox; +var crypto_box_open_afternm = crypto_secretbox_open; + +function crypto_box(c, m, d, n, y, x) { + var k = new Uint8Array(32); + crypto_box_beforenm(k, y, x); + return crypto_box_afternm(c, m, d, n, k); +} + +function crypto_box_open(m, c, d, n, y, x) { + var k = new Uint8Array(32); + crypto_box_beforenm(k, y, x); + return crypto_box_open_afternm(m, c, d, n, k); +} + +var K = [ + 0x428a2f98, 0xd728ae22, 0x71374491, 0x23ef65cd, + 0xb5c0fbcf, 0xec4d3b2f, 0xe9b5dba5, 0x8189dbbc, + 0x3956c25b, 0xf348b538, 0x59f111f1, 0xb605d019, + 0x923f82a4, 0xaf194f9b, 0xab1c5ed5, 0xda6d8118, + 0xd807aa98, 0xa3030242, 0x12835b01, 0x45706fbe, + 0x243185be, 0x4ee4b28c, 0x550c7dc3, 0xd5ffb4e2, + 0x72be5d74, 0xf27b896f, 0x80deb1fe, 0x3b1696b1, + 0x9bdc06a7, 0x25c71235, 0xc19bf174, 0xcf692694, + 0xe49b69c1, 0x9ef14ad2, 0xefbe4786, 0x384f25e3, + 0x0fc19dc6, 0x8b8cd5b5, 0x240ca1cc, 0x77ac9c65, + 0x2de92c6f, 0x592b0275, 0x4a7484aa, 0x6ea6e483, + 0x5cb0a9dc, 0xbd41fbd4, 0x76f988da, 0x831153b5, + 0x983e5152, 0xee66dfab, 0xa831c66d, 0x2db43210, + 0xb00327c8, 0x98fb213f, 0xbf597fc7, 0xbeef0ee4, + 0xc6e00bf3, 0x3da88fc2, 0xd5a79147, 0x930aa725, + 0x06ca6351, 0xe003826f, 0x14292967, 0x0a0e6e70, + 0x27b70a85, 0x46d22ffc, 0x2e1b2138, 0x5c26c926, + 0x4d2c6dfc, 0x5ac42aed, 0x53380d13, 0x9d95b3df, + 0x650a7354, 0x8baf63de, 0x766a0abb, 0x3c77b2a8, + 0x81c2c92e, 0x47edaee6, 0x92722c85, 0x1482353b, + 0xa2bfe8a1, 0x4cf10364, 0xa81a664b, 0xbc423001, + 0xc24b8b70, 0xd0f89791, 0xc76c51a3, 0x0654be30, + 0xd192e819, 0xd6ef5218, 0xd6990624, 0x5565a910, + 0xf40e3585, 0x5771202a, 0x106aa070, 0x32bbd1b8, + 0x19a4c116, 0xb8d2d0c8, 0x1e376c08, 0x5141ab53, + 0x2748774c, 0xdf8eeb99, 0x34b0bcb5, 0xe19b48a8, + 0x391c0cb3, 0xc5c95a63, 0x4ed8aa4a, 0xe3418acb, + 0x5b9cca4f, 0x7763e373, 0x682e6ff3, 0xd6b2b8a3, + 0x748f82ee, 0x5defb2fc, 0x78a5636f, 0x43172f60, + 0x84c87814, 0xa1f0ab72, 0x8cc70208, 0x1a6439ec, + 0x90befffa, 0x23631e28, 0xa4506ceb, 0xde82bde9, + 0xbef9a3f7, 0xb2c67915, 0xc67178f2, 0xe372532b, + 0xca273ece, 0xea26619c, 0xd186b8c7, 0x21c0c207, + 0xeada7dd6, 0xcde0eb1e, 0xf57d4f7f, 0xee6ed178, + 0x06f067aa, 0x72176fba, 0x0a637dc5, 0xa2c898a6, + 0x113f9804, 0xbef90dae, 0x1b710b35, 0x131c471b, + 0x28db77f5, 0x23047d84, 0x32caab7b, 0x40c72493, + 0x3c9ebe0a, 0x15c9bebc, 0x431d67c4, 0x9c100d4c, + 0x4cc5d4be, 0xcb3e42b6, 0x597f299c, 0xfc657e2a, + 0x5fcb6fab, 0x3ad6faec, 0x6c44198c, 0x4a475817 +]; + +function crypto_hashblocks_hl(hh, hl, m, n) { + var wh = new Int32Array(16), wl = new Int32Array(16), + bh0, bh1, bh2, bh3, bh4, bh5, bh6, bh7, + bl0, bl1, bl2, bl3, bl4, bl5, bl6, bl7, + th, tl, i, j, h, l, a, b, c, d; + + var ah0 = hh[0], + ah1 = hh[1], + ah2 = hh[2], + ah3 = hh[3], + ah4 = hh[4], + ah5 = hh[5], + ah6 = hh[6], + ah7 = hh[7], + + al0 = hl[0], + al1 = hl[1], + al2 = hl[2], + al3 = hl[3], + al4 = hl[4], + al5 = hl[5], + al6 = hl[6], + al7 = hl[7]; + + var pos = 0; + while (n >= 128) { + for (i = 0; i < 16; i++) { + j = 8 * i + pos; + wh[i] = (m[j+0] << 24) | (m[j+1] << 16) | (m[j+2] << 8) | m[j+3]; + wl[i] = (m[j+4] << 24) | (m[j+5] << 16) | (m[j+6] << 8) | m[j+7]; + } + for (i = 0; i < 80; i++) { + bh0 = ah0; + bh1 = ah1; + bh2 = ah2; + bh3 = ah3; + bh4 = ah4; + bh5 = ah5; + bh6 = ah6; + bh7 = ah7; + + bl0 = al0; + bl1 = al1; + bl2 = al2; + bl3 = al3; + bl4 = al4; + bl5 = al5; + bl6 = al6; + bl7 = al7; + + // add + h = ah7; + l = al7; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + // Sigma1 + h = ((ah4 >>> 14) | (al4 << (32-14))) ^ ((ah4 >>> 18) | (al4 << (32-18))) ^ ((al4 >>> (41-32)) | (ah4 << (32-(41-32)))); + l = ((al4 >>> 14) | (ah4 << (32-14))) ^ ((al4 >>> 18) | (ah4 << (32-18))) ^ ((ah4 >>> (41-32)) | (al4 << (32-(41-32)))); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // Ch + h = (ah4 & ah5) ^ (~ah4 & ah6); + l = (al4 & al5) ^ (~al4 & al6); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // K + h = K[i*2]; + l = K[i*2+1]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // w + h = wh[i%16]; + l = wl[i%16]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + th = c & 0xffff | d << 16; + tl = a & 0xffff | b << 16; + + // add + h = th; + l = tl; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + // Sigma0 + h = ((ah0 >>> 28) | (al0 << (32-28))) ^ ((al0 >>> (34-32)) | (ah0 << (32-(34-32)))) ^ ((al0 >>> (39-32)) | (ah0 << (32-(39-32)))); + l = ((al0 >>> 28) | (ah0 << (32-28))) ^ ((ah0 >>> (34-32)) | (al0 << (32-(34-32)))) ^ ((ah0 >>> (39-32)) | (al0 << (32-(39-32)))); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // Maj + h = (ah0 & ah1) ^ (ah0 & ah2) ^ (ah1 & ah2); + l = (al0 & al1) ^ (al0 & al2) ^ (al1 & al2); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + bh7 = (c & 0xffff) | (d << 16); + bl7 = (a & 0xffff) | (b << 16); + + // add + h = bh3; + l = bl3; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = th; + l = tl; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + bh3 = (c & 0xffff) | (d << 16); + bl3 = (a & 0xffff) | (b << 16); + + ah1 = bh0; + ah2 = bh1; + ah3 = bh2; + ah4 = bh3; + ah5 = bh4; + ah6 = bh5; + ah7 = bh6; + ah0 = bh7; + + al1 = bl0; + al2 = bl1; + al3 = bl2; + al4 = bl3; + al5 = bl4; + al6 = bl5; + al7 = bl6; + al0 = bl7; + + if (i%16 === 15) { + for (j = 0; j < 16; j++) { + // add + h = wh[j]; + l = wl[j]; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = wh[(j+9)%16]; + l = wl[(j+9)%16]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // sigma0 + th = wh[(j+1)%16]; + tl = wl[(j+1)%16]; + h = ((th >>> 1) | (tl << (32-1))) ^ ((th >>> 8) | (tl << (32-8))) ^ (th >>> 7); + l = ((tl >>> 1) | (th << (32-1))) ^ ((tl >>> 8) | (th << (32-8))) ^ ((tl >>> 7) | (th << (32-7))); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + // sigma1 + th = wh[(j+14)%16]; + tl = wl[(j+14)%16]; + h = ((th >>> 19) | (tl << (32-19))) ^ ((tl >>> (61-32)) | (th << (32-(61-32)))) ^ (th >>> 6); + l = ((tl >>> 19) | (th << (32-19))) ^ ((th >>> (61-32)) | (tl << (32-(61-32)))) ^ ((tl >>> 6) | (th << (32-6))); + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + wh[j] = (c & 0xffff) | (d << 16); + wl[j] = (a & 0xffff) | (b << 16); + } + } + } + + // add + h = ah0; + l = al0; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[0]; + l = hl[0]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[0] = ah0 = (c & 0xffff) | (d << 16); + hl[0] = al0 = (a & 0xffff) | (b << 16); + + h = ah1; + l = al1; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[1]; + l = hl[1]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[1] = ah1 = (c & 0xffff) | (d << 16); + hl[1] = al1 = (a & 0xffff) | (b << 16); + + h = ah2; + l = al2; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[2]; + l = hl[2]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[2] = ah2 = (c & 0xffff) | (d << 16); + hl[2] = al2 = (a & 0xffff) | (b << 16); + + h = ah3; + l = al3; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[3]; + l = hl[3]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[3] = ah3 = (c & 0xffff) | (d << 16); + hl[3] = al3 = (a & 0xffff) | (b << 16); + + h = ah4; + l = al4; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[4]; + l = hl[4]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[4] = ah4 = (c & 0xffff) | (d << 16); + hl[4] = al4 = (a & 0xffff) | (b << 16); + + h = ah5; + l = al5; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[5]; + l = hl[5]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[5] = ah5 = (c & 0xffff) | (d << 16); + hl[5] = al5 = (a & 0xffff) | (b << 16); + + h = ah6; + l = al6; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[6]; + l = hl[6]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[6] = ah6 = (c & 0xffff) | (d << 16); + hl[6] = al6 = (a & 0xffff) | (b << 16); + + h = ah7; + l = al7; + + a = l & 0xffff; b = l >>> 16; + c = h & 0xffff; d = h >>> 16; + + h = hh[7]; + l = hl[7]; + + a += l & 0xffff; b += l >>> 16; + c += h & 0xffff; d += h >>> 16; + + b += a >>> 16; + c += b >>> 16; + d += c >>> 16; + + hh[7] = ah7 = (c & 0xffff) | (d << 16); + hl[7] = al7 = (a & 0xffff) | (b << 16); + + pos += 128; + n -= 128; + } + + return n; +} + +function crypto_hash(out, m, n) { + var hh = new Int32Array(8), + hl = new Int32Array(8), + x = new Uint8Array(256), + i, b = n; + + hh[0] = 0x6a09e667; + hh[1] = 0xbb67ae85; + hh[2] = 0x3c6ef372; + hh[3] = 0xa54ff53a; + hh[4] = 0x510e527f; + hh[5] = 0x9b05688c; + hh[6] = 0x1f83d9ab; + hh[7] = 0x5be0cd19; + + hl[0] = 0xf3bcc908; + hl[1] = 0x84caa73b; + hl[2] = 0xfe94f82b; + hl[3] = 0x5f1d36f1; + hl[4] = 0xade682d1; + hl[5] = 0x2b3e6c1f; + hl[6] = 0xfb41bd6b; + hl[7] = 0x137e2179; + + crypto_hashblocks_hl(hh, hl, m, n); + n %= 128; + + for (i = 0; i < n; i++) x[i] = m[b-n+i]; + x[n] = 128; + + n = 256-128*(n<112?1:0); + x[n-9] = 0; + ts64(x, n-8, (b / 0x20000000) | 0, b << 3); + crypto_hashblocks_hl(hh, hl, x, n); + + for (i = 0; i < 8; i++) ts64(out, 8*i, hh[i], hl[i]); + + return 0; +} + +function add(p, q) { + var a = gf(), b = gf(), c = gf(), + d = gf(), e = gf(), f = gf(), + g = gf(), h = gf(), t = gf(); + + Z(a, p[1], p[0]); + Z(t, q[1], q[0]); + M(a, a, t); + A(b, p[0], p[1]); + A(t, q[0], q[1]); + M(b, b, t); + M(c, p[3], q[3]); + M(c, c, D2); + M(d, p[2], q[2]); + A(d, d, d); + Z(e, b, a); + Z(f, d, c); + A(g, d, c); + A(h, b, a); + + M(p[0], e, f); + M(p[1], h, g); + M(p[2], g, f); + M(p[3], e, h); +} + +function cswap(p, q, b) { + var i; + for (i = 0; i < 4; i++) { + sel25519(p[i], q[i], b); + } +} + +function pack(r, p) { + var tx = gf(), ty = gf(), zi = gf(); + inv25519(zi, p[2]); + M(tx, p[0], zi); + M(ty, p[1], zi); + pack25519(r, ty); + r[31] ^= par25519(tx) << 7; +} + +function scalarmult(p, q, s) { + var b, i; + set25519(p[0], gf0); + set25519(p[1], gf1); + set25519(p[2], gf1); + set25519(p[3], gf0); + for (i = 255; i >= 0; --i) { + b = (s[(i/8)|0] >> (i&7)) & 1; + cswap(p, q, b); + add(q, p); + add(p, p); + cswap(p, q, b); + } +} + +function scalarbase(p, s) { + var q = [gf(), gf(), gf(), gf()]; + set25519(q[0], X); + set25519(q[1], Y); + set25519(q[2], gf1); + M(q[3], X, Y); + scalarmult(p, q, s); +} + +function crypto_sign_keypair(pk, sk, seeded) { + var d = new Uint8Array(64); + var p = [gf(), gf(), gf(), gf()]; + var i; + + if (!seeded) randombytes(sk, 32); + crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + + scalarbase(p, d); + pack(pk, p); + + for (i = 0; i < 32; i++) sk[i+32] = pk[i]; + return 0; +} + +var L = new Float64Array([0xed, 0xd3, 0xf5, 0x5c, 0x1a, 0x63, 0x12, 0x58, 0xd6, 0x9c, 0xf7, 0xa2, 0xde, 0xf9, 0xde, 0x14, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x10]); + +function modL(r, x) { + var carry, i, j, k; + for (i = 63; i >= 32; --i) { + carry = 0; + for (j = i - 32, k = i - 12; j < k; ++j) { + x[j] += carry - 16 * x[i] * L[j - (i - 32)]; + carry = (x[j] + 128) >> 8; + x[j] -= carry * 256; + } + x[j] += carry; + x[i] = 0; + } + carry = 0; + for (j = 0; j < 32; j++) { + x[j] += carry - (x[31] >> 4) * L[j]; + carry = x[j] >> 8; + x[j] &= 255; + } + for (j = 0; j < 32; j++) x[j] -= carry * L[j]; + for (i = 0; i < 32; i++) { + x[i+1] += x[i] >> 8; + r[i] = x[i] & 255; + } +} + +function reduce(r) { + var x = new Float64Array(64), i; + for (i = 0; i < 64; i++) x[i] = r[i]; + for (i = 0; i < 64; i++) r[i] = 0; + modL(r, x); +} + +// Note: difference from C - smlen returned, not passed as argument. +function crypto_sign(sm, m, n, sk) { + var d = new Uint8Array(64), h = new Uint8Array(64), r = new Uint8Array(64); + var i, j, x = new Float64Array(64); + var p = [gf(), gf(), gf(), gf()]; + + crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + + var smlen = n + 64; + for (i = 0; i < n; i++) sm[64 + i] = m[i]; + for (i = 0; i < 32; i++) sm[32 + i] = d[32 + i]; + + crypto_hash(r, sm.subarray(32), n+32); + reduce(r); + scalarbase(p, r); + pack(sm, p); + + for (i = 32; i < 64; i++) sm[i] = sk[i]; + crypto_hash(h, sm, n + 64); + reduce(h); + + for (i = 0; i < 64; i++) x[i] = 0; + for (i = 0; i < 32; i++) x[i] = r[i]; + for (i = 0; i < 32; i++) { + for (j = 0; j < 32; j++) { + x[i+j] += h[i] * d[j]; + } + } + + modL(sm.subarray(32), x); + return smlen; +} + +function unpackneg(r, p) { + var t = gf(), chk = gf(), num = gf(), + den = gf(), den2 = gf(), den4 = gf(), + den6 = gf(); + + set25519(r[2], gf1); + unpack25519(r[1], p); + S(num, r[1]); + M(den, num, D); + Z(num, num, r[2]); + A(den, r[2], den); + + S(den2, den); + S(den4, den2); + M(den6, den4, den2); + M(t, den6, num); + M(t, t, den); + + pow2523(t, t); + M(t, t, num); + M(t, t, den); + M(t, t, den); + M(r[0], t, den); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) M(r[0], r[0], I); + + S(chk, r[0]); + M(chk, chk, den); + if (neq25519(chk, num)) return -1; + + if (par25519(r[0]) === (p[31]>>7)) Z(r[0], gf0, r[0]); + + M(r[3], r[0], r[1]); + return 0; +} + +function crypto_sign_open(m, sm, n, pk) { + var i, mlen; + var t = new Uint8Array(32), h = new Uint8Array(64); + var p = [gf(), gf(), gf(), gf()], + q = [gf(), gf(), gf(), gf()]; + + mlen = -1; + if (n < 64) return -1; + + if (unpackneg(q, pk)) return -1; + + for (i = 0; i < n; i++) m[i] = sm[i]; + for (i = 0; i < 32; i++) m[i+32] = pk[i]; + crypto_hash(h, m, n); + reduce(h); + scalarmult(p, q, h); + + scalarbase(q, sm.subarray(32)); + add(p, q); + pack(t, p); + + n -= 64; + if (crypto_verify_32(sm, 0, t, 0)) { + for (i = 0; i < n; i++) m[i] = 0; + return -1; + } + + for (i = 0; i < n; i++) m[i] = sm[i + 64]; + mlen = n; + return mlen; +} + +var crypto_secretbox_KEYBYTES = 32, + crypto_secretbox_NONCEBYTES = 24, + crypto_secretbox_ZEROBYTES = 32, + crypto_secretbox_BOXZEROBYTES = 16, + crypto_scalarmult_BYTES = 32, + crypto_scalarmult_SCALARBYTES = 32, + crypto_box_PUBLICKEYBYTES = 32, + crypto_box_SECRETKEYBYTES = 32, + crypto_box_BEFORENMBYTES = 32, + crypto_box_NONCEBYTES = crypto_secretbox_NONCEBYTES, + crypto_box_ZEROBYTES = crypto_secretbox_ZEROBYTES, + crypto_box_BOXZEROBYTES = crypto_secretbox_BOXZEROBYTES, + crypto_sign_BYTES = 64, + crypto_sign_PUBLICKEYBYTES = 32, + crypto_sign_SECRETKEYBYTES = 64, + crypto_sign_SEEDBYTES = 32, + crypto_hash_BYTES = 64; + +nacl.lowlevel = { + crypto_core_hsalsa20: crypto_core_hsalsa20, + crypto_stream_xor: crypto_stream_xor, + crypto_stream: crypto_stream, + crypto_stream_salsa20_xor: crypto_stream_salsa20_xor, + crypto_stream_salsa20: crypto_stream_salsa20, + crypto_onetimeauth: crypto_onetimeauth, + crypto_onetimeauth_verify: crypto_onetimeauth_verify, + crypto_verify_16: crypto_verify_16, + crypto_verify_32: crypto_verify_32, + crypto_secretbox: crypto_secretbox, + crypto_secretbox_open: crypto_secretbox_open, + crypto_scalarmult: crypto_scalarmult, + crypto_scalarmult_base: crypto_scalarmult_base, + crypto_box_beforenm: crypto_box_beforenm, + crypto_box_afternm: crypto_box_afternm, + crypto_box: crypto_box, + crypto_box_open: crypto_box_open, + crypto_box_keypair: crypto_box_keypair, + crypto_hash: crypto_hash, + crypto_sign: crypto_sign, + crypto_sign_keypair: crypto_sign_keypair, + crypto_sign_open: crypto_sign_open, + + crypto_secretbox_KEYBYTES: crypto_secretbox_KEYBYTES, + crypto_secretbox_NONCEBYTES: crypto_secretbox_NONCEBYTES, + crypto_secretbox_ZEROBYTES: crypto_secretbox_ZEROBYTES, + crypto_secretbox_BOXZEROBYTES: crypto_secretbox_BOXZEROBYTES, + crypto_scalarmult_BYTES: crypto_scalarmult_BYTES, + crypto_scalarmult_SCALARBYTES: crypto_scalarmult_SCALARBYTES, + crypto_box_PUBLICKEYBYTES: crypto_box_PUBLICKEYBYTES, + crypto_box_SECRETKEYBYTES: crypto_box_SECRETKEYBYTES, + crypto_box_BEFORENMBYTES: crypto_box_BEFORENMBYTES, + crypto_box_NONCEBYTES: crypto_box_NONCEBYTES, + crypto_box_ZEROBYTES: crypto_box_ZEROBYTES, + crypto_box_BOXZEROBYTES: crypto_box_BOXZEROBYTES, + crypto_sign_BYTES: crypto_sign_BYTES, + crypto_sign_PUBLICKEYBYTES: crypto_sign_PUBLICKEYBYTES, + crypto_sign_SECRETKEYBYTES: crypto_sign_SECRETKEYBYTES, + crypto_sign_SEEDBYTES: crypto_sign_SEEDBYTES, + crypto_hash_BYTES: crypto_hash_BYTES +}; + +/* High-level API */ + +function checkLengths(k, n) { + if (k.length !== crypto_secretbox_KEYBYTES) throw new Error('bad key size'); + if (n.length !== crypto_secretbox_NONCEBYTES) throw new Error('bad nonce size'); +} + +function checkBoxLengths(pk, sk) { + if (pk.length !== crypto_box_PUBLICKEYBYTES) throw new Error('bad public key size'); + if (sk.length !== crypto_box_SECRETKEYBYTES) throw new Error('bad secret key size'); +} + +function checkArrayTypes() { + var t, i; + for (i = 0; i < arguments.length; i++) { + if ((t = Object.prototype.toString.call(arguments[i])) !== '[object Uint8Array]') + throw new TypeError('unexpected type ' + t + ', use Uint8Array'); + } +} + +function cleanup(arr) { + for (var i = 0; i < arr.length; i++) arr[i] = 0; +} + +nacl.util = {}; + +nacl.util.decodeUTF8 = function(s) { + var i, d = unescape(encodeURIComponent(s)), b = new Uint8Array(d.length); + for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i); + return b; +}; + +nacl.util.encodeUTF8 = function(arr) { + var i, s = []; + for (i = 0; i < arr.length; i++) s.push(String.fromCharCode(arr[i])); + return decodeURIComponent(escape(s.join(''))); +}; + +nacl.util.encodeBase64 = function(arr) { + if (typeof btoa === 'undefined') { + return (new Buffer(arr)).toString('base64'); + } else { + var i, s = [], len = arr.length; + for (i = 0; i < len; i++) s.push(String.fromCharCode(arr[i])); + return btoa(s.join('')); + } +}; + +nacl.util.decodeBase64 = function(s) { + if (typeof atob === 'undefined') { + return new Uint8Array(Array.prototype.slice.call(new Buffer(s, 'base64'), 0)); + } else { + var i, d = atob(s), b = new Uint8Array(d.length); + for (i = 0; i < d.length; i++) b[i] = d.charCodeAt(i); + return b; + } +}; + +nacl.randomBytes = function(n) { + var b = new Uint8Array(n); + randombytes(b, n); + return b; +}; + +nacl.secretbox = function(msg, nonce, key) { + checkArrayTypes(msg, nonce, key); + checkLengths(key, nonce); + var m = new Uint8Array(crypto_secretbox_ZEROBYTES + msg.length); + var c = new Uint8Array(m.length); + for (var i = 0; i < msg.length; i++) m[i+crypto_secretbox_ZEROBYTES] = msg[i]; + crypto_secretbox(c, m, m.length, nonce, key); + return c.subarray(crypto_secretbox_BOXZEROBYTES); +}; + +nacl.secretbox.open = function(box, nonce, key) { + checkArrayTypes(box, nonce, key); + checkLengths(key, nonce); + var c = new Uint8Array(crypto_secretbox_BOXZEROBYTES + box.length); + var m = new Uint8Array(c.length); + for (var i = 0; i < box.length; i++) c[i+crypto_secretbox_BOXZEROBYTES] = box[i]; + if (c.length < 32) return false; + if (crypto_secretbox_open(m, c, c.length, nonce, key) !== 0) return false; + return m.subarray(crypto_secretbox_ZEROBYTES); +}; + +nacl.secretbox.keyLength = crypto_secretbox_KEYBYTES; +nacl.secretbox.nonceLength = crypto_secretbox_NONCEBYTES; +nacl.secretbox.overheadLength = crypto_secretbox_BOXZEROBYTES; + +nacl.scalarMult = function(n, p) { + checkArrayTypes(n, p); + if (n.length !== crypto_scalarmult_SCALARBYTES) throw new Error('bad n size'); + if (p.length !== crypto_scalarmult_BYTES) throw new Error('bad p size'); + var q = new Uint8Array(crypto_scalarmult_BYTES); + crypto_scalarmult(q, n, p); + return q; +}; + +nacl.scalarMult.base = function(n) { + checkArrayTypes(n); + if (n.length !== crypto_scalarmult_SCALARBYTES) throw new Error('bad n size'); + var q = new Uint8Array(crypto_scalarmult_BYTES); + crypto_scalarmult_base(q, n); + return q; +}; + +nacl.scalarMult.scalarLength = crypto_scalarmult_SCALARBYTES; +nacl.scalarMult.groupElementLength = crypto_scalarmult_BYTES; + +nacl.box = function(msg, nonce, publicKey, secretKey) { + var k = nacl.box.before(publicKey, secretKey); + return nacl.secretbox(msg, nonce, k); +}; + +nacl.box.before = function(publicKey, secretKey) { + checkArrayTypes(publicKey, secretKey); + checkBoxLengths(publicKey, secretKey); + var k = new Uint8Array(crypto_box_BEFORENMBYTES); + crypto_box_beforenm(k, publicKey, secretKey); + return k; +}; + +nacl.box.after = nacl.secretbox; + +nacl.box.open = function(msg, nonce, publicKey, secretKey) { + var k = nacl.box.before(publicKey, secretKey); + return nacl.secretbox.open(msg, nonce, k); +}; + +nacl.box.open.after = nacl.secretbox.open; + +nacl.box.keyPair = function() { + var pk = new Uint8Array(crypto_box_PUBLICKEYBYTES); + var sk = new Uint8Array(crypto_box_SECRETKEYBYTES); + crypto_box_keypair(pk, sk); + return {publicKey: pk, secretKey: sk}; +}; + +nacl.box.keyPair.fromSecretKey = function(secretKey) { + checkArrayTypes(secretKey); + if (secretKey.length !== crypto_box_SECRETKEYBYTES) + throw new Error('bad secret key size'); + var pk = new Uint8Array(crypto_box_PUBLICKEYBYTES); + crypto_scalarmult_base(pk, secretKey); + return {publicKey: pk, secretKey: new Uint8Array(secretKey)}; +}; + +nacl.box.publicKeyLength = crypto_box_PUBLICKEYBYTES; +nacl.box.secretKeyLength = crypto_box_SECRETKEYBYTES; +nacl.box.sharedKeyLength = crypto_box_BEFORENMBYTES; +nacl.box.nonceLength = crypto_box_NONCEBYTES; +nacl.box.overheadLength = nacl.secretbox.overheadLength; + +nacl.sign = function(msg, secretKey) { + checkArrayTypes(msg, secretKey); + if (secretKey.length !== crypto_sign_SECRETKEYBYTES) + throw new Error('bad secret key size'); + var signedMsg = new Uint8Array(crypto_sign_BYTES+msg.length); + crypto_sign(signedMsg, msg, msg.length, secretKey); + return signedMsg; +}; + +nacl.sign.open = function(signedMsg, publicKey) { + if (arguments.length !== 2) + throw new Error('nacl.sign.open accepts 2 arguments; did you mean to use nacl.sign.detached.verify?'); + checkArrayTypes(signedMsg, publicKey); + if (publicKey.length !== crypto_sign_PUBLICKEYBYTES) + throw new Error('bad public key size'); + var tmp = new Uint8Array(signedMsg.length); + var mlen = crypto_sign_open(tmp, signedMsg, signedMsg.length, publicKey); + if (mlen < 0) return null; + var m = new Uint8Array(mlen); + for (var i = 0; i < m.length; i++) m[i] = tmp[i]; + return m; +}; + +nacl.sign.detached = function(msg, secretKey) { + var signedMsg = nacl.sign(msg, secretKey); + var sig = new Uint8Array(crypto_sign_BYTES); + for (var i = 0; i < sig.length; i++) sig[i] = signedMsg[i]; + return sig; +}; + +nacl.sign.detached.verify = function(msg, sig, publicKey) { + checkArrayTypes(msg, sig, publicKey); + if (sig.length !== crypto_sign_BYTES) + throw new Error('bad signature size'); + if (publicKey.length !== crypto_sign_PUBLICKEYBYTES) + throw new Error('bad public key size'); + var sm = new Uint8Array(crypto_sign_BYTES + msg.length); + var m = new Uint8Array(crypto_sign_BYTES + msg.length); + var i; + for (i = 0; i < crypto_sign_BYTES; i++) sm[i] = sig[i]; + for (i = 0; i < msg.length; i++) sm[i+crypto_sign_BYTES] = msg[i]; + return (crypto_sign_open(m, sm, sm.length, publicKey) >= 0); +}; + +nacl.sign.keyPair = function() { + var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); + var sk = new Uint8Array(crypto_sign_SECRETKEYBYTES); + crypto_sign_keypair(pk, sk); + return {publicKey: pk, secretKey: sk}; +}; + +nacl.sign.keyPair.fromSecretKey = function(secretKey) { + checkArrayTypes(secretKey); + if (secretKey.length !== crypto_sign_SECRETKEYBYTES) + throw new Error('bad secret key size'); + var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); + for (var i = 0; i < pk.length; i++) pk[i] = secretKey[32+i]; + return {publicKey: pk, secretKey: new Uint8Array(secretKey)}; +}; + +nacl.sign.keyPair.fromSeed = function(seed) { + checkArrayTypes(seed); + if (seed.length !== crypto_sign_SEEDBYTES) + throw new Error('bad seed size'); + var pk = new Uint8Array(crypto_sign_PUBLICKEYBYTES); + var sk = new Uint8Array(crypto_sign_SECRETKEYBYTES); + for (var i = 0; i < 32; i++) sk[i] = seed[i]; + crypto_sign_keypair(pk, sk, true); + return {publicKey: pk, secretKey: sk}; +}; + +nacl.sign.publicKeyLength = crypto_sign_PUBLICKEYBYTES; +nacl.sign.secretKeyLength = crypto_sign_SECRETKEYBYTES; +nacl.sign.seedLength = crypto_sign_SEEDBYTES; +nacl.sign.signatureLength = crypto_sign_BYTES; + +nacl.hash = function(msg) { + checkArrayTypes(msg); + var h = new Uint8Array(crypto_hash_BYTES); + crypto_hash(h, msg, msg.length); + return h; +}; + +nacl.hash.hashLength = crypto_hash_BYTES; + +nacl.verify = function(x, y) { + checkArrayTypes(x, y); + // Zero length arguments are considered not equal. + if (x.length === 0 || y.length === 0) return false; + if (x.length !== y.length) return false; + return (vn(x, 0, y, 0, x.length) === 0) ? true : false; +}; + +nacl.setPRNG = function(fn) { + randombytes = fn; +}; + +(function() { + // Initialize PRNG if environment provides CSPRNG. + // If not, methods calling randombytes will throw. + var crypto; + if (typeof window !== 'undefined') { + // Browser. + if (window.crypto && window.crypto.getRandomValues) { + crypto = window.crypto; // Standard + } else if (window.msCrypto && window.msCrypto.getRandomValues) { + crypto = window.msCrypto; // Internet Explorer 11+ + } + if (crypto) { + nacl.setPRNG(function(x, n) { + var i, v = new Uint8Array(n); + crypto.getRandomValues(v); + for (i = 0; i < n; i++) x[i] = v[i]; + cleanup(v); + }); + } + } else if (typeof require !== 'undefined') { + // Node.js. + crypto = require('crypto'); + if (crypto) { + nacl.setPRNG(function(x, n) { + var i, v = crypto.randomBytes(n); + for (i = 0; i < n; i++) x[i] = v[i]; + cleanup(v); + }); + } + } +})(); + +class Base58 { + constructor() { + this.ALPHABET = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz'; + this.ALPHABET_MAP = {}; + + for (let i = 0; i < this.ALPHABET.length; i++) { + this.ALPHABET_MAP[this.ALPHABET.charAt(i)] = i; + } + } + + encode(buffer) { + buffer = new Uint8Array(buffer); + let carry, digits, j; + if (buffer.length === 0) { + return ''; + } + let i = 0; + digits = [0]; + while (i < buffer.length) { + j = 0; + while (j < digits.length) { + digits[j] <<= 8; + j++; + } + digits[0] += buffer[i]; + carry = 0; + j = 0; + while (j < digits.length) { + digits[j] += carry; + carry = (digits[j] / 58) | 0; + digits[j] %= 58; + ++j; + } + while (carry) { + digits.push(carry % 58); + carry = (carry / 58) | 0; + } + i++; + } + i = 0; + while (buffer[i] === 0 && i < buffer.length - 1) { + digits.push(0); + i++; + } + return digits.reverse().map(digit => this.ALPHABET[digit]).join(''); + } + + decode(string) { + if (string.length === 0) { + return new Uint8Array(0); + } + let bytes = [0]; + let i = 0, j, c, carry; + while (i < string.length) { + c = string[i]; + if (!(c in this.ALPHABET_MAP)) { + throw new Error(`Base58.decode received unacceptable input. Character '${c}' is not in the Base58 alphabet.`); + } + j = 0; + while (j < bytes.length) { + bytes[j] *= 58; + j++; + } + bytes[0] += this.ALPHABET_MAP[c]; + carry = 0; + j = 0; + while (j < bytes.length) { + bytes[j] += carry; + carry = bytes[j] >> 8; + bytes[j] &= 0xff; + ++j; + } + while (carry) { + bytes.push(carry & 0xff); + carry >>= 8; + } + i++; + } + i = 0; + while (string[i] === '1' && i < string.length - 1) { + bytes.push(0); + i++; + } + return new Uint8Array(bytes.reverse()); + } +} + + +class Curve25519 { + constructor() { + this.gf0 = this.gf(); + this.gf1 = this.gf([1]); + this.D = this.gf([0x78a3, 0x1359, 0x4dca, 0x75eb, 0xd8ab, 0x4141, 0x0a4d, 0x0070, 0xe898, 0x7779, 0x4079, 0x8cc7, 0xfe73, 0x2b6f, 0x6cee, 0x5203]); + this.I = this.gf([0xa0b0, 0x4a0e, 0x1b27, 0xc4ee, 0xe478, 0xad2f, 0x1806, 0x2f43, 0xd7a7, 0x3dfb, 0x0099, 0x2b4d, 0xdf0b, 0x4fc1, 0x2480, 0x2b83]); + } + + gf(init) { + let i, r = new Float64Array(16); + if (init) for (i = 0; i < init.length; i++) r[i] = init[i]; + return r; + } + + car25519(o) { + var c, i; + for (i = 0; i < 16; i++) { + o[i] += 65536; + c = Math.floor(o[i] / 65536); + o[(i + 1) * (i < 15 ? 1 : 0)] += c - 1 + 37 * (c - 1) * (i === 15 ? 1 : 0); + o[i] -= (c * 65536); + } + } + + sel25519(p, q, b) { + let t, c = ~(b - 1); + for (let i = 0; i < 16; i++) { + t = c & (p[i] ^ q[i]); + p[i] ^= t; + q[i] ^= t; + } + } + + A(o, a, b) { + for (let i = 0; i < 16; i++) o[i] = a[i] + b[i]; + } + + Z(o, a, b) { + for (let i = 0; i < 16; i++) o[i] = a[i] - b[i]; + } + + M(o, a, b) { + var i, j, t = new Float64Array(31); + for (i = 0; i < 31; i++) t[i] = 0; + for (i = 0; i < 16; i++) { + for (j = 0; j < 16; j++) { + t[i+j] += a[i] * b[j]; + } + } + for (i = 0; i < 15; i++) { + t[i] += 38 * t[i+16]; + } + for (i = 0; i < 16; i++) o[i] = t[i]; + this.car25519(o); + this.car25519(o); + } + + inv25519(o, i) { + var c = this.gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 253; a >= 0; a--) { + this.S(c, c); + if(a !== 2 && a !== 4) this.M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; + } + + pack25519(o, n) { + var i, j, b; + var m = gf(), t = gf(); + for (i = 0; i < 16; i++) t[i] = n[i]; + this.car25519(t); + this.car25519(t); + this.car25519(t); + for (j = 0; j < 2; j++) { + m[0] = t[0] - 0xffed; + for (i = 1; i < 15; i++) { + m[i] = t[i] - 0xffff - ((m[i-1]>>16) & 1); + m[i-1] &= 0xffff; + } + m[15] = t[15] - 0x7fff - ((m[14]>>16) & 1); + b = (m[15]>>16) & 1; + m[14] &= 0xffff; + this.sel25519(t, m, 1-b); + } + for (i = 0; i < 16; i++) { + o[2*i] = t[i] & 0xff; + o[2*i+1] = t[i] >> 8; + } + } + S(o, a) { + this.M(o, a, a); + } + + unpackneg(r, p) { + var t = this.gf(), chk = this.gf(), num = this.gf(), + den = this.gf(), den2 = this.gf(), den4 = this.gf(), + den6 = this.gf(); + + this.set25519(r[2], gf1); + this.unpack25519(r[1], p); + this.S(num, r[1]); + this.M(den, num, D); + this.Z(num, num, r[2]); + this.A(den, r[2], den); + + this.S(den2, den); + this.S(den4, den2); + this.M(den6, den4, den2); + this.M(t, den6, num); + this.M(t, t, den); + + this.pow2523(t, t); + this.M(t, t, num); + this.M(t, t, den); + this.M(t, t, den); + this.M(r[0], t, den); + + this.S(chk, r[0]); + this.M(chk, chk, den); + if (this.neq25519(chk, num)) this.M(r[0], r[0], I); + + this.S(chk, r[0]); + this.M(chk, chk, den); + if (this.neq25519(chk, num)) return -1; + + if (this.par25519(r[0]) === (p[31] >> 7)) this.Z(r[0], gf0, r[0]); + + this.M(r[3], r[0], r[1]); + return 0; + } + + neq25519(a, b) { + var c = new Uint8Array(32), d = new Uint8Array(32); + this.pack25519(c, a); + this.pack25519(d, b); + return this.crypto_verify_32(c, 0, d, 0); + } + par25519(a) { + var d = new Uint8Array(32); + this.pack25519(d, a); + return d[0] & 1; + } + unpack25519(o, n) { + var i; + for (i = 0; i < 16; i++) o[i] = n[2*i] + (n[2*i+1] << 8); + o[15] &= 0x7fff; + } + + crypto_verify_32(x, xi, y, yi) { + return this.vn(x, xi, y, yi, 32); + } + + pow2523(o, i) { + var c = this.gf(); + var a; + for (a = 0; a < 16; a++) c[a] = i[a]; + for (a = 250; a >= 0; a--) { + this.S(c, c); + if (a !== 1) this.M(c, c, i); + } + for (a = 0; a < 16; a++) o[a] = c[a]; + } + + vn(x, xi, y, yi, n) { + var i, d = 0; + for (i = 0; i < n; i++) d |= x[xi + i] ^ y[yi + i]; + return (1 & ((d - 1) >>> 8)) - 1; + } + set25519(r, a) { + var i; + for (i = 0; i < 16; i++) r[i] = a[i] | 0; + } + + convertPublicKey(pk) { + var z = new Uint8Array(32), + q = [this.gf(), this.gf(), this.gf(), this.gf()], + a = this.gf(), b = this.gf(); + + if (this.unpackneg(q, pk)) return null; // reject invalid key + + var y = q[1]; + + this.A(a, gf1, y); + this.Z(b, gf1, y); + this.inv25519(b, b); + this.M(a, a, b); + + this.pack25519(z, a); + return z; + } + + convertSecretKey(sk) { + var d = new Uint8Array(64), o = new Uint8Array(32), i; + nacl.lowlevel.crypto_hash(d, sk, 32); + d[0] &= 248; + d[31] &= 127; + d[31] |= 64; + for (i = 0; i < 32; i++) o[i] = d[i]; + for (i = 0; i < 64; i++) d[i] = 0; + return o; + } + + convertKeyPair(edKeyPair) { + var publicKey = this.convertPublicKey(edKeyPair.publicKey); + if (!publicKey) return null; + return { + publicKey: publicKey, + secretKey: this.convertSecretKey(edKeyPair.secretKey) + }; + } +} + + + +const base58Instant = new Base58(); + +const curve25519Instance = new Curve25519(); + + +self.addEventListener('message', async (e) => { + + try { + const decodeMsgs = e.data.messages.map((eachMessage) => { + return decodeMessage( + eachMessage, + e.data.isReceipient, + e.data._publicKey, + e.data.privateKey + ); + }); + postMessage(decodeMsgs); + } catch (error) { + console.log('error', error); + postMessage({ + type: 'error', + message: error.message || 'An error occurred', + }); + } +}); + +const decode = (string) => { + const binaryString = atob(string); + const binaryLength = binaryString.length; + const bytes = new Uint8Array(binaryLength); + + for (let i = 0; i < binaryLength; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + + const decoder = new TextDecoder(); + const decodedString = decoder.decode(bytes); + return decodedString; +}; + +export const decryptChatMessageBase64 = ( + encryptedMessage, + privateKey, + recipientPublicKey, + lastReference +) => { + + let _encryptedMessage = atob(encryptedMessage); + const binaryLength = _encryptedMessage.length; + const bytes = new Uint8Array(binaryLength); + + for (let i = 0; i < binaryLength; i++) { + bytes[i] = _encryptedMessage.charCodeAt(i); + } + + let _base58RecipientPublic = recipientPublicKey + + try { + _base58RecipientPublic = recipientPublicKey.key + } catch (error) { + _base58RecipientPublic = recipientPublicKey + } + + const _base58RecipientPublicKey = + _base58RecipientPublic instanceof Uint8Array + ? base58Instant.encode(_base58RecipientPublic) + : _base58RecipientPublic; + const _recipientPublicKey = base58Instant.decode(_base58RecipientPublicKey); + const _lastReference = + lastReference instanceof Uint8Array + ? lastReference + : base58Instant.decode(lastReference); + + const convertedPrivateKey = curve25519Instance.convertSecretKey(privateKey); + const convertedPublicKey = curve25519Instance.convertPublicKey(_recipientPublicKey); + const sharedSecret = new Uint8Array(32); + nacl.lowlevel.crypto_scalarmult( + sharedSecret, + convertedPrivateKey, + convertedPublicKey + ); + + const _chatEncryptionSeed = new Sha256() + .process(sharedSecret) + .finish().result; + const _decryptedMessage = nacl.secretbox.open( + bytes, + _lastReference.slice(0, 24), + _chatEncryptionSeed + ); + + if (_decryptedMessage === false) { + return _decryptedMessage; + } + return new TextDecoder('utf-8').decode(_decryptedMessage); +}; + +const decodeMessage = ( + encodedMessageObj, + isReceipient, + _publicKey, + privateKey +) => { + let isReceipientVar; + let _publicKeyVar; + try { + isReceipientVar = isReceipient; + _publicKeyVar = _publicKey; + } catch (error) { + isReceipientVar = isReceipient; + _publicKeyVar = _publicKey; + } + + let decodedMessageObj = {}; + + if (isReceipientVar === true) { + // direct chat + if ( + encodedMessageObj.isEncrypted === true && + _publicKeyVar.hasPubKey === true && + encodedMessageObj.data + ) { + let decodedMessage = decryptChatMessageBase64( + encodedMessageObj.data, + privateKey, + _publicKeyVar, + encodedMessageObj.reference + ); + decodedMessageObj = { ...encodedMessageObj, decodedMessage }; + } else if ( + encodedMessageObj.isEncrypted === false && + encodedMessageObj.data + ) { + let decodedMessage = decode(encodedMessageObj.data); + decodedMessageObj = { ...encodedMessageObj, decodedMessage }; + } else { + decodedMessageObj = { + ...encodedMessageObj, + decodedMessage: 'Cannot Decrypt Message!', + }; + } + } else { + // group chat + let decodedMessage = decode(encodedMessageObj.data); + decodedMessageObj = { ...encodedMessageObj, decodedMessage }; + } + return decodedMessageObj; +}; diff --git a/plugins/plugins/core/components/webworkerSortMessages.js b/plugins/plugins/core/components/webworkerSortMessages.js new file mode 100644 index 00000000..2f290b40 --- /dev/null +++ b/plugins/plugins/core/components/webworkerSortMessages.js @@ -0,0 +1,15 @@ + + + +self.addEventListener('message', async e => { + const response = e.data.list.sort(function (a, b) { + return a.timestamp + - b.timestamp +}) +postMessage(response) +}) + + + + + diff --git a/plugins/plugins/core/group-management/group-management.src.js b/plugins/plugins/core/group-management/group-management.src.js index bd35cc58..3503aaa8 100644 --- a/plugins/plugins/core/group-management/group-management.src.js +++ b/plugins/plugins/core/group-management/group-management.src.js @@ -1743,6 +1743,12 @@ class GroupManagement extends LitElement { }) return joinedG } + const getGroupInfo = async (groupId) => { + let joinedG = await parentEpml.request('apiCall', { + url: `/groups/${groupId}` + }) + return joinedG + } const getGroupInvites = async () => { const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] @@ -1839,6 +1845,25 @@ class GroupManagement extends LitElement { if (!selectedAddress || Object.entries(selectedAddress).length === 0) return this.selectedAddress = selectedAddress }) + parentEpml.subscribe('side_effect_action', async sideEffectActionParam => { + const sideEffectAction = JSON.parse(sideEffectActionParam) + + if(sideEffectAction && sideEffectAction.type === 'openJoinGroupModal'){ + const res = await getGroupInfo(sideEffectAction.data) + if(res && res.groupId){ + if(res.isOpen){ + this.joinGroup(res) + + } else { + let snackbarstring = get("managegroup.mg45") + parentEpml.request('showSnackBar', `${snackbarstring}`) + } + } + window.parent.reduxStore.dispatch( + window.parent.reduxAction.setSideEffectAction(null) + ); + } + }) parentEpml.subscribe('config', c => { if (!configLoaded) { setTimeout(getOpen_JoinedGroups, 1) @@ -2849,6 +2874,17 @@ class GroupManagement extends LitElement { } } + setTxNotification(tx){ + window.parent.reduxStore.dispatch( + window.parent.reduxAction.setNewNotification({ + type: 'JOIN_GROUP', + status: 'confirming', + reference: tx, + timestamp: Date.now() + }) + ); + } + async _joinGroup(groupId, groupName) { this.resetDefaultSettings() const joinFeeInput = this.joinFee @@ -2885,7 +2921,8 @@ class GroupManagement extends LitElement { lastReference: lastRef, groupdialog1: groupdialog1, groupdialog2: groupdialog2 - } + }, + apiVersion: 2 }) return myTxnrequest } @@ -2897,6 +2934,12 @@ class GroupManagement extends LitElement { throw new Error(txnResponse) } else if (txnResponse.success === true && !txnResponse.data.error) { this.message = this.renderErr8Text() + this.setTxNotification({ + groupName, + groupId, + timestamp: Date.now(), + ...(txnResponse.data || {}) + }) this.error = false } else { this.error = true diff --git a/plugins/plugins/core/messaging/chain-messaging/chain-messaging.js b/plugins/plugins/core/messaging/chain-messaging/chain-messaging.js index ab09e3f6..69d972b6 100644 --- a/plugins/plugins/core/messaging/chain-messaging/chain-messaging.js +++ b/plugins/plugins/core/messaging/chain-messaging/chain-messaging.js @@ -1,4 +1,4 @@ -!function(t){"function"==typeof define&&define.amd?define(t):t()}((function(){"use strict";const t=window,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,i=Symbol(),s=new WeakMap;let n=class{constructor(t,e,s){if(this._$cssResult$=!0,s!==i)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const i=this.t;if(e&&void 0===t){const e=void 0!==i&&1===i.length;e&&(t=s.get(i)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&s.set(i,t))}return t}toString(){return this.cssText}};const r=(t,...e)=>{const s=1===t.length?t[0]:e.reduce(((e,i,s)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(i)+t[s+1]),t[0]);return new n(s,t,i)},o=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const i of t.cssRules)e+=i.cssText;return(t=>new n("string"==typeof t?t:t+"",void 0,i))(e)})(t):t;var l;const h=window,a=h.trustedTypes,d=a?a.emptyScript:"",c=h.reactiveElementPolyfillSupport,u={toAttribute(t,e){switch(e){case Boolean:t=t?d:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let i=t;switch(e){case Boolean:i=null!==t;break;case Number:i=null===t?null:Number(t);break;case Object:case Array:try{i=JSON.parse(t)}catch(t){i=null}}return i}},p=(t,e)=>e!==t&&(e==e||t==t),v={attribute:!0,type:String,converter:u,reflect:!1,hasChanged:p};let $=class extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this.u()}static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&void 0!==e?e:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((e,i)=>{const s=this._$Ep(i,e);void 0!==s&&(this._$Ev.set(s,i),t.push(s))})),t}static createProperty(t,e=v){if(e.state&&(e.attribute=!1),this.finalize(),this.elementProperties.set(t,e),!e.noAccessor&&!this.prototype.hasOwnProperty(t)){const i="symbol"==typeof t?Symbol():"__"+t,s=this.getPropertyDescriptor(t,i,e);void 0!==s&&Object.defineProperty(this.prototype,t,s)}}static getPropertyDescriptor(t,e,i){return{get(){return this[e]},set(s){const n=this[t];this[e]=s,this.requestUpdate(t,n,i)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||v}static finalize(){if(this.hasOwnProperty("finalized"))return!1;this.finalized=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,e=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const i of e)this.createProperty(i,t[i])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const i=new Set(t.flat(1/0).reverse());for(const t of i)e.unshift(o(t))}else void 0!==t&&e.push(o(t));return e}static _$Ep(t,e){const i=e.attribute;return!1===i?void 0:"string"==typeof i?i:"string"==typeof t?t.toLowerCase():void 0}u(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)))}addController(t){var e,i;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(i=t.hostConnected)||void 0===i||i.call(t))}removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((t,e)=>{this.hasOwnProperty(e)&&(this._$Ei.set(e,this[e]),delete this[e])}))}createRenderRoot(){var i;const s=null!==(i=this.shadowRoot)&&void 0!==i?i:this.attachShadow(this.constructor.shadowRootOptions);return((i,s)=>{e?i.adoptedStyleSheets=s.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):s.forEach((e=>{const s=document.createElement("style"),n=t.litNonce;void 0!==n&&s.setAttribute("nonce",n),s.textContent=e.cssText,i.appendChild(s)}))})(s,this.constructor.elementStyles),s}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)}))}attributeChangedCallback(t,e,i){this._$AK(t,i)}_$EO(t,e,i=v){var s;const n=this.constructor._$Ep(t,i);if(void 0!==n&&!0===i.reflect){const r=(void 0!==(null===(s=i.converter)||void 0===s?void 0:s.toAttribute)?i.converter:u).toAttribute(e,i.type);this._$El=t,null==r?this.removeAttribute(n):this.setAttribute(n,r),this._$El=null}}_$AK(t,e){var i;const s=this.constructor,n=s._$Ev.get(t);if(void 0!==n&&this._$El!==n){const t=s.getPropertyOptions(n),r="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(i=t.converter)||void 0===i?void 0:i.fromAttribute)?t.converter:u;this._$El=n,this[n]=r.fromAttribute(e,t.type),this._$El=null}}requestUpdate(t,e,i){let s=!0;void 0!==t&&(((i=i||this.constructor.getPropertyOptions(t)).hasChanged||p)(this[t],e)?(this._$AL.has(t)||this._$AL.set(t,e),!0===i.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,i))):s=!1),!this.isUpdatePending&&s&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((t,e)=>this[e]=t)),this._$Ei=void 0);let e=!1;const i=this._$AL;try{e=this.shouldUpdate(i),e?(this.willUpdate(i),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)})),this.update(i)):this._$Ek()}catch(t){throw e=!1,this._$Ek(),t}e&&this._$AE(i)}willUpdate(t){}_$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach((t=>{var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,e)=>this._$EO(e,this[e],t))),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}};var _;$.finalized=!0,$.elementProperties=new Map,$.elementStyles=[],$.shadowRootOptions={mode:"open"},null==c||c({ReactiveElement:$}),(null!==(l=h.reactiveElementVersions)&&void 0!==l?l:h.reactiveElementVersions=[]).push("1.6.1");const f=window,m=f.trustedTypes,g=m?m.createPolicy("lit-html",{createHTML:t=>t}):void 0,A="$lit$",y=`lit$${(Math.random()+"").slice(9)}$`,E="?"+y,S=`<${E}>`,b=document,w=()=>b.createComment(""),C=t=>null===t||"object"!=typeof t&&"function"!=typeof t,x=Array.isArray,U="[ \t\n\f\r]",P=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,H=/-->/g,T=/>/g,N=RegExp(`>|${U}(?:([^\\s"'>=/]+)(${U}*=${U}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),k=/'/g,O=/"/g,R=/^(?:script|style|textarea|title)$/i,M=(t=>(e,...i)=>({_$litType$:t,strings:e,values:i}))(1),L=Symbol.for("lit-noChange"),I=Symbol.for("lit-nothing"),j=new WeakMap,z=b.createTreeWalker(b,129,null,!1),B=(t,e)=>{const i=t.length-1,s=[];let n,r=2===e?"":"",o=P;for(let e=0;e"===h[0]?(o=null!=n?n:P,a=-1):void 0===h[1]?a=-2:(a=o.lastIndex-h[2].length,l=h[1],o=void 0===h[3]?N:'"'===h[3]?O:k):o===O||o===k?o=N:o===H||o===T?o=P:(o=N,n=void 0);const c=o===N&&t[e+1].startsWith("/>")?" ":"";r+=o===P?i+S:a>=0?(s.push(l),i.slice(0,a)+A+i.slice(a)+y+c):i+y+(-2===a?(s.push(void 0),e):c)}const l=r+(t[i]||"")+(2===e?"":"");if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return[void 0!==g?g.createHTML(l):l,s]};class D{constructor({strings:t,_$litType$:e},i){let s;this.parts=[];let n=0,r=0;const o=t.length-1,l=this.parts,[h,a]=B(t,e);if(this.el=D.createElement(h,i),z.currentNode=this.el.content,2===e){const t=this.el.content,e=t.firstChild;e.remove(),t.append(...e.childNodes)}for(;null!==(s=z.nextNode())&&l.length0){s.textContent=m?m.emptyScript:"";for(let i=0;ix(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]))(t)?this.T(t):this._(t)}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t))}_(t){this._$AH!==I&&C(this._$AH)?this._$AA.nextSibling.data=t:this.$(b.createTextNode(t)),this._$AH=t}g(t){var e;const{values:i,_$litType$:s}=t,n="number"==typeof s?this._$AC(t):(void 0===s.el&&(s.el=D.createElement(s.h,this.options)),s);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===n)this._$AH.v(i);else{const t=new q(n,this),e=t.u(this.options);t.v(i),this.$(e),this._$AH=t}}_$AC(t){let e=j.get(t.strings);return void 0===e&&j.set(t.strings,e=new D(t)),e}T(t){x(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let i,s=0;for(const n of t)s===e.length?e.push(i=new W(this.k(w()),this.k(w()),this,this.options)):i=e[s],i._$AI(n),s++;s2||""!==i[0]||""!==i[1]?(this._$AH=Array(i.length-1).fill(new String),this.strings=i):this._$AH=I}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,e=this,i,s){const n=this.strings;let r=!1;if(void 0===n)t=V(this,t,e,0),r=!C(t)||t!==this._$AH&&t!==L,r&&(this._$AH=t);else{const s=t;let o,l;for(t=n[0],o=0;o{var s,n;const r=null!==(s=null==i?void 0:i.renderBefore)&&void 0!==s?s:e;let o=r._$litPart$;if(void 0===o){const t=null!==(n=null==i?void 0:i.renderBefore)&&void 0!==n?n:null;r._$litPart$=o=new W(e.insertBefore(w(),t),t,void 0,null!=i?i:{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}render(){return L}}et.finalized=!0,et._$litElement$=!0,null===(Y=globalThis.litElementHydrateSupport)||void 0===Y||Y.call(globalThis,{LitElement:et});const it=globalThis.litElementPolyfillSupport;null==it||it({LitElement:et}),(null!==(tt=globalThis.litElementVersions)&&void 0!==tt?tt:globalThis.litElementVersions=[]).push("3.3.2");window.customElements.define("chain-messaging",class extends et{static get properties(){return{loading:{type:Boolean},theme:{type:String,reflect:!0}}}static get styles(){return r` +!function(t){"function"==typeof define&&define.amd?define(t):t()}((function(){"use strict";const t=window,e=t.ShadowRoot&&(void 0===t.ShadyCSS||t.ShadyCSS.nativeShadow)&&"adoptedStyleSheets"in Document.prototype&&"replace"in CSSStyleSheet.prototype,s=Symbol(),r=new WeakMap;let i=class{constructor(t,e,r){if(this._$cssResult$=!0,r!==s)throw Error("CSSResult is not constructable. Use `unsafeCSS` or `css` instead.");this.cssText=t,this.t=e}get styleSheet(){let t=this.o;const s=this.t;if(e&&void 0===t){const e=void 0!==s&&1===s.length;e&&(t=r.get(s)),void 0===t&&((this.o=t=new CSSStyleSheet).replaceSync(this.cssText),e&&r.set(s,t))}return t}toString(){return this.cssText}};const n=(t,...e)=>{const r=1===t.length?t[0]:e.reduce(((e,s,r)=>e+(t=>{if(!0===t._$cssResult$)return t.cssText;if("number"==typeof t)return t;throw Error("Value passed to 'css' function must be a 'css' function result: "+t+". Use 'unsafeCSS' to pass non-literal values, but take care to ensure page security.")})(s)+t[r+1]),t[0]);return new i(r,t,s)},o=e?t=>t:t=>t instanceof CSSStyleSheet?(t=>{let e="";for(const s of t.cssRules)e+=s.cssText;return(t=>new i("string"==typeof t?t:t+"",void 0,s))(e)})(t):t;var a;const l=window,h=l.trustedTypes,c=h?h.emptyScript:"",u=l.reactiveElementPolyfillSupport,d={toAttribute(t,e){switch(e){case Boolean:t=t?c:null;break;case Object:case Array:t=null==t?t:JSON.stringify(t)}return t},fromAttribute(t,e){let s=t;switch(e){case Boolean:s=null!==t;break;case Number:s=null===t?null:Number(t);break;case Object:case Array:try{s=JSON.parse(t)}catch(t){s=null}}return s}},p=(t,e)=>e!==t&&(e==e||t==t),g={attribute:!0,type:String,converter:d,reflect:!1,hasChanged:p},f="finalized";let y=class extends HTMLElement{constructor(){super(),this._$Ei=new Map,this.isUpdatePending=!1,this.hasUpdated=!1,this._$El=null,this._$Eu()}static addInitializer(t){var e;this.finalize(),(null!==(e=this.h)&&void 0!==e?e:this.h=[]).push(t)}static get observedAttributes(){this.finalize();const t=[];return this.elementProperties.forEach(((e,s)=>{const r=this._$Ep(s,e);void 0!==r&&(this._$Ev.set(r,s),t.push(r))})),t}static createProperty(t,e=g){if(e.state&&(e.attribute=!1),this.finalize(),this.elementProperties.set(t,e),!e.noAccessor&&!this.prototype.hasOwnProperty(t)){const s="symbol"==typeof t?Symbol():"__"+t,r=this.getPropertyDescriptor(t,s,e);void 0!==r&&Object.defineProperty(this.prototype,t,r)}}static getPropertyDescriptor(t,e,s){return{get(){return this[e]},set(r){const i=this[t];this[e]=r,this.requestUpdate(t,i,s)},configurable:!0,enumerable:!0}}static getPropertyOptions(t){return this.elementProperties.get(t)||g}static finalize(){if(this.hasOwnProperty(f))return!1;this[f]=!0;const t=Object.getPrototypeOf(this);if(t.finalize(),void 0!==t.h&&(this.h=[...t.h]),this.elementProperties=new Map(t.elementProperties),this._$Ev=new Map,this.hasOwnProperty("properties")){const t=this.properties,e=[...Object.getOwnPropertyNames(t),...Object.getOwnPropertySymbols(t)];for(const s of e)this.createProperty(s,t[s])}return this.elementStyles=this.finalizeStyles(this.styles),!0}static finalizeStyles(t){const e=[];if(Array.isArray(t)){const s=new Set(t.flat(1/0).reverse());for(const t of s)e.unshift(o(t))}else void 0!==t&&e.push(o(t));return e}static _$Ep(t,e){const s=e.attribute;return!1===s?void 0:"string"==typeof s?s:"string"==typeof t?t.toLowerCase():void 0}_$Eu(){var t;this._$E_=new Promise((t=>this.enableUpdating=t)),this._$AL=new Map,this._$Eg(),this.requestUpdate(),null===(t=this.constructor.h)||void 0===t||t.forEach((t=>t(this)))}addController(t){var e,s;(null!==(e=this._$ES)&&void 0!==e?e:this._$ES=[]).push(t),void 0!==this.renderRoot&&this.isConnected&&(null===(s=t.hostConnected)||void 0===s||s.call(t))}removeController(t){var e;null===(e=this._$ES)||void 0===e||e.splice(this._$ES.indexOf(t)>>>0,1)}_$Eg(){this.constructor.elementProperties.forEach(((t,e)=>{this.hasOwnProperty(e)&&(this._$Ei.set(e,this[e]),delete this[e])}))}createRenderRoot(){var s;const r=null!==(s=this.shadowRoot)&&void 0!==s?s:this.attachShadow(this.constructor.shadowRootOptions);return((s,r)=>{e?s.adoptedStyleSheets=r.map((t=>t instanceof CSSStyleSheet?t:t.styleSheet)):r.forEach((e=>{const r=document.createElement("style"),i=t.litNonce;void 0!==i&&r.setAttribute("nonce",i),r.textContent=e.cssText,s.appendChild(r)}))})(r,this.constructor.elementStyles),r}connectedCallback(){var t;void 0===this.renderRoot&&(this.renderRoot=this.createRenderRoot()),this.enableUpdating(!0),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostConnected)||void 0===e?void 0:e.call(t)}))}enableUpdating(t){}disconnectedCallback(){var t;null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostDisconnected)||void 0===e?void 0:e.call(t)}))}attributeChangedCallback(t,e,s){this._$AK(t,s)}_$EO(t,e,s=g){var r;const i=this.constructor._$Ep(t,s);if(void 0!==i&&!0===s.reflect){const n=(void 0!==(null===(r=s.converter)||void 0===r?void 0:r.toAttribute)?s.converter:d).toAttribute(e,s.type);this._$El=t,null==n?this.removeAttribute(i):this.setAttribute(i,n),this._$El=null}}_$AK(t,e){var s;const r=this.constructor,i=r._$Ev.get(t);if(void 0!==i&&this._$El!==i){const t=r.getPropertyOptions(i),n="function"==typeof t.converter?{fromAttribute:t.converter}:void 0!==(null===(s=t.converter)||void 0===s?void 0:s.fromAttribute)?t.converter:d;this._$El=i,this[i]=n.fromAttribute(e,t.type),this._$El=null}}requestUpdate(t,e,s){let r=!0;void 0!==t&&(((s=s||this.constructor.getPropertyOptions(t)).hasChanged||p)(this[t],e)?(this._$AL.has(t)||this._$AL.set(t,e),!0===s.reflect&&this._$El!==t&&(void 0===this._$EC&&(this._$EC=new Map),this._$EC.set(t,s))):r=!1),!this.isUpdatePending&&r&&(this._$E_=this._$Ej())}async _$Ej(){this.isUpdatePending=!0;try{await this._$E_}catch(t){Promise.reject(t)}const t=this.scheduleUpdate();return null!=t&&await t,!this.isUpdatePending}scheduleUpdate(){return this.performUpdate()}performUpdate(){var t;if(!this.isUpdatePending)return;this.hasUpdated,this._$Ei&&(this._$Ei.forEach(((t,e)=>this[e]=t)),this._$Ei=void 0);let e=!1;const s=this._$AL;try{e=this.shouldUpdate(s),e?(this.willUpdate(s),null===(t=this._$ES)||void 0===t||t.forEach((t=>{var e;return null===(e=t.hostUpdate)||void 0===e?void 0:e.call(t)})),this.update(s)):this._$Ek()}catch(t){throw e=!1,this._$Ek(),t}e&&this._$AE(s)}willUpdate(t){}_$AE(t){var e;null===(e=this._$ES)||void 0===e||e.forEach((t=>{var e;return null===(e=t.hostUpdated)||void 0===e?void 0:e.call(t)})),this.hasUpdated||(this.hasUpdated=!0,this.firstUpdated(t)),this.updated(t)}_$Ek(){this._$AL=new Map,this.isUpdatePending=!1}get updateComplete(){return this.getUpdateComplete()}getUpdateComplete(){return this._$E_}shouldUpdate(t){return!0}update(t){void 0!==this._$EC&&(this._$EC.forEach(((t,e)=>this._$EO(e,this[e],t))),this._$EC=void 0),this._$Ek()}updated(t){}firstUpdated(t){}};var m;y[f]=!0,y.elementProperties=new Map,y.elementStyles=[],y.shadowRootOptions={mode:"open"},null==u||u({ReactiveElement:y}),(null!==(a=l.reactiveElementVersions)&&void 0!==a?a:l.reactiveElementVersions=[]).push("1.6.3");const _=window,v=_.trustedTypes,$=v?v.createPolicy("lit-html",{createHTML:t=>t}):void 0,w="$lit$",E=`lit$${(Math.random()+"").slice(9)}$`,A="?"+E,b=`<${A}>`,S=document,T=()=>S.createComment(""),M=t=>null===t||"object"!=typeof t&&"function"!=typeof t,C=Array.isArray,R="[ \t\n\f\r]",P=/<(?:(!--|\/[^a-zA-Z])|(\/?[a-zA-Z][^>\s]*)|(\/?$))/g,x=/-->/g,O=/>/g,N=RegExp(`>|${R}(?:([^\\s"'>=/]+)(${R}*=${R}*(?:[^ \t\n\f\r"'\`<>=]|("|')|))|$)`,"g"),U=/'/g,I=/"/g,D=/^(?:script|style|textarea|title)$/i,k=(t=>(e,...s)=>({_$litType$:t,strings:e,values:s}))(1),H=Symbol.for("lit-noChange"),q=Symbol.for("lit-nothing"),L=new WeakMap,j=S.createTreeWalker(S,129,null,!1);function B(t,e){if(!Array.isArray(t)||!t.hasOwnProperty("raw"))throw Error("invalid template strings array");return void 0!==$?$.createHTML(e):e}const V=(t,e)=>{const s=t.length-1,r=[];let i,n=2===e?"":"",o=P;for(let e=0;e"===l[0]?(o=null!=i?i:P,h=-1):void 0===l[1]?h=-2:(h=o.lastIndex-l[2].length,a=l[1],o=void 0===l[3]?N:'"'===l[3]?I:U):o===I||o===U?o=N:o===x||o===O?o=P:(o=N,i=void 0);const u=o===N&&t[e+1].startsWith("/>")?" ":"";n+=o===P?s+b:h>=0?(r.push(a),s.slice(0,h)+w+s.slice(h)+E+u):s+E+(-2===h?(r.push(void 0),e):u)}return[B(t,n+(t[s]||"")+(2===e?"":"")),r]};class z{constructor({strings:t,_$litType$:e},s){let r;this.parts=[];let i=0,n=0;const o=t.length-1,a=this.parts,[l,h]=V(t,e);if(this.el=z.createElement(l,s),j.currentNode=this.el.content,2===e){const t=this.el.content,e=t.firstChild;e.remove(),t.append(...e.childNodes)}for(;null!==(r=j.nextNode())&&a.length0){r.textContent=v?v.emptyScript:"";for(let s=0;sC(t)||"function"==typeof(null==t?void 0:t[Symbol.iterator]))(t)?this.T(t):this._(t)}k(t){return this._$AA.parentNode.insertBefore(t,this._$AB)}$(t){this._$AH!==t&&(this._$AR(),this._$AH=this.k(t))}_(t){this._$AH!==q&&M(this._$AH)?this._$AA.nextSibling.data=t:this.$(S.createTextNode(t)),this._$AH=t}g(t){var e;const{values:s,_$litType$:r}=t,i="number"==typeof r?this._$AC(t):(void 0===r.el&&(r.el=z.createElement(B(r.h,r.h[0]),this.options)),r);if((null===(e=this._$AH)||void 0===e?void 0:e._$AD)===i)this._$AH.v(s);else{const t=new W(i,this),e=t.u(this.options);t.v(s),this.$(e),this._$AH=t}}_$AC(t){let e=L.get(t.strings);return void 0===e&&L.set(t.strings,e=new z(t)),e}T(t){C(this._$AH)||(this._$AH=[],this._$AR());const e=this._$AH;let s,r=0;for(const i of t)r===e.length?e.push(s=new F(this.k(T()),this.k(T()),this,this.options)):s=e[r],s._$AI(i),r++;r2||""!==s[0]||""!==s[1]?(this._$AH=Array(s.length-1).fill(new String),this.strings=s):this._$AH=q}get tagName(){return this.element.tagName}get _$AU(){return this._$AM._$AU}_$AI(t,e=this,s,r){const i=this.strings;let n=!1;if(void 0===i)t=K(this,t,e,0),n=!M(t)||t!==this._$AH&&t!==H,n&&(this._$AH=t);else{const r=t;let o,a;for(t=i[0],o=0;o{var r,i;const n=null!==(r=null==s?void 0:s.renderBefore)&&void 0!==r?r:e;let o=n._$litPart$;if(void 0===o){const t=null!==(i=null==s?void 0:s.renderBefore)&&void 0!==i?i:null;n._$litPart$=o=new F(e.insertBefore(T(),t),t,void 0,null!=s?s:{})}return o._$AI(t),o})(e,this.renderRoot,this.renderOptions)}connectedCallback(){var t;super.connectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!0)}disconnectedCallback(){var t;super.disconnectedCallback(),null===(t=this._$Do)||void 0===t||t.setConnected(!1)}render(){return H}}rt.finalized=!0,rt._$litElement$=!0,null===(et=globalThis.litElementHydrateSupport)||void 0===et||et.call(globalThis,{LitElement:rt});const it=globalThis.litElementPolyfillSupport;null==it||it({LitElement:rt}),(null!==(st=globalThis.litElementVersions)&&void 0!==st?st:globalThis.litElementVersions=[]).push("3.3.3");class nt{static prepareOutgoingData(t){return JSON.stringify(t)}constructor(t){if(!t)throw new Error("Source must be spcified");if(!this.constructor.type)throw new Error("Type not defined");if(this.constructor.name||console.warn("No name provided"),this.constructor.description||console.warn("No description provided"),!this.sendMessage)throw new Error("A new target requires a sendMessage method")}}const ot={},at={};class lt{static registerPlugin(t,e){return t.init(lt,e),lt}static registerTargetType(t,e){if(t in at)throw new Error("Target type has already been registered");if(!(e.prototype instanceof nt))throw new Error("Target constructors must inherit from the Target base class");return at[t]=e,lt}static registerEpmlMessageType(t,e){return ot[t]=e,lt}registerPlugin(t){return t.init(this),this}static handleMessage(t,e){const s=lt.prepareIncomingData(t);"EpmlMessageType"in s&&ot[s.EpmlMessageType](s,e,this)}static prepareIncomingData(t){return"string"!=typeof t?t:JSON.parse(t)}static createTargets(t){Array.isArray(t)||(t=[t]);const e=[];for(const s of t)void 0===s.allowObjects&&(s.allowObjects=!1),e.push(...lt.createTarget(s));return e}static createTarget(t){if(!at[t.type])throw new Error(`Target type '${t.type}' not registered`);let e=new at[t.type](t.source);Array.isArray(e)||(e=[e]);for(const s of e)s.allowObjects=t.allowObjects;return e}constructor(t){this.targets=this.constructor.createTargets(t)}}var ht=(t,e)=>{for(e=t="";t++<36;e+=51*t&52?(15^t?8^Math.random()*(20^t?16:4):4).toString(16):"-");return e};const ct=15,ut="EPML_READY_STATE_CHECK",dt="EPML_READY_STATE_CHECK_RESPONSE",pt={},gt={init:(t,e)=>{if(t.prototype.ready)throw new Error("Epml.prototype.ready is already defined");if(t.prototype.imReady)throw new Error("Epml.prototype.imReady is already defined");t.prototype.ready=mt,t.prototype.resetReadyCheck=_t,t.prototype.imReady=yt,t.registerEpmlMessageType(ut,ft),t.registerEpmlMessageType(dt,$t)}};function ft(t,e){e._i_am_ready&&e.sendMessage({EpmlMessageType:dt,requestID:t.requestID})}function yt(){for(const t of this.targets)t._i_am_ready=!0}function mt(){return this._ready_plugin=this._ready_plugin||{},this._ready_plugin.pendingReadyResolves=this._ready_plugin.pendingReadyResolves?this._ready_plugin.pendingReadyResolves:[],this._pending_ready_checking||(this._pending_ready_checking=!0,vt.call(this,this.targets).then((()=>{this._ready_plugin.pendingReadyResolves.forEach((t=>t()))}))),new Promise((t=>{this._ready_plugin.isReady?t():this._ready_plugin.pendingReadyResolves.push(t)}))}function _t(){this._ready_plugin=this._ready_plugin||{},this._ready_plugin.isReady=!1}function vt(t){return this._ready_plugin=this._ready_plugin||{},this._ready_plugin.pendingReadyResolves=[],Promise.all(t.map((t=>new Promise(((e,s)=>{const r=ht(),i=setInterval((()=>{t.sendMessage({EpmlMessageType:ut,requestID:r})}),ct);pt[r]=()=>{clearInterval(i),e()}}))))).then((()=>{this._ready_plugin.isReady=!0}))}function $t(t,e){e._ready_plugin=e._ready_plugin||{},e._ready_plugin._is_ready=!0,pt[t.requestID]()}const wt=new Map;class Et extends nt{static get sources(){return Array.from(wt.keys())}static get targets(){return Array.from(wt.values())}static getTargetFromSource(t){return wt.get(t)}static hasTarget(t){return wt.has(t)}static get type(){return"WINDOW"}static get name(){return"Content window plugin"}static get description(){return"Allows Epml to communicate with iframes and popup windows."}static test(t){return"object"==typeof t&&t===t.self}isFrom(t){}constructor(t){if(super(t),wt.has(t))return wt.get(t);if(!this.constructor.test(t))throw new Error("Source can not be used with target");this._source=t,this._sourceOrigin="*",wt.set(t,this)}get source(){return this._source}sendMessage(t){t=nt.prepareOutgoingData(t),this._source.postMessage(t,this._sourceOrigin)}}var At={init:function(t){!function(t,e,s){if(t.addEventListener)t.addEventListener(e,s,!1);else{if(!t.attachEvent)throw new Error("Could not bind event.");t.attachEvent("on"+e,s)}}(window,"message",(e=>{Et.hasTarget(e.source)&&t.handleMessage(e.data,Et.getTargetFromSource(e.source))})),t.registerTargetType(Et.type,Et)}};const bt="REQUEST",St="REQUEST_RESPONSE",Tt=new Map,Mt={},Ct={init:(t,e)=>{if(t.prototype.request)throw new Error("Epml.prototype.request is already defined");if(t.prototype.route)throw new Error("Empl.prototype.route is already defined");t.prototype.request=Rt,t.prototype.route=Ot,t.registerEpmlMessageType(bt,xt),t.registerEpmlMessageType(St,Pt)}},Rt=function(t,e,s){return Promise.all(this.targets.map((r=>{const i=ht(),n={EpmlMessageType:bt,requestOrResponse:"request",requestID:i,requestType:t,data:e};return r.sendMessage(n),new Promise(((t,e)=>{let r;s&&(r=setTimeout((()=>{delete Mt[i],e(new Error("Request timed out"))}),s)),Mt[i]=(...e)=>{r&&clearTimeout(r),t(...e)}}))}))).then((t=>{if(1===this.targets.length)return t[0]}))};function Pt(t,e,s){if(t.requestID in Mt){const e=t.data;Mt[t.requestID](e)}else console.warn("requestID not found in pendingRequests")}function xt(t,e){if(!Tt.has(e))return void console.warn("Route does not exist - missing target");const s=Tt.get(e)[t.requestType];s?s(t,e):console.warn("Route does not exist")}function Ot(t,e){if(this.routes||(this.routes={}),!this.routes[t])for(const s of this.targets){Tt.has(s)||Tt.set(s,{});Tt.get(s)[t]=(t,s)=>{Promise.resolve(e(t)).catch((t=>t instanceof Error?t.message:t)).then((e=>{s.sendMessage({data:e,EpmlMessageType:St,requestOrResponse:"request",requestID:t.requestID})}))}}}const Nt="PROXY_MESSAGE",Ut=new class{constructor(t){this._map=t||new Map,this._revMap=new Map,this._map.forEach(((t,e)=>{this._revMap.set(e,t)}))}values(){return this._map.values()}entries(){return this._map.entries()}push(t,e){this._map.set(t,e),this._revMap.set(e,t)}getByKey(t){return this._map.get(t)}getByValue(t){return this._revMap.get(t)}hasKey(t){return this._map.has(t)}hasValue(t){return this._revMap.has(t)}deleteByKey(t){const e=this._map.get(t);this._map.delete(t),this._revMap.delete(e)}deleteByValue(t){const e=this._revMap.get(t);this._map.delete(e),this._revMap.delete(t)}};class It extends nt{static get proxySources(){return Ut}static get sources(){for(const[t,e]of Ut)for(const[t]of e);Array.from(Ut.entries()).map(((t,e)=>({proxy:t,target:Array.from(e.keys())[0]})))}static get targets(){return Array.from(Ut.values())}static getTargetFromSource(t){return Ut.getByValue(t)}static hasTarget(t){return Ut.hasValue(t)}static get type(){return"PROXY"}static get name(){return"Proxy target"}static get description(){return"Uses other target, and proxies requests, allowing things like iframes to communicate through their host"}static test(t){return"object"==typeof t&&t.proxy instanceof this.Epml}isFrom(t){}constructor(t){if(super(t),this.constructor.proxySources.push(t.id,this),!this.constructor.test(t))throw new Error("Source can not be used with target");this._source=t}get source(){return this._source}sendMessage(t){const e=ht();t=nt.prepareOutgoingData(t),t={EpmlMessageType:Nt,state:"TRANSIT",requestID:e,target:this._source.target,message:t,id:this._source.id},this._source.proxy.targets[0].sendMessage(t)}}const Dt=It.proxySources;let kt;var Ht={init:function(t){Object.defineProperty(It,"Epml",{get:()=>t}),kt=t,t.registerTargetType(It.type,It),t.registerProxyInstance=Lt,t.registerEpmlMessageType(Nt,qt)}};function qt(t,e){if("TRANSIT"===t.state){const e=Dt.getByKey(t.target);if(!e)return void console.warn(`Target ${t.target} not registered.`);t.state="DELIVERY",e.targets.forEach((e=>e.sendMessage(t)))}else if("DELIVERY"===t.state){if(!Dt.getByKey(t.target))return void console.warn(`Target ${t.target} not registered.`);const e=Dt.getByKey(t.target);kt.handleMessage(t.message,e)}}function Lt(t,e){Dt.hasKey(t)&&console.warn(`${t} is already defined. Overwriting...`),Dt.push(t,e)}const jt="STREAM_UPDATE",Bt={};class Vt{static get streams(){return Bt}constructor(t,e=(()=>{})){if(this._name=t,this.targets=[],this._subscriptionFn=e,t in Bt)return console.warn(`Stream with name ${t} already exists! Returning it instead`),Bt[t];Bt[t]=this}async subscribe(t){t in this.targets&&console.info("Target is already subscribed to this stream");const e=await this._subscriptionFn();this._sendMessage(e,t),this.targets.push(t)}_sendMessage(t,e){e.sendMessage({data:nt.prepareOutgoingData(t),EpmlMessageType:jt,streamName:this._name})}emit(t){this.targets.forEach((e=>this._sendMessage(t,e)))}}const zt="JOIN_STREAM",Kt={},Wt={init:(t,e)=>{if(t.prototype.subscribe)throw new Error("Epml.prototype.subscribe is already defined");if(t.prototype.createStream)throw new Error("Empl.prototype.createStream is already defined");t.prototype.subscribe=Yt,t.registerEpmlMessageType(zt,Ft),t.registerEpmlMessageType(jt,Jt)}},Ft=function(t,e){const s=t.data.name,r=Vt.streams[s];r?r.subscribe(e):console.warn(`No stream with name ${s}`,this)},Yt=function(t,e){this.targets.forEach((e=>{e.sendMessage({EpmlMessageType:zt,data:{name:t}})})),Kt[t]=Kt[t]||[],Kt[t].push(e)},Jt=function(t,e){Kt[t.streamName].forEach((e=>e(t.data)))};lt.registerPlugin(Ct),lt.registerPlugin(gt),lt.registerPlugin(At),lt.registerPlugin(Wt),lt.registerPlugin(Ht),lt.allowProxying=!0;var Qt="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{};function Xt(){throw new Error("setTimeout has not been defined")}function Zt(){throw new Error("clearTimeout has not been defined")}var Gt=Xt,te=Zt;function ee(t){if(Gt===setTimeout)return setTimeout(t,0);if((Gt===Xt||!Gt)&&setTimeout)return Gt=setTimeout,setTimeout(t,0);try{return Gt(t,0)}catch(e){try{return Gt.call(null,t,0)}catch(e){return Gt.call(this,t,0)}}}"function"==typeof Qt.setTimeout&&(Gt=setTimeout),"function"==typeof Qt.clearTimeout&&(te=clearTimeout);var se,re=[],ie=!1,ne=-1;function oe(){ie&&se&&(ie=!1,se.length?re=se.concat(re):ne=-1,re.length&&ae())}function ae(){if(!ie){var t=ee(oe);ie=!0;for(var e=re.length;e;){for(se=re,re=[];++ne1)for(var s=1;s=0)}));new lt({type:"WINDOW",source:window.parent});window.customElements.define("chain-messaging",class extends rt{static get properties(){return{loading:{type:Boolean},theme:{type:String,reflect:!0}}}static get styles(){return n` * { --mdc-theme-primary: rgb(3, 169, 244); --paper-input-container-focus-color: var(--mdc-theme-primary); @@ -15,8 +15,8 @@ background: var(--white); } - `}constructor(){super(),this.theme=localStorage.getItem("qortalTheme")?localStorage.getItem("qortalTheme"):"light"}render(){return M` + `}constructor(){super(),this.theme=localStorage.getItem("qortalTheme")?localStorage.getItem("qortalTheme"):"light"}render(){return k`

    Coming Soon!

    - `}firstUpdated(){this.changeTheme(),setInterval((()=>{this.changeTheme()}),100),window.addEventListener("contextmenu",(t=>{t.preventDefault()})),window.addEventListener("click",(()=>{})),window.onkeyup=t=>{t.keyCode}}changeTheme(){const t=localStorage.getItem("qortalTheme");this.theme="dark"===t?"dark":"light",document.querySelector("html").setAttribute("theme",this.theme)}isEmptyArray(t){return!t||0===t.length}})})); + `}firstUpdated(){this.changeTheme(),window.addEventListener("storage",(()=>{const t=localStorage.getItem("qortalTheme");this.theme="dark"===t?"dark":"light",document.querySelector("html").setAttribute("theme",this.theme)})),Ee()&&window.addEventListener("contextmenu",(t=>{t.preventDefault(),window.parent.electronAPI.showMyMenu()}))}clearConsole(){Ee()&&(console.clear(),window.parent.electronAPI.clearCache())}changeTheme(){const t=localStorage.getItem("qortalTheme");this.theme="dark"===t?"dark":"light",document.querySelector("html").setAttribute("theme",this.theme)}isEmptyArray(t){return!t||0===t.length}})})); diff --git a/plugins/plugins/core/messaging/q-chat/q-chat-css.src.js b/plugins/plugins/core/messaging/q-chat/q-chat-css.src.js index eb4f7ca3..80b11a08 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat-css.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat-css.src.js @@ -157,6 +157,10 @@ export const qchatStyles = css` padding-top: 20px; padding-left: 20px; padding-right: 20px; + display: flex; + align-items: center; + gap: 10px; + justify-content: space-between; } .center { @@ -172,13 +176,13 @@ export const qchatStyles = css` border-radius: 5px; border: none; display: inline-block; - padding: 14px; color: #fff; - background: var(--tradehead); width: 100%; font-size: 15px; text-align: center; cursor: pointer; + display: flex; + flex: 0; } .people-list .create-chat:hover { diff --git a/plugins/plugins/core/messaging/q-chat/q-chat.src.js b/plugins/plugins/core/messaging/q-chat/q-chat.src.js index 838f42d2..f73558c4 100644 --- a/plugins/plugins/core/messaging/q-chat/q-chat.src.js +++ b/plugins/plugins/core/messaging/q-chat/q-chat.src.js @@ -1,10 +1,9 @@ -import { LitElement, html, css } from 'lit' +import { LitElement, html } from 'lit' import { render } from 'lit/html.js' import { passiveSupport } from 'passive-events-support/src/utils' import { Epml } from '../../../../epml.js' -import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' +import { get, translate } from 'lit-translate' import { qchatStyles } from './q-chat-css.src.js' -import { repeat } from 'lit/directives/repeat.js' import { Editor, Extension } from '@tiptap/core' import isElectron from 'is-electron' import WebWorker from 'web-worker:./computePowWorker.src.js' @@ -13,19 +12,22 @@ import Underline from '@tiptap/extension-underline'; import Placeholder from '@tiptap/extension-placeholder' import Highlight from '@tiptap/extension-highlight' import snackbar from '../../components/snackbar.js' +import ShortUniqueId from 'short-unique-id'; import '../../components/ChatWelcomePage.js' import '../../components/ChatHead.js' import '../../components/ChatPage.js' import '../../components/WrapperModal.js' import '../../components/ChatSearchResults.js' - +import '../../components/ChatGroupsModal.js' import '@material/mwc-button' import '@material/mwc-dialog' import '@material/mwc-icon' import '@material/mwc-snackbar' import '@polymer/paper-spinner/paper-spinner-lite.js' import '@vaadin/grid' +import '@vaadin/tooltip'; + passiveSupport({ events: ['touchstart'] }) @@ -53,15 +55,20 @@ class Chat extends LitElement { userFoundModalOpen: { type: Boolean }, userSelected: { type: Object }, editor: {type: Object}, - groupInvites: { type: Array } + groupInvites: { type: Array }, + loggedInUserName: {type: String}, + loggedInUserAddress: {type: String}, + openDialogGroupsModal: {type: Boolean} } } - static styles = [qchatStyles] + static get styles() { + return [qchatStyles]; + } constructor() { super() - this.selectedAddress = {} + this.selectedAddress = window.parent.reduxStore.getState().app.selectedAddress this.config = { user: { node: { @@ -92,12 +99,15 @@ class Chat extends LitElement { this.userFoundModalOpen = false this.userSelected = {} this.groupInvites = [] + this.loggedInUserName = "" + this.openDialogGroupsModal = false + this.uid = new ShortUniqueId(); + } async setActiveChatHeadUrl(url) { - this.activeChatHeadUrl = '' - await this.updateComplete; this.activeChatHeadUrl = url + this.requestUpdate() } resetChatEditor(){ @@ -146,6 +156,8 @@ class Chat extends LitElement { this.unsubscribeStore = window.parent.reduxStore.subscribe(() => { try { + const currentState = window.parent.reduxStore.getState(); + if(window.parent.location && window.parent.location.search) { const queryString = window.parent.location.search const params = new URLSearchParams(queryString) @@ -157,8 +169,13 @@ class Chat extends LitElement { this.setActiveChatHeadUrl(chat) } } - } catch (error) { - } + if(currentState.app.accountInfo && currentState.app.accountInfo.names && currentState.app.accountInfo.names.length > 0 && this.loggedInUserName !== currentState.app.accountInfo.names[0].name){ + this.loggedInUserName = currentState.app.accountInfo.names[0].name + } + if(currentState.app.accountInfo && currentState.app.accountInfo.addressInfo && currentState.app.accountInfo.addressInfo.address && this.loggedInUserAddress !== currentState.app.accountInfo.addressInfo.address){ + this.loggedInUserAddress = currentState.app.accountInfo.addressInfo.address + } + } catch (error) { /* empty */ } }) } @@ -178,44 +195,74 @@ class Chat extends LitElement { }) } + setOpenDialogGroupsModal(val){ + this.openDialogGroupsModal = val + } + + openTabToGroupManagement(){ + window.parent.reduxStore.dispatch( + window.parent.reduxAction.setNewTab({ + url: `group-management`, + id: this.uid.rnd(), + myPlugObj: { + "url": "group-management", + "domain": "core", + "page": "group-management/index.html", + "title": "Group Management", + "icon": "vaadin:group", + "mwcicon": "group", + "pluginNumber": "plugin-fJZNpyLGTl", + "menus": [], + "parent": false + }, + openExisting: true + }) + ); + } + render() { return html`
    this.scrollToBottom()}> @@ -370,7 +417,7 @@ class Chat extends LitElement {
    - + ` } @@ -432,23 +479,32 @@ class Chat extends LitElement { document.querySelector('html').setAttribute('theme', this.theme) }) - if (!isElectron()) { - } else { + if (!isElectron()) { /* empty */ } else { window.addEventListener('contextmenu', (event) => { - event.preventDefault() - window.parent.electronAPI.showMyMenu() - }) + // Check if the clicked element has the class + let target = event.target; + while (target !== null) { + if (target.classList && target.classList.contains('customContextMenuDiv')) { + // Your custom context menu logic + this.showContextMenu(event); + return; + } + target = target.parentNode; + } + + // If it doesn't, show the default Electron context menu + event.preventDefault(); + window.parent.electronAPI.showMyMenu(); + }); + + + } let configLoaded = false parentEpml.ready().then(() => { - parentEpml.subscribe('selected_address', async selectedAddress => { - this.selectedAddress = {} - selectedAddress = JSON.parse(selectedAddress) - if (!selectedAddress || Object.entries(selectedAddress).length === 0) return - this.selectedAddress = selectedAddress - }) + parentEpml.subscribe('config', c => { if (!configLoaded) { setTimeout(getBlockedUsers, 1) @@ -464,6 +520,7 @@ class Chat extends LitElement { url: `/addresses/balance/${window.parent.reduxStore.getState().app.selectedAddress.address}` }).then(res => { this.balance = res + this.requestUpdate() }) }) parentEpml.imReady() @@ -473,9 +530,10 @@ class Chat extends LitElement { }, 60000) } + + clearConsole() { - if (!isElectron()) { - } else { + if (!isElectron()) { /* empty */ } else { console.clear() window.parent.electronAPI.clearCache() } @@ -565,7 +623,7 @@ class Chat extends LitElement { recipient = _recipient } else { recipient = myNameRes.owner - }; + } const getAddressPublicKey = async () => { let isEncrypted; @@ -620,7 +678,7 @@ class Chat extends LitElement { const worker = new WebWorker() let nonce = null let chatBytesArray = null; - await new Promise((res, rej) => { + await new Promise((res) => { worker.postMessage({chatBytes, path, difficulty}) worker.onmessage = e => { worker.terminate() @@ -669,7 +727,7 @@ class Chat extends LitElement { } renderLoadingText() { - return html`${translate("chatpage.cchange2")}` + return html`
    ` } renderSendText() { @@ -833,6 +891,9 @@ class Chat extends LitElement { chatId=${this.activeChatHeadUrl} .setOpenPrivateMessage=${(val) => this.setOpenPrivateMessage(val)} .setActiveChatHeadUrl=${(val)=> this.setActiveChatHeadUrl(val)} + balance=${this.balance} + loggedInUserName=${this.loggedInUserName} + loggedInUserAddress=${this.loggedInUserAddress} > ` @@ -897,7 +958,15 @@ class Chat extends LitElement { scrollToBottom() { const viewElement = this.shadowRoot.querySelector('chat-page').shadowRoot.querySelector('chat-scroller').shadowRoot.getElementById('viewElement') - viewElement.scroll({ top: viewElement.scrollHeight, left: 0, behavior: 'smooth' }) + + const chatScrollerElement = this.shadowRoot.querySelector('chat-page').shadowRoot.querySelector('chat-scroller') + if (chatScrollerElement && chatScrollerElement.disableAddingNewMessages) { + const chatPageElement = this.shadowRoot.querySelector('chat-page') + if(chatPageElement && chatPageElement.getLastestMessages) + chatPageElement.getLastestMessages() + } else { + viewElement.scroll({ top: viewElement.scrollHeight, left: 0, behavior: 'smooth' }) + } } showNewMessageBar() { diff --git a/plugins/plugins/core/name-registration/name-registration.src.js b/plugins/plugins/core/name-registration/name-registration.src.js index fd7d80b4..ecd51d15 100644 --- a/plugins/plugins/core/name-registration/name-registration.src.js +++ b/plugins/plugins/core/name-registration/name-registration.src.js @@ -21,6 +21,7 @@ import '@vaadin/icon' import '@vaadin/icons' import '@vaadin/grid' import '@vaadin/text-field' +import { warningModal } from '../../utils/warning-modal.js' const parentEpml = new Epml({ type: 'WINDOW', source: window.parent }) @@ -753,7 +754,16 @@ class NameRegistration extends LitElement { return html` this.openUpdateNameDialog(nameObj)}>update ${translate("publishpage.pchange2")} ${translate("login.name")}` } - openUpdateNameDialog(nameObj) { + async openUpdateNameDialog(nameObj) { + const res = await warningModal.showModalAndWaitPublish( + { + message: get('registernamepage.nchange48') + } + ); + if (res.action !== 'accept'){ + this.closeUpdateNameDialog() + return + } this.toUpdateName = '' this.shadowRoot.getElementById("oldNameInput").value = '' this.shadowRoot.getElementById("newNameInput").value = '' diff --git a/plugins/plugins/core/q-app/q-apps.src.js b/plugins/plugins/core/q-app/q-apps.src.js index 37b502e2..7a2b0792 100644 --- a/plugins/plugins/core/q-app/q-apps.src.js +++ b/plugins/plugins/core/q-app/q-apps.src.js @@ -4,6 +4,7 @@ import { Epml } from '../../../epml.js' import { use, get, translate, translateUnsafeHTML, registerTranslateConfig } from 'lit-translate' import { columnBodyRenderer, gridRowDetailsRenderer } from '@vaadin/grid/lit.js' import isElectron from 'is-electron' +import '@polymer/paper-spinner/paper-spinner-lite.js' registerTranslateConfig({ loader: lang => fetch(`/language/${lang}.json`).then(res => res.json()) @@ -42,7 +43,9 @@ class QApps extends LitElement { blockedResources: { type: Array }, textStatus: { type: String }, textProgress: { type: String }, - theme: { type: String, reflect: true } + theme: { type: String, reflect: true }, + hasInitiallyFetched: {type:Boolean}, + isLoading: {type: Boolean} } } @@ -369,6 +372,7 @@ class QApps extends LitElement { this.blockedNames = [] this.relayMode = null this.isLoading = false + this.hasInitiallyFetched = false this.btnDisabled = false this.searchName = '' this.searchResources = [] @@ -421,13 +425,14 @@ class QApps extends LitElement {
    - ${this.pageRes == null ? html` - Loading... + ${this.isLoading ? html` +
    ` : ''} - ${this.isEmptyArray(this.pageRes) ? html` + ${this.isEmptyArray(this.pageRes) && this.hasInitiallyFetched ? html` ${translate("appspage.schange10")} ` : ''} ${this.renderRelayModeText()}
    +
    @@ -738,11 +743,16 @@ class QApps extends LitElement { const myNode = window.parent.reduxStore.getState().app.nodeConfig.knownNodes[window.parent.reduxStore.getState().app.nodeConfig.node] const nodeUrl = myNode.protocol + '://' + myNode.domain + ':' + myNode.port let jsonOffsetUrl = `${nodeUrl}/arbitrary/resources?service=APP&default=true&limit=20&offset=${offset}&reverse=false&includestatus=true&includemetadata=true&excludeblocked=true` - + this.isLoading = true + const jsonOffsetRes = await fetch(jsonOffsetUrl) const jsonOffsetData = await jsonOffsetRes.json() this.pageRes = jsonOffsetData + if(!this.hasInitiallyFetched){ + this.hasInitiallyFetched = true + } + this.isLoading = false } async updateItemsFromPage(page) { diff --git a/plugins/plugins/core/qdn/publish/publish.src.js b/plugins/plugins/core/qdn/publish/publish.src.js index 37710534..fd720190 100644 --- a/plugins/plugins/core/qdn/publish/publish.src.js +++ b/plugins/plugins/core/qdn/publish/publish.src.js @@ -276,7 +276,7 @@ class PublishData extends LitElement {

    ${this.categories.map((c, index) => html` - ${c.name} + ${c.name} `)}

    @@ -300,7 +300,9 @@ class PublishData extends LitElement {

    ${this.successMessage}

    ${this.loading ? html` ` : ''}
    - this.shadowRoot.querySelector('#publishWithFeeDialog').close()}> ${translate("appspage.schange40")} + { + this.doPublish(e, true, false)} + }> ${translate("appspage.schange40")} { this.doPublish(e, false, true) }}> ${translate("publishpage.pchange11")} diff --git a/plugins/plugins/utils/id-generation.js b/plugins/plugins/utils/id-generation.js new file mode 100644 index 00000000..88323156 --- /dev/null +++ b/plugins/plugins/utils/id-generation.js @@ -0,0 +1,14 @@ +export function simpleHash(str) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = (hash << 5) - hash + str.charCodeAt(i); + hash = hash & hash; // Convert to 32bit integer + } + return hash.toString(); +} + +export function generateIdFromAddresses(address1, address2) { + // Sort addresses lexicographically and concatenate + const sortedAddresses = [address1, address2].sort().join(''); + return simpleHash(sortedAddresses); +} \ No newline at end of file diff --git a/plugins/plugins/utils/queue.js b/plugins/plugins/utils/queue.js new file mode 100644 index 00000000..00a235e6 --- /dev/null +++ b/plugins/plugins/utils/queue.js @@ -0,0 +1,71 @@ +export class RequestQueue { + constructor(maxConcurrent = 5) { + this.queue = []; + this.maxConcurrent = maxConcurrent; + this.currentConcurrent = 0; + } + + push(request) { + return new Promise((resolve, reject) => { + this.queue.push({ + request, + resolve, + reject, + }); + this.checkQueue(); + }); + } + + checkQueue() { + if (this.queue.length === 0 || this.currentConcurrent >= this.maxConcurrent) return; + + const { request, resolve, reject } = this.queue.shift(); + this.currentConcurrent++; + + request() + .then(resolve) + .catch(reject) + .finally(() => { + this.currentConcurrent--; + this.checkQueue(); + }); + } +} + +export class RequestQueueWithPromise { + constructor(maxConcurrent = 5) { + this.queue = []; + this.maxConcurrent = maxConcurrent; + this.currentlyProcessing = 0; + } + + // Add a request to the queue and return a promise + enqueue(request) { + return new Promise((resolve, reject) => { + // Push the request and its resolve and reject callbacks to the queue + this.queue.push({ request, resolve, reject }); + this.process(); + }); + } + + // Process requests in the queue + async process() { + while (this.queue.length > 0 && this.currentlyProcessing < this.maxConcurrent) { + this.currentlyProcessing++; + const { request, resolve, reject } = this.queue.shift(); + try { + const response = await request(); + resolve(response); + } catch (error) { + reject(error); + } finally { + this.currentlyProcessing--; + this.process(); + } + } + } + } + + + + diff --git a/plugins/plugins/utils/replace-messages-edited.js b/plugins/plugins/utils/replace-messages-edited.js index ad815fe8..129a4a8b 100644 --- a/plugins/plugins/utils/replace-messages-edited.js +++ b/plugins/plugins/utils/replace-messages-edited.js @@ -1,117 +1,251 @@ -export const replaceMessagesEdited = async ({ - decodedMessages, - parentEpml, - isReceipient, - decodeMessageFunc, - _publicKey -}) => { - const findNewMessages = decodedMessages.map(async (msg) => { - let msgItem = msg - try { - let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` - if (!isReceipient) { - msgQuery = `&txGroupId=${msg.txGroupId}` - } - const response = await parentEpml.request("apiCall", { - type: "api", - url: `/chat/messages?chatreference=${msg.signature}&reverse=true${msgQuery}&limit=1&sender=${msg.sender}&encoding=BASE64`, - }) +// export const replaceMessagesEdited = async ({ +// decodedMessages, +// parentEpml, +// isReceipient, +// decodeMessageFunc, +// _publicKey, +// addToUpdateMessageHashmap +// }) => { +// const findNewMessages = decodedMessages.map(async (msg) => { +// let msgItem = null +// try { +// let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` +// if (!isReceipient) { +// msgQuery = `&txGroupId=${msg.txGroupId}` +// } +// const response = await parentEpml.request("apiCall", { +// type: "api", +// url: `/chat/messages?chatreference=${msg.signature}&reverse=true${msgQuery}&limit=1&sender=${msg.sender}&encoding=BASE64`, +// }) - if (response && Array.isArray(response) && response.length !== 0) { - let responseItem = { ...response[0] } - const decodeResponseItem = decodeMessageFunc(responseItem, isReceipient, _publicKey) - delete decodeResponseItem.timestamp +// if (response && Array.isArray(response) && response.length !== 0) { +// let responseItem = { ...response[0] } +// const decodeResponseItem = decodeMessageFunc(responseItem, isReceipient, _publicKey) +// delete decodeResponseItem.timestamp - msgItem = { - ...msg, - ...decodeResponseItem, - senderName: msg.senderName, - sender: msg.sender, - editedTimestamp: response[0].timestamp, - } - } - } catch (error) { - } +// msgItem = { +// ...msg, +// ...decodeResponseItem, +// senderName: msg.senderName, +// sender: msg.sender, +// editedTimestamp: response[0].timestamp, +// originalSignature: msg.signature +// } +// } +// } catch (error) { +// } - return msgItem - }) - const updateMessages = await Promise.all(findNewMessages) - const findNewMessages2 = updateMessages.map(async (msg) => { - let parsedMessageObj = msg - try { - parsedMessageObj = JSON.parse(msg.decodedMessage) - } catch (error) { - return msg - } - let msgItem = msg - try { - let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` - if (!isReceipient) { - msgQuery = `&txGroupId=${msg.txGroupId}` - } - if (parsedMessageObj.repliedTo) { - let originalReply - if(+parsedMessageObj.version > 2){ - originalReply = await parentEpml.request("apiCall", { - type: "api", - url: `/chat/message/${parsedMessageObj.repliedTo}?encoding=BASE64`, - }) - } - if(+parsedMessageObj.version < 3){ - originalReply = await parentEpml.request("apiCall", { - type: "api", - url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&encoding=BASE64`, - }) - } +// return msgItem +// }) +// const updateMessages = await Promise.all(findNewMessages) +// const filterOutNull = updateMessages.filter((item)=> item !== 'null' && item !== null) + +// const findNewMessages2 = filterOutNull.map(async (msg) => { +// let parsedMessageObj = msg +// try { +// parsedMessageObj = JSON.parse(msg.decodedMessage) +// } catch (error) { +// return msg +// } +// let msgItem = msg +// try { +// let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}` +// if (!isReceipient) { +// msgQuery = `&txGroupId=${msg.txGroupId}` +// } +// if (parsedMessageObj.repliedTo) { +// let originalReply +// if(+parsedMessageObj.version > 2){ +// originalReply = await parentEpml.request("apiCall", { +// type: "api", +// url: `/chat/message/${parsedMessageObj.repliedTo}?encoding=BASE64`, +// }) +// } +// if(+parsedMessageObj.version < 3){ +// originalReply = await parentEpml.request("apiCall", { +// type: "api", +// url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&encoding=BASE64`, +// }) +// } - const originalReplyMessage = originalReply.timestamp ? originalReply : originalReply.length !== 0 ? originalReply[0] : null +// const originalReplyMessage = originalReply.timestamp ? originalReply : originalReply.length !== 0 ? originalReply[0] : null - const response = await parentEpml.request("apiCall", { +// const response = await parentEpml.request("apiCall", { +// type: "api", +// url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&limit=1&sender=${originalReplyMessage.sender}&encoding=BASE64`, +// }) + +// if ( +// originalReplyMessage && +// response && +// Array.isArray(response) && +// response.length !== 0 +// ) { +// const decodeOriginalReply = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey) + +// const decodeUpdatedReply = decodeMessageFunc(response[0], isReceipient, _publicKey) +// const formattedRepliedToData = { +// ...decodeUpdatedReply, +// senderName: decodeOriginalReply.senderName, +// sender: decodeOriginalReply.sender, +// } +// msgItem = { +// ...msg, +// repliedToData: formattedRepliedToData, +// } +// } else { + + +// if ( +// originalReplyMessage +// ) { + +// msgItem = { +// ...msg, +// repliedToData: decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey), +// } +// } +// } +// } +// } catch (error) { +// } + +// return msgItem +// }) +// const updateMessages2 = await Promise.all(findNewMessages2) +// console.log({updateMessages2}) +// updateMessages2.forEach((item)=> { +// addToUpdateMessageHashmap(item.originalSignature, item) +// }) +// return updateMessages2 +// } + + +export const replaceMessagesEdited = async ({ + decodedMessages, + parentEpml, + isReceipient, + decodeMessageFunc, + _publicKey, + addToUpdateMessageHashmap +}) => { + const MAX_CONCURRENT_REQUESTS = 5; // Maximum number of concurrent requests + + const executeWithConcurrencyLimit = async (array, asyncFn) => { + const results = []; + const concurrencyPool = []; + + for (const item of array) { + const promise = asyncFn(item); + concurrencyPool.push(promise); + + if (concurrencyPool.length >= MAX_CONCURRENT_REQUESTS) { + results.push(...await Promise.all(concurrencyPool)); + concurrencyPool.length = 0; // Clear the concurrency pool + } + } + + if (concurrencyPool.length > 0) { + results.push(...await Promise.all(concurrencyPool)); + } + + return results; + }; + + const findUpdatedMessage = async (msg) => { + let msgItem = { ...msg }; + + try { + let msgQuery = `&involving=${msg.recipient}&involving=${msg.sender}`; + if (!isReceipient) { + msgQuery = `&txGroupId=${msg.txGroupId}`; + } + + // Find new messages first + const newMsgResponse = await parentEpml.request("apiCall", { + type: "api", + url: `/chat/messages?chatreference=${msg.signature}&reverse=true${msgQuery}&limit=1&sender=${msg.sender}&encoding=BASE64`, + }); + + if (Array.isArray(newMsgResponse) && newMsgResponse.length > 0) { + const decodeResponseItem = decodeMessageFunc(newMsgResponse[0], isReceipient, _publicKey); + delete decodeResponseItem.timestamp; + + msgItem = { + ...msgItem, + ...decodeResponseItem, + senderName: msg.senderName, + sender: msg.sender, + editedTimestamp: newMsgResponse[0].timestamp, + originalSignature: msg.signature + }; + } + + // Then check and find replies in the same iteration + let parsedMessageObj; + try { + parsedMessageObj = JSON.parse(msg.decodedMessage); + } catch (error) { + // If parsing fails, return the msgItem as is + return msgItem; + } + + if (parsedMessageObj.repliedTo) { + let originalReply; + if(+parsedMessageObj.version > 2){ + originalReply = await parentEpml.request("apiCall", { + type: "api", + url: `/chat/message/${parsedMessageObj.repliedTo}?encoding=BASE64`, + }); + } else { + originalReply = await parentEpml.request("apiCall", { + type: "api", + url: `/chat/messages?reference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&encoding=BASE64`, + }); + } + + const originalReplyMessage = originalReply.timestamp ? originalReply : originalReply.length > 0 ? originalReply[0] : null; + + const replyResponse = await parentEpml.request("apiCall", { type: "api", url: `/chat/messages?chatreference=${parsedMessageObj.repliedTo}&reverse=true${msgQuery}&limit=1&sender=${originalReplyMessage.sender}&encoding=BASE64`, - }) - + }); + if ( originalReplyMessage && - response && - Array.isArray(response) && - response.length !== 0 + Array.isArray(replyResponse) && + replyResponse.length !== 0 ) { - const decodeOriginalReply = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey) - - const decodeUpdatedReply = decodeMessageFunc(response[0], isReceipient, _publicKey) - const formattedRepliedToData = { + const decodeOriginalReply = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey); + const decodeUpdatedReply = decodeMessageFunc(replyResponse[0], isReceipient, _publicKey); + + msgItem.repliedToData = { ...decodeUpdatedReply, senderName: decodeOriginalReply.senderName, sender: decodeOriginalReply.sender, - } - msgItem = { - ...msg, - repliedToData: formattedRepliedToData, - } - } else { - - - if ( - originalReplyMessage - ) { - - msgItem = { - ...msg, - repliedToData: decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey), - } - } + }; + } else if (originalReplyMessage) { + msgItem.repliedToData = decodeMessageFunc(originalReplyMessage, isReceipient, _publicKey); } } + } catch (error) { + // Handle or log the error gracefully + console.error(error); } - - return msgItem - }) - const updateMessages2 = await Promise.all(findNewMessages2) - - return updateMessages2 -} + + return msgItem; + }; + + const sortedMessages = decodedMessages.sort((a, b) => b.timestamp - a.timestamp); + + // Execute the functions with concurrency limit + const updatedMessages = await executeWithConcurrencyLimit(sortedMessages, findUpdatedMessage); + addToUpdateMessageHashmap(updatedMessages); + + return updatedMessages; + +}; diff --git a/plugins/plugins/utils/warning-modal.js b/plugins/plugins/utils/warning-modal.js new file mode 100644 index 00000000..7bda31cd --- /dev/null +++ b/plugins/plugins/utils/warning-modal.js @@ -0,0 +1,252 @@ +import { get } from 'lit-translate'; + +export class WarningModal { + constructor() { + this.initializeStyles(); + } + + + async showModalAndWaitPublish(data) { + return new Promise((resolve) => { + const modal = this.createModal(data); + document.body.appendChild(modal); + this.addModalEventListeners(modal, resolve); + }); + } + + createModal(data) { + const modal = document.createElement('div'); + modal.id = "backdrop"; + modal.classList.add("backdrop"); + modal.innerHTML = ` + + `; + return modal; + } + + addModalEventListeners(modal, resolve) { + // Event listener for the 'OK' button + const okButton = modal.querySelector('#ok-button'); + okButton.addEventListener('click', () => { + const userData = { isWithFee: true }; + if (modal.parentNode === document.body) { + document.body.removeChild(modal); + } + resolve({ action: 'accept', userData }); + }); + + // Prevent modal content from closing the modal + const modalContent = modal.querySelector('.modal-content'); + modalContent.addEventListener('click', e => { + e.stopPropagation(); + }); + + // Event listeners for backdrop and 'Cancel' button + const backdropClick = document.getElementById('backdrop'); + backdropClick.addEventListener('click', () => { + if (modal.parentNode === document.body) { + document.body.removeChild(modal); + } + resolve({ action: 'reject' }); + }); + + const cancelButton = modal.querySelector('#cancel-button'); + cancelButton.addEventListener('click', () => { + if (modal.parentNode === document.body) { + document.body.removeChild(modal); + } + resolve({ action: 'reject' }); + }); + } + + initializeStyles() { + const styles = ` + * { + --mdc-theme-primary: rgb(3, 169, 244); + --mdc-theme-secondary: var(--mdc-theme-primary); + --paper-input-container-focus-color: var(--mdc-theme-primary); + --mdc-checkbox-unchecked-color: var(--black); + --mdc-theme-on-surface: var(--black); + --mdc-checkbox-disabled-color: var(--black); + --mdc-checkbox-ink-color: var(--black); + } + + .backdrop { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgb(186 186 186 / 26%); + overflow: hidden; + animation: backdrop_blur cubic-bezier(0.22, 1, 0.36, 1) 0.1s forwards; + z-index: 1000000; + } + + @keyframes backdrop_blur { + 0% { + backdrop-filter: blur(0px); + background: transparent; + } + 100% { + backdrop-filter: blur(5px); + background: rgb(186 186 186 / 26%); + } + } + + @keyframes modal_transition { + 0% { + visibility: hidden; + opacity: 0; + } + 100% { + visibility: visible; + opacity: 1; + } + } + + .modal { + position: relative; + display: flex; + justify-content: center; + align-items: center; + width: 100%; + height: 100%; + animation: 0.1s cubic-bezier(0.22, 1, 0.36, 1) 0s 1 normal forwards running modal_transition; + z-index: 1000001; + } + + @keyframes modal_transition { + 0% { + visibility: hidden; + opacity: 0; + } + 100% { + visibility: visible; + opacity: 1; + } + } + + .modal-content { + background-color: var(--white); + border-radius: 10px; + padding: 20px; + box-shadow: 0 0 10px rgba(0, 0, 0, 0.3); + max-width: 650px; + min-width: 300px; + display: flex; + flex-direction: column; + justify-content: space-between; + } + + .modal-body { + padding: 25px; + } + + .modal-subcontainer { + color: var(--black); + display: flex; + flex-direction: column; + align-items: flex-start; + gap: 15px; + } + + .modal-subcontainer-error { + color: var(--black); + display: flex; + flex-direction: column; + align-items: center; + gap: 15px; + } + + .modal-paragraph-error { + font-family: Roboto, sans-serif; + font-size: 20px; + letter-spacing: 0.3px; + font-weight: 700; + color: var(--black); + margin: 0; + } + + .modal-paragraph { + font-family: Roboto, sans-serif; + font-size: 18px; + letter-spacing: 0.3px; + font-weight: 300; + color: var(--black); + margin: 0; + word-wrap: break-word; + overflow-wrap: break-word; + } + + .capitalize-first { + text-transform: capitalize; + } + + .checkbox-row { + display: flex; + align-items: center; + font-family: Montserrat, sans-serif; + font-weight: 600; + color: var(--black); + } + + .modal-buttons { + display: flex; + justify-content: space-between; + margin-top: 20px; + } + + .modal-buttons button { + background-color: #4caf50; + border: none; + color: #fff; + padding: 10px 20px; + border-radius: 5px; + cursor: pointer; + transition: background-color 0.2s; + } + + .modal-buttons button:hover { + background-color: #3e8e41; + } + + #cancel-button { + background-color: #f44336; + } + + #cancel-button:hover { + background-color: #d32f2f; + } + `; + + const styleSheet = new CSSStyleSheet(); + styleSheet.replaceSync(styles); + + document.adoptedStyleSheets = [styleSheet]; + } + + + static getInstance() { + if (!WarningModal.instance) { + WarningModal.instance = new WarningModal(); + } + return WarningModal.instance; + } +} + +export const warningModal = WarningModal.getInstance(); \ No newline at end of file