${this.soldBTCTemplate()}
diff --git a/qortal-ui-core/src/notifications/notification-actions/new-message.js b/qortal-ui-core/src/notifications/notification-actions/new-message.js
index faaaad32..f430fdd2 100644
--- a/qortal-ui-core/src/notifications/notification-actions/new-message.js
+++ b/qortal-ui-core/src/notifications/notification-actions/new-message.js
@@ -2,7 +2,6 @@ import { store } from '../../store.js'
import { doPageUrl } from '../../redux/app/app-actions.js'
export const newMessage = (data) => {
-
const alert = playSound(data.sound)
// Should I show notification ?
@@ -18,7 +17,7 @@ export const newMessage = (data) => {
}
notify.onclick = (e) => {
- const pageUrl = `/app/q-chat/${data.req.url}`
+ const pageUrl = `/app/q-chat/?chat=${data.req.url}`
store.dispatch(doPageUrl(pageUrl))
}
} else {
@@ -26,7 +25,7 @@ export const newMessage = (data) => {
const notify = new Notification(data.title, data.options)
notify.onclick = (e) => {
- const pageUrl = `/app/q-chat/${data.req.url}`
+ const pageUrl = `/app/q-chat/?chat=${data.req.url}`
store.dispatch(doPageUrl(pageUrl))
}
}
diff --git a/qortal-ui-core/src/plugins/streams.js b/qortal-ui-core/src/plugins/streams.js
index ad6696d1..9cb6c3f7 100644
--- a/qortal-ui-core/src/plugins/streams.js
+++ b/qortal-ui-core/src/plugins/streams.js
@@ -9,6 +9,7 @@ const CHAT_HEADS_STREAM_NAME = 'chat_heads'
const NODE_CONFIG_STREAM_NAME = 'node_config'
const COPY_MENU_SWITCH = 'copy_menu_switch'
const FRAME_PASTE_MENU_SWITCH = 'frame_paste_menu_switch'
+const CHAT_LAST_SEEN = 'chat_last_seen'
export const loggedInStream = new EpmlStream(LOGIN_STREAM_NAME, () => store.getState().app.loggedIn)
export const configStream = new EpmlStream(CONFIG_STREAM_NAME, () => store.getState().config)
@@ -18,6 +19,7 @@ export const chatHeadsStateStream = new EpmlStream(CHAT_HEADS_STREAM_NAME, () =>
export const nodeConfigStream = new EpmlStream(NODE_CONFIG_STREAM_NAME, () => store.getState().app.nodeConfig)
export const copyMenuSwitchStream = new EpmlStream(COPY_MENU_SWITCH, () => store.getState().app.copyMenuSwitch)
export const framePasteMenuSwitchStream = new EpmlStream(FRAME_PASTE_MENU_SWITCH, () => store.getState().app.framePasteMenuSwitch)
+export const chatLastSeenStream = new EpmlStream(CHAT_LAST_SEEN, () => store.getState().app.chatLastSeen)
let oldState = {
@@ -46,6 +48,9 @@ store.subscribe(() => {
if (oldState.app.framePasteMenuSwitch !== state.app.framePasteMenuSwitch) {
framePasteMenuSwitchStream.emit(state.app.framePasteMenuSwitch)
}
+ if (oldState.app.chatLastSeen !== state.app.chatLastSeen) {
+ chatLastSeenStream.emit(state.app.chatLastSeen)
+ }
if (oldState.app.selectedAddress !== state.app.selectedAddress) {
selectedAddressStream.emit({
diff --git a/qortal-ui-core/src/redux/app/actions/app-core.js b/qortal-ui-core/src/redux/app/actions/app-core.js
index e96b11b4..894f2c2e 100644
--- a/qortal-ui-core/src/redux/app/actions/app-core.js
+++ b/qortal-ui-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, COPY_MENU_SWITCH, PASTE_MENU_SWITCH, FRAME_PASTE_MENU_SWITCH } from '../app-action-types.js'
+import { UPDATE_BLOCK_INFO, UPDATE_NODE_STATUS, UPDATE_NODE_INFO, CHAT_HEADS, ACCOUNT_INFO, COPY_MENU_SWITCH, PASTE_MENU_SWITCH, FRAME_PASTE_MENU_SWITCH, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN } from '../app-action-types.js'
export const doUpdateBlockInfo = (blockObj) => {
return (dispatch, getState) => {
@@ -105,3 +105,31 @@ const framePasteMenuSwitch = (payload) => {
payload
}
}
+
+export const addAutoLoadImageChat = (payload) => {
+ return {
+ type: ADD_AUTO_LOAD_IMAGES_CHAT,
+ payload
+ }
+}
+
+export const removeAutoLoadImageChat = (payload) => {
+ return {
+ type: REMOVE_AUTO_LOAD_IMAGES_CHAT,
+ payload
+ }
+}
+
+export const setChatLastSeen = (payload) => {
+ return {
+ type: SET_CHAT_LAST_SEEN,
+ payload
+ }
+}
+export const addChatLastSeen = (payload) => {
+ return {
+ type: ADD_CHAT_LAST_SEEN,
+ payload
+ }
+}
+
diff --git a/qortal-ui-core/src/redux/app/app-action-types.js b/qortal-ui-core/src/redux/app/app-action-types.js
index 00f4c54d..518d4cd7 100644
--- a/qortal-ui-core/src/redux/app/app-action-types.js
+++ b/qortal-ui-core/src/redux/app/app-action-types.js
@@ -20,3 +20,7 @@ export const ADD_NEW_PLUGIN_URL = 'ADD_NEW_PLUGIN_URL'
export const COPY_MENU_SWITCH = 'COPY_MENU_SWITCH'
export const PASTE_MENU_SWITCH = 'PASTE_MENU_SWITCH'
export const FRAME_PASTE_MENU_SWITCH = 'FRAME_PASTE_MENU_SWITCH'
+export const ADD_AUTO_LOAD_IMAGES_CHAT = 'ADD_AUTO_LOAD_IMAGES_CHAT'
+export const REMOVE_AUTO_LOAD_IMAGES_CHAT = 'REMOVE_AUTO_LOAD_IMAGES_CHAT'
+export const SET_CHAT_LAST_SEEN = 'SET_CHAT_LAST_SEEN'
+export const ADD_CHAT_LAST_SEEN = 'ADD_CHAT_LAST_SEEN'
diff --git a/qortal-ui-core/src/redux/app/app-reducer.js b/qortal-ui-core/src/redux/app/app-reducer.js
index 0fa9b67a..68a48675 100644
--- a/qortal-ui-core/src/redux/app/app-reducer.js
+++ b/qortal-ui-core/src/redux/app/app-reducer.js
@@ -1,8 +1,14 @@
// Loading state, login state, isNavDrawOpen state etc. None of this needs to be saved to localstorage.
-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, COPY_MENU_SWITCH, PASTE_MENU_SWITCH, FRAME_PASTE_MENU_SWITCH } from './app-action-types.js'
+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, COPY_MENU_SWITCH, PASTE_MENU_SWITCH, FRAME_PASTE_MENU_SWITCH, ADD_AUTO_LOAD_IMAGES_CHAT, REMOVE_AUTO_LOAD_IMAGES_CHAT, SET_CHAT_LAST_SEEN, ADD_CHAT_LAST_SEEN } 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 localForage from "localforage";
+const chatLastSeen = localForage.createInstance({
+ name: "chat-last-seen",
+});
+
const INITIAL_STATE = {
loggedIn: false,
@@ -42,7 +48,9 @@ const INITIAL_STATE = {
framePasteMenuSwitch: {
isOpen: false,
elementId: ''
- }
+ },
+ autoLoadImageChats: loadStateFromLocalStorage('autoLoadImageChats') || [],
+ chatLastSeen: []
}
export default (state = INITIAL_STATE, action) => {
@@ -146,6 +154,60 @@ export default (state = INITIAL_STATE, action) => {
...state,
framePasteMenuSwitch: action.payload
}
+ case ADD_AUTO_LOAD_IMAGES_CHAT: {
+ const findChat = state.autoLoadImageChats.findIndex((chat)=> chat === action.payload)
+ console.log({findChat})
+ if(findChat !== -1) return state
+ const updatedState = [...state.autoLoadImageChats, action.payload]
+
+ saveStateToLocalStorage('autoLoadImageChats', updatedState)
+ return {
+ ...state,
+ autoLoadImageChats: updatedState
+ }
+ }
+
+ case REMOVE_AUTO_LOAD_IMAGES_CHAT: {
+ const updatedState = state.autoLoadImageChats.filter((chat)=> chat !== action.payload)
+ saveStateToLocalStorage('autoLoadImageChats', updatedState)
+ return {
+ ...state,
+ autoLoadImageChats: updatedState
+ }
+ }
+ case SET_CHAT_LAST_SEEN: {
+ return {
+ ...state,
+ chatLastSeen: action.payload
+ }
+ }
+ case ADD_CHAT_LAST_SEEN: {
+ const chatId = action.payload.key
+ const timestamp = action.payload.timestamp
+ if(!chatId || !timestamp) return state
+ let newChatLastSeen = [...state.chatLastSeen]
+ const findChatIndex = state.chatLastSeen.findIndex((chat)=> chat.key === chatId)
+ if(findChatIndex !== -1){
+
+ newChatLastSeen[findChatIndex] = {
+ key: chatId,
+ timestamp,
+ }
+ }
+ if(findChatIndex === -1){
+
+ newChatLastSeen = [...newChatLastSeen, {
+ key: chatId,
+ timestamp,
+ }]
+ }
+ chatLastSeen.setItem(chatId, timestamp)
+ return {
+ ...state,
+ chatLastSeen: newChatLastSeen
+ }
+ }
+
default:
return state
}
diff --git a/qortal-ui-core/src/styles/switch-theme.css b/qortal-ui-core/src/styles/switch-theme.css
index 564b0a35..2a700a48 100644
--- a/qortal-ui-core/src/styles/switch-theme.css
+++ b/qortal-ui-core/src/styles/switch-theme.css
@@ -9,7 +9,8 @@ html {
--copybutton: #707584;
--chat-group: #080808;
--chat-bubble: #9f9f9f0a;
- --chat-bubble-bg: #f3f3f3;
+ --chat-bubble-bg: #e6e6e6;
+ --chat-bubble-myBg: #d1ddf2;
--chat-bubble-msg-color: #080808;
--reaction-bubble-outline: #6b6969;
--chat-menu-bg: #ffffff;
@@ -70,6 +71,7 @@ html[theme="dark"] {
--chat-group: #ffffff;
--chat-bubble: #9694941a;
--chat-bubble-bg: #2d3749;
+ --chat-bubble-myBg: #40444d;
--chat-bubble-msg-color: #ffffff;
--reaction-bubble-outline: #ffffff;
--chat-menu-bg: #32394c;
diff --git a/qortal-ui-crypto/api/constants.js b/qortal-ui-crypto/api/constants.js
index ae139416..11c0cbbf 100644
--- a/qortal-ui-crypto/api/constants.js
+++ b/qortal-ui-crypto/api/constants.js
@@ -159,7 +159,7 @@ const ADDRESS_VERSION = 58
const PROXY_URL = "/proxy/"
// Chat reference timestamp
-const CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP = 0
+const CHAT_REFERENCE_FEATURE_TRIGGER_TIMESTAMP = 1674316800000
// Used as a salt for all qora addresses. Salts used for storing your private keys in local storage will be randomly generated
const STATIC_SALT = new Uint8Array([54, 190, 201, 206, 65, 29, 123, 129, 147, 231, 180, 166, 171, 45, 95, 165, 78, 200, 208, 194, 44, 207, 221, 146, 45, 238, 68, 68, 69, 102, 62, 6])
diff --git a/qortal-ui-crypto/api/transactions/names/UpdateNameTransaction.js b/qortal-ui-crypto/api/transactions/names/UpdateNameTransaction.js
new file mode 100644
index 00000000..bfc31563
--- /dev/null
+++ b/qortal-ui-crypto/api/transactions/names/UpdateNameTransaction.js
@@ -0,0 +1,73 @@
+'use strict'
+import TransactionBase from '../TransactionBase.js'
+import { QORT_DECIMALS } from '../../constants.js'
+
+export default class UpdateNameTransaction extends TransactionBase {
+ constructor() {
+ super()
+ this.type = 4
+ }
+
+ render(html) {
+ return html`
+ ${this._dialogUpdateName1}
+
+ ${this.nameText}
+
+ ${this._dialogUpdateName2}
+
+ ${this.newNameText}
+
+ ${this._dialogUpdateName3}
+ `
+ }
+
+ set dialogUpdateName1(dialogUpdateName1) {
+ this._dialogUpdateName1 = dialogUpdateName1
+ }
+
+ set dialogUpdateName2(dialogUpdateName2) {
+ this._dialogUpdateName2 = dialogUpdateName2
+ }
+
+ set dialogUpdateName3(dialogUpdateName3) {
+ this._dialogUpdateName3 = dialogUpdateName3
+ }
+
+ set fee(fee) {
+ this._fee = fee * QORT_DECIMALS
+ this._feeBytes = this.constructor.utils.int64ToBytes(this._fee)
+ }
+
+ set name(name) {
+ this.nameText = name
+ this._nameBytes = this.constructor.utils.stringtoUTF8Array(name)
+ this._nameLength = this.constructor.utils.int32ToBytes(this._nameBytes.length)
+ }
+
+ set newName(newName) {
+ this.newNameText = newName
+ this._newNameBytes = this.constructor.utils.stringtoUTF8Array(newName)
+ this._newNameLength = this.constructor.utils.int32ToBytes(this._newNameBytes.length)
+ }
+
+ set newData(newData) {
+ this.newDataText = newData.length === 0 ? "Registered Name on the Qortal Chain" : newData
+ this._newDataBytes = this.constructor.utils.stringtoUTF8Array(this.newDataText)
+ this._newDataLength = this.constructor.utils.int32ToBytes(this._newDataBytes.length)
+ }
+
+ get params() {
+ const params = super.params
+ params.push(
+ this._nameLength,
+ this._nameBytes,
+ this._newNameLength,
+ this._newNameBytes,
+ this._newDataLength,
+ this._newDataBytes,
+ this._feeBytes
+ )
+ return params
+ }
+}
\ No newline at end of file
diff --git a/qortal-ui-crypto/api/transactions/transactions.js b/qortal-ui-crypto/api/transactions/transactions.js
index 16b7b09b..9fa87e99 100644
--- a/qortal-ui-crypto/api/transactions/transactions.js
+++ b/qortal-ui-crypto/api/transactions/transactions.js
@@ -1,5 +1,6 @@
import PaymentTransaction from './PaymentTransaction.js'
import RegisterNameTransaction from './names/RegisterNameTransaction.js'
+import UpdateNameTransaction from './names/UpdateNameTransaction.js'
import SellNameTransacion from './names/SellNameTransacion.js'
import CancelSellNameTransacion from './names/CancelSellNameTransacion.js'
import BuyNameTransacion from './names/BuyNameTransacion.js'
@@ -25,6 +26,7 @@ import TransferPrivsTransaction from './TransferPrivsTransaction.js'
export const transactionTypes = {
2: PaymentTransaction,
3: RegisterNameTransaction,
+ 4: UpdateNameTransaction,
5: SellNameTransacion,
6: CancelSellNameTransacion,
7: BuyNameTransacion,
diff --git a/qortal-ui-crypto/package.json b/qortal-ui-crypto/package.json
index 5d9362c0..cae726c6 100644
--- a/qortal-ui-crypto/package.json
+++ b/qortal-ui-crypto/package.json
@@ -1,6 +1,6 @@
{
"name": "qortal-ui-crypto",
- "version": "2.2.5",
+ "version": "3.1.0",
"description": "Qortal Project - decentralize the world - Data storage, communications, web hosting, decentralized trading, complete infrastructure for the future blockchain-based Internet",
"keywords": [
"QORT",
@@ -23,6 +23,6 @@
"lodash": "4.17.21"
},
"engines": {
- "node": ">=16.17.1"
+ "node": ">=18.12.1"
}
-}
+}
\ No newline at end of file
diff --git a/qortal-ui-plugins/package.json b/qortal-ui-plugins/package.json
index 198b0be6..6121f786 100644
--- a/qortal-ui-plugins/package.json
+++ b/qortal-ui-plugins/package.json
@@ -1,6 +1,6 @@
{
"name": "qortal-ui-plugins",
- "version": "3.0.0",
+ "version": "3.1.0",
"description": "Qortal Project - decentralize the world - Data storage, communications, web hosting, decentralized trading, complete infrastructure for the future blockchain-based Internet",
"keywords": [
"QORT",
@@ -20,27 +20,28 @@
"@lit-labs/motion": "1.0.3",
"@material/mwc-list": "0.27.0",
"@material/mwc-select": "0.27.0",
- "@tiptap/core": "2.0.0-beta.209",
- "@tiptap/extension-highlight": "2.0.0-beta.209",
- "@tiptap/extension-image": "2.0.0-beta.209",
- "@tiptap/extension-placeholder": "2.0.0-beta.209",
- "@tiptap/extension-underline": "2.0.0-beta.209",
- "@tiptap/html": "2.0.0-beta.209",
- "@tiptap/starter-kit": "2.0.0-beta.209",
+ "@tiptap/pm": "2.0.0-beta.217",
+ "@tiptap/core": "2.0.0-beta.217",
+ "@tiptap/extension-highlight": "2.0.0-beta.217",
+ "@tiptap/extension-image": "2.0.0-beta.217",
+ "@tiptap/extension-placeholder": "2.0.0-beta.217",
+ "@tiptap/extension-underline": "2.0.0-beta.217",
+ "@tiptap/html": "2.0.0-beta.217",
+ "@tiptap/starter-kit": "2.0.0-beta.217",
"asmcrypto.js": "2.3.2",
"compressorjs": "1.1.1",
"emoji-picker-js": "https://github.com/Qortal/emoji-picker-js",
"localforage": "1.10.0",
"prosemirror-commands": "1.5.0",
- "prosemirror-dropcursor": "1.6.1",
+ "prosemirror-dropcursor": "1.7.0",
"prosemirror-gapcursor": "1.3.1",
"prosemirror-history": "1.3.0",
- "prosemirror-keymap": "1.2.0",
- "prosemirror-model": "1.18.3",
+ "prosemirror-keymap": "1.2.1",
+ "prosemirror-model": "1.19.0",
"prosemirror-schema-list": "1.2.2",
"prosemirror-state": "1.4.2",
- "prosemirror-transform": "1.7.0",
- "prosemirror-view": "1.29.1",
+ "prosemirror-transform": "1.7.1",
+ "prosemirror-view": "1.30.1",
"short-unique-id": "4.4.4"
},
"devDependencies": {
@@ -48,12 +49,12 @@
"@material/mwc-button": "0.27.0",
"@material/mwc-checkbox": "0.27.0",
"@material/mwc-dialog": "0.27.0",
- "@material/mwc-fab": "0.27.0",
"@material/mwc-formfield": "0.27.0",
"@material/mwc-icon": "0.27.0",
"@material/mwc-icon-button": "0.27.0",
"@material/mwc-slider": "0.27.0",
"@material/mwc-snackbar": "0.27.0",
+ "@material/mwc-fab": "0.27.0",
"@material/mwc-tab": "0.27.0",
"@material/mwc-tab-bar": "0.27.0",
"@material/mwc-textfield": "0.27.0",
@@ -70,11 +71,12 @@
"@rollup/plugin-node-resolve": "15.0.1",
"@rollup/plugin-replace": "5.0.2",
"@rollup/plugin-terser": "0.4.0",
- "@vaadin/avatar": "23.3.6",
- "@vaadin/button": "23.3.6",
- "@vaadin/grid": "23.3.6",
- "@vaadin/icons": "23.3.6",
- "@vaadin/tooltip": "23.3.6",
+ "@vaadin/avatar": "23.3.7",
+ "@vaadin/button": "23.3.7",
+ "@vaadin/grid": "23.3.7",
+ "@vaadin/icons": "23.3.7",
+ "@vaadin/tooltip": "23.3.7",
+ "axios": "1.3.3",
"@zip.js/zip.js": "^2.6.62",
"epml": "0.3.3",
"file-saver": "2.0.5",
@@ -83,13 +85,13 @@
"lit": "2.6.1",
"lit-translate": "2.0.1",
"passive-events-support": "1.0.33",
- "rollup": "3.12.0",
+ "rollup": "3.15.0",
"rollup-plugin-node-globals": "1.4.0",
"rollup-plugin-progress": "1.1.2",
- "rollup-plugin-web-worker-loader": "1.6.1",
- "validator": "^13.7.0"
+ "rollup-plugin-web-worker-loader": "1.6.1"
+
},
"engines": {
- "node": ">=16.17.1"
+ "node": ">=18.12.1"
}
}
diff --git a/qortal-ui-plugins/plugins/core/components/ChatHead.js b/qortal-ui-plugins/plugins/core/components/ChatHead.js
index c28a583f..61a30fb2 100644
--- a/qortal-ui-plugins/plugins/core/components/ChatHead.js
+++ b/qortal-ui-plugins/plugins/core/components/ChatHead.js
@@ -1,11 +1,15 @@
import { LitElement, html, css } from 'lit'
import { render } from 'lit/html.js'
import { Epml } from '../../../epml.js'
+import localForage from "localforage";
+import { translate} from 'lit-translate';
import '@material/mwc-icon'
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
-
+const chatLastSeen = localForage.createInstance({
+ name: "chat-last-seen",
+});
class ChatHead extends LitElement {
static get properties() {
return {
@@ -15,7 +19,8 @@ class ChatHead extends LitElement {
iconName: { type: String },
activeChatHeadUrl: { type: String },
isImageLoaded: { type: Boolean },
- setActiveChatHeadUrl: {attribute: false}
+ setActiveChatHeadUrl: {attribute: false},
+ lastReadMessageTimestamp: {type: Number}
}
}
@@ -24,9 +29,13 @@ class ChatHead extends LitElement {
li {
width: 100%;
- padding: 7px 5px 7px 5px;
+ padding: 10px 5px 10px 5px;
cursor: pointer;
width: 100%;
+ box-sizing: border-box;
+ display: flex;
+ align-items: flex-start;
+
}
li:hover {
@@ -44,12 +53,21 @@ class ChatHead extends LitElement {
color: var(--chat-group);
}
- .about {
- margin-top: 8px;
- }
+
.about {
- padding-left: 8px;
+
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ margin: 0px;
+ }
+ .inner-container {
+ display: flex;
+ width: calc(100% - 45px);
+ flex-direction: column;
+ justify-content: center;
}
.status {
@@ -64,6 +82,13 @@ class ChatHead extends LitElement {
clear: both;
height: 0;
}
+
+ .name {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ width: 100%;
+ }
`
}
@@ -82,9 +107,11 @@ class ChatHead extends LitElement {
this.activeChatHeadUrl = ''
this.isImageLoaded = false
this.imageFetches = 0
+ this.lastReadMessageTimestamp = 0
+ this.loggedInAddress = window.parent.reduxStore.getState().app.selectedAddress.address
}
- createImage(imageUrl) {
+ createImage(imageUrl) {
const imageHTMLRes = new Image();
imageHTMLRes.src = imageUrl;
imageHTMLRes.style= "width:40px; height:40px; float: left; border-radius:50%";
@@ -99,7 +126,7 @@ class ChatHead extends LitElement {
setTimeout(() => {
this.imageFetches = this.imageFetches + 1;
imageHTMLRes.src = imageUrl;
- }, 500);
+ }, 750);
} else {
@@ -109,32 +136,61 @@ class ChatHead extends LitElement {
return imageHTMLRes;
}
+
+
render() {
let avatarImg = '';
let backupAvatarImg = ''
+ let isUnread = false
+
if(this.chatInfo.name){
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.chatInfo.name}/qortal_avatar?async=true&apiKey=${myNode.apiKey}`;
avatarImg= this.createImage(avatarUrl)
-
}
+ if(this.lastReadMessageTimestamp && this.chatInfo.timestamp){
+ if(this.lastReadMessageTimestamp < this.chatInfo.timestamp){
+ isUnread = true
+ }
+ }
+
+ if(this.activeChatHeadUrl === this.chatInfo.url){
+ isUnread = false
+ }
+
+ if(this.chatInfo.sender === this.loggedInAddress){
+ isUnread = false
+ }
return html`
this.getUrl(this.chatInfo.url)} class="clearfix ${this.activeChatHeadUrl === this.chatInfo.url ? 'active' : ''}">
${this.isImageLoaded ? html`${avatarImg}` : html`` }
${!this.isImageLoaded && !this.chatInfo.name && !this.chatInfo.groupName ? html`account_circle` : html`` }
- ${!this.isImageLoaded && this.chatInfo.name ? html`${this.chatInfo.name.charAt(0)}
`: ''}
- ${!this.isImageLoaded && this.chatInfo.groupName ? html`${this.chatInfo.groupName.charAt(0)}
`: ''}
+ ${!this.isImageLoaded && this.chatInfo.name ? html`${this.chatInfo.name.charAt(0)}
`: ''}
+ ${!this.isImageLoaded && this.chatInfo.groupName ? html`${this.chatInfo.groupName.charAt(0)}
`: ''}
+
-
${this.chatInfo.groupName ? this.chatInfo.groupName : this.chatInfo.name !== undefined ? this.chatInfo.name : this.chatInfo.address.substr(0, 15)} ${this.chatInfo.groupId !== undefined ? 'lock_open' : 'lock'}
+
${this.chatInfo.groupName ? this.chatInfo.groupName : this.chatInfo.name !== undefined ? this.chatInfo.name : this.chatInfo.address.substr(0, 15)} ${this.chatInfo.groupId !== undefined ? 'lock_open' : 'lock'}
+
+
+
+
+
+
${translate('chatpage.cchange90')}
+
+
+
+
+
`
}
- firstUpdated() {
+ async firstUpdated() {
let configLoaded = false
+ this.lastReadMessageTimestamp = await chatLastSeen.getItem(this.chatInfo.url) || 0
parentEpml.ready().then(() => {
parentEpml.subscribe('selected_address', async selectedAddress => {
this.selectedAddress = {}
@@ -142,6 +198,15 @@ class ChatHead extends LitElement {
if (!selectedAddress || Object.entries(selectedAddress).length === 0) return
this.selectedAddress = selectedAddress
})
+ parentEpml.subscribe('chat_last_seen', async chatList => {
+ const parsedChatList = JSON.parse(chatList)
+ const findChatSeen = parsedChatList.find(chat=> chat.key === this.chatInfo.url)
+
+ if(findChatSeen && this.lastReadMessageTimestamp !== findChatSeen.timestamp){
+ this.lastReadMessageTimestamp = findChatSeen.timestamp
+ this.requestUpdate()
+ }
+ })
parentEpml.subscribe('config', c => {
if (!configLoaded) {
configLoaded = true
@@ -156,7 +221,18 @@ class ChatHead extends LitElement {
if(changedProperties.has('activeChatHeadUrl')){
return true
}
+ if(changedProperties.has('lastReadMessageTimestamp')){
+ return true
+ }
if(changedProperties.has('chatInfo')){
+
+ const prevChatInfo = changedProperties.get('chatInfo')
+
+ if(prevChatInfo.address !== this.chatInfo.address){
+
+ this.isImageLoaded = false
+ this.requestUpdate()
+ }
return true
}
diff --git a/qortal-ui-plugins/plugins/core/components/ChatPage.js b/qortal-ui-plugins/plugins/core/components/ChatPage.js
index 25561083..e3d53560 100644
--- a/qortal-ui-plugins/plugins/core/components/ChatPage.js
+++ b/qortal-ui-plugins/plugins/core/components/ChatPage.js
@@ -12,7 +12,8 @@ import { Editor, Extension } from '@tiptap/core'
import * as zip from "@zip.js/zip.js";
import { saveAs } from 'file-saver';
import './ChatGifs/ChatGifs.js';
-// import localForage from "localforage";
+
+import localForage from "localforage";
registerTranslateConfig({
loader: lang => fetch(`/language/${lang}.json`).then(res => res.json())
});
@@ -41,9 +42,13 @@ import { replaceMessagesEdited } from '../../utils/replace-messages-edited.js';
import { publishData } from '../../utils/publish-image.js';
import { EmojiPicker } from 'emoji-picker-js';
import WebWorker from 'web-worker:./computePowWorker.js';
-import WebWorkerImage from 'web-worker:./computePowWorkerImage.js';
+import WebWorkerFile from 'web-worker:./computePowWorkerFile.js';
import '@polymer/paper-dialog/paper-dialog.js'
+const chatLastSeen = localForage.createInstance({
+ name: "chat-last-seen",
+});
+
const parentEpml = new Epml({ type: 'WINDOW', source: window.parent })
class ChatPage extends LitElement {
@@ -75,7 +80,11 @@ class ChatPage extends LitElement {
editedMessageObj: { type: Object },
iframeHeight: { type: Number },
imageFile: { type: Object },
+ attachment: { type: Object },
isUploadingImage: { type: Boolean },
+ isDeletingImage: { type: Boolean },
+ isUploadingAttachment: { type: Boolean },
+ isDeletingAttachment: { type: Boolean },
userLanguage: { type: String },
lastMessageRefVisible: { type: Boolean },
isLoadingOldMessages: { type: Boolean },
@@ -92,7 +101,7 @@ class ChatPage extends LitElement {
userFound: { type: Array },
userFoundModalOpen: { type: Boolean },
webWorker: { type: Object },
- webWorkerImage: { type: Object },
+ webWorkerFile: { type: Object },
myTrimmedMeassage: { type: String },
editor: {type: Object},
currentEditor: {type: String},
@@ -101,23 +110,407 @@ class ChatPage extends LitElement {
openUserInfo: { type: Boolean },
selectedHead: { type: Object },
userName: { type: String },
- goToRepliedMessage: { attribute: false },
openGifModal: { type: Boolean },
gifsLoading: { type: Boolean },
+ goToRepliedMessage: {attribute: false},
+ isLoadingGoToRepliedMessage: {type: Object}
}
}
static get styles() {
- return css`
- html {
- scroll-behavior: smooth;
- }
-
- .chat-head-container {
- display: flex;
- justify-content: flex-start;
- flex-direction: column;
- height: 50vh;
+ 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%;
@@ -681,12 +1074,6 @@ class ChatPage extends LitElement {
padding:0px;
}
- .modal-button-row {
- display: flex;
- align-items: center;
- justify-content: space-between;
- width: 100%;
- }
.modal-button {
font-family: Roboto, sans-serif;
@@ -849,14 +1236,47 @@ class ChatPage extends LitElement {
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;
+ }
+`
+}
constructor() {
super()
this.getOldMessage = this.getOldMessage.bind(this)
this._sendMessage = this._sendMessage.bind(this)
- this.insertImage = this.insertImage.bind(this)
+ this.insertFile = this.insertFile.bind(this)
+ this.pasteImage = this.pasteImage.bind(this)
this.toggleEnableChatEnter = this.toggleEnableChatEnter.bind(this)
this._downObserverhandler = this._downObserverhandler.bind(this)
this.setOpenTipUser = this.setOpenTipUser.bind(this)
@@ -887,6 +1307,7 @@ class ChatPage extends LitElement {
this.editedMessageObj = null
this.iframeHeight = 42
this.imageFile = null
+ this.attachment = null
this.uid = new ShortUniqueId()
this.userLanguage = ""
this.lastMessageRefVisible = false
@@ -914,12 +1335,18 @@ class ChatPage extends LitElement {
selected: false
}
this.webWorker = null;
- this.webWorkerImage = null;
+ this.webWorkerFile = null;
this.currentEditor = '_chatEditorDOM'
this.initialChat = this.initialChat.bind(this)
this.setOpenGifModal = this.setOpenGifModal.bind(this)
this.isEnabledChatEnter = true
this.openGifModal = false
+ this.isLoadingGoToRepliedMessage = {
+ isLoading: false,
+ top: 0,
+ left: 0,
+ offsetHeight: 0
+ }
}
@@ -1039,7 +1466,6 @@ console.log({zipFileBlob})