From 86ed01cd1d17b3cd2b84c9bc49fe5380a5a31175 Mon Sep 17 00:00:00 2001 From: PhilReact Date: Mon, 18 Nov 2024 19:07:50 +0200 Subject: [PATCH] added mentions and search --- package-lock.json | 535 +++++++++++++++--- package.json | 7 +- src/App.tsx | 4 +- src/MessageQueueContext.tsx | 38 +- src/background-cases.ts | 55 ++ src/background.ts | 95 +++- src/chatComputePow.worker.js | 107 ++++ src/components/Chat/ChatGroup.tsx | 166 ++++-- src/components/Chat/ChatList.tsx | 120 ++++- src/components/Chat/ChatOptions.tsx | 716 +++++++++++++++++++++++++ src/components/Chat/MentionList.tsx | 69 +++ src/components/Chat/MessageItem.tsx | 7 + src/components/Chat/TipTap.tsx | 139 ++++- src/components/Chat/styles.css | 49 ++ src/components/ContextMenuMentions.tsx | 130 +++++ src/components/Group/Group.tsx | 20 +- src/index.css | 1 + src/memory-pow.wasm | Bin 0 -> 3399 bytes src/qortalRequests/get.ts | 69 ++- vite.config.ts | 12 +- 20 files changed, 2104 insertions(+), 235 deletions(-) create mode 100644 src/chatComputePow.worker.js create mode 100644 src/components/Chat/ChatOptions.tsx create mode 100644 src/components/Chat/MentionList.tsx create mode 100644 src/components/ContextMenuMentions.tsx create mode 100644 src/memory-pow.wasm diff --git a/package-lock.json b/package-lock.json index 857d8d2..6b0b75c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -33,6 +33,7 @@ "@tiptap/extension-color": "^2.5.9", "@tiptap/extension-highlight": "^2.6.6", "@tiptap/extension-image": "^2.6.6", + "@tiptap/extension-mention": "^2.9.1", "@tiptap/extension-placeholder": "^2.6.2", "@tiptap/extension-text-style": "^2.5.9", "@tiptap/extension-underline": "^2.6.6", @@ -50,6 +51,7 @@ "dompurify": "^3.1.6", "emoji-picker-react": "^4.12.0", "file-saver": "^2.0.5", + "html-to-text": "^9.0.5", "jssha": "3.3.1", "lodash": "^4.17.21", "mime": "^4.0.4", @@ -73,7 +75,10 @@ "short-unique-id": "^5.2.0", "slate": "^0.103.0", "slate-react": "^0.109.0", - "tiptap-extension-resize-image": "^1.1.8" + "tippy.js": "^6.3.7", + "tiptap-extension-resize-image": "^1.1.8", + "vite-plugin-top-level-await": "^1.4.4", + "vite-plugin-wasm": "^3.3.0" }, "devDependencies": { "@testing-library/dom": "^10.3.0", @@ -2020,7 +2025,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "aix" @@ -2036,7 +2040,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -2052,7 +2055,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -2068,7 +2070,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "android" @@ -2084,7 +2085,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -2100,7 +2100,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -2116,7 +2115,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -2132,7 +2130,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "freebsd" @@ -2148,7 +2145,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2164,7 +2160,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2180,7 +2175,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2196,7 +2190,6 @@ "cpu": [ "loong64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2212,7 +2205,6 @@ "cpu": [ "mips64el" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2228,7 +2220,6 @@ "cpu": [ "ppc64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2244,7 +2235,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2260,7 +2250,6 @@ "cpu": [ "s390x" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2276,7 +2265,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -2292,7 +2280,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "netbsd" @@ -2308,7 +2295,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "openbsd" @@ -2324,7 +2310,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "sunos" @@ -2340,7 +2325,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -2356,7 +2340,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -2372,7 +2355,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -3021,7 +3003,7 @@ "version": "0.3.6", "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@jridgewell/gen-mapping": "^0.3.5", @@ -3412,9 +3394,25 @@ } }, "node_modules/@remirror/core-constants": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-2.0.2.tgz", - "integrity": "sha512-dyHY+sMF0ihPus3O27ODd4+agdHMEmuRdyiZJ2CCWjPV5UFmn17ZbElvk6WOGVE4rdCJKZQCrPV2BcikOMLUGQ==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==" + }, + "node_modules/@rollup/plugin-virtual": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@rollup/plugin-virtual/-/plugin-virtual-3.0.2.tgz", + "integrity": "sha512-10monEYsBp3scM4/ND4LNH5Rxvh3e/cVeL3jWTgZ2SrQ+BmUoQcopVQvnaMcOnykb1VkxUFuDAN+0FnpTFRy2A==", + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.13.0", @@ -3423,7 +3421,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "android" @@ -3436,7 +3433,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "android" @@ -3449,7 +3445,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -3462,7 +3457,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "darwin" @@ -3475,7 +3469,6 @@ "cpu": [ "arm" ], - "dev": true, "optional": true, "os": [ "linux" @@ -3488,7 +3481,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -3501,7 +3493,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -3514,7 +3505,6 @@ "cpu": [ "riscv64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -3527,7 +3517,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -3540,7 +3529,6 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "linux" @@ -3553,7 +3541,6 @@ "cpu": [ "arm64" ], - "dev": true, "optional": true, "os": [ "win32" @@ -3566,7 +3553,6 @@ "cpu": [ "ia32" ], - "dev": true, "optional": true, "os": [ "win32" @@ -3579,12 +3565,23 @@ "cpu": [ "x64" ], - "dev": true, "optional": true, "os": [ "win32" ] }, + "node_modules/@selderee/plugin-htmlparser2": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@selderee/plugin-htmlparser2/-/plugin-htmlparser2-0.11.0.tgz", + "integrity": "sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==", + "dependencies": { + "domhandler": "^5.0.3", + "selderee": "^0.11.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -3592,6 +3589,206 @@ "devOptional": true, "license": "MIT" }, + "node_modules/@swc/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core/-/core-1.9.2.tgz", + "integrity": "sha512-dYyEkO6mRYtZFpnOsnYzv9rY69fHAHoawYOjGOEcxk9WYtaJhowMdP/w6NcOKnz2G7GlZaenjkzkMa6ZeQeMsg==", + "hasInstallScript": true, + "dependencies": { + "@swc/counter": "^0.1.3", + "@swc/types": "^0.1.15" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/swc" + }, + "optionalDependencies": { + "@swc/core-darwin-arm64": "1.9.2", + "@swc/core-darwin-x64": "1.9.2", + "@swc/core-linux-arm-gnueabihf": "1.9.2", + "@swc/core-linux-arm64-gnu": "1.9.2", + "@swc/core-linux-arm64-musl": "1.9.2", + "@swc/core-linux-x64-gnu": "1.9.2", + "@swc/core-linux-x64-musl": "1.9.2", + "@swc/core-win32-arm64-msvc": "1.9.2", + "@swc/core-win32-ia32-msvc": "1.9.2", + "@swc/core-win32-x64-msvc": "1.9.2" + }, + "peerDependencies": { + "@swc/helpers": "*" + }, + "peerDependenciesMeta": { + "@swc/helpers": { + "optional": true + } + } + }, + "node_modules/@swc/core-darwin-arm64": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-arm64/-/core-darwin-arm64-1.9.2.tgz", + "integrity": "sha512-nETmsCoY29krTF2PtspEgicb3tqw7Ci5sInTI03EU5zpqYbPjoPH99BVTjj0OsF53jP5MxwnLI5Hm21lUn1d6A==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-darwin-x64": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-darwin-x64/-/core-darwin-x64-1.9.2.tgz", + "integrity": "sha512-9gD+bwBz8ZByjP6nZTXe/hzd0tySIAjpDHgkFiUrc+5zGF+rdTwhcNrzxNHJmy6mw+PW38jqII4uspFHUqqxuQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm-gnueabihf": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.9.2.tgz", + "integrity": "sha512-kYq8ief1Qrn+WmsTWAYo4r+Coul4dXN6cLFjiPZ29Cv5pyU+GFvSPAB4bEdMzwy99rCR0u2P10UExaeCjurjvg==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.9.2.tgz", + "integrity": "sha512-n0W4XiXlmEIVqxt+rD3ZpkogsEWUk1jJ+i5bQNgB+1JuWh0fBE8c/blDgTQXa0GB5lTPVDZQussgdNOCnAZwiA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-arm64-musl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.9.2.tgz", + "integrity": "sha512-8xzrOmsyCC1zrx2Wzx/h8dVsdewO1oMCwBTLc1gSJ/YllZYTb04pNm6NsVbzUX2tKddJVRgSJXV10j/NECLwpA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-gnu": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.9.2.tgz", + "integrity": "sha512-kZrNz/PjRQKcchWF6W292jk3K44EoVu1ad5w+zbS4jekIAxsM8WwQ1kd+yjUlN9jFcF8XBat5NKIs9WphJCVXg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-linux-x64-musl": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.9.2.tgz", + "integrity": "sha512-TTIpR4rjMkhX1lnFR+PSXpaL83TrQzp9znRdp2TzYrODlUd/R20zOwSo9vFLCyH6ZoD47bccY7QeGZDYT3nlRg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-arm64-msvc": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.9.2.tgz", + "integrity": "sha512-+Eg2d4icItKC0PMjZxH7cSYFLWk0aIp94LNmOw6tPq0e69ax6oh10upeq0D1fjWsKLmOJAWEvnXlayZcijEXDw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-ia32-msvc": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.9.2.tgz", + "integrity": "sha512-nLWBi4vZDdM/LkiQmPCakof8Dh1/t5EM7eudue04V1lIcqx9YHVRS3KMwEaCoHLGg0c312Wm4YgrWQd9vwZ5zQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/core-win32-x64-msvc": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.9.2.tgz", + "integrity": "sha512-ik/k+JjRJBFkXARukdU82tSVx0CbExFQoQ78qTO682esbYXzjdB5eLVkoUbwen299pnfr88Kn4kyIqFPTje8Xw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=10" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + }, + "node_modules/@swc/types": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.15.tgz", + "integrity": "sha512-XKaZ+dzDIQ9Ot9o89oJQ/aluI17+VvUnIpYJTcZtvv1iYX6MzHh3Ik2CSR7MdPKpPwfZXHBeCingb2b4PoDVdw==", + "dependencies": { + "@swc/counter": "^0.1.3" + } + }, "node_modules/@tanstack/react-virtual": { "version": "3.10.8", "resolved": "https://registry.npmjs.org/@tanstack/react-virtual/-/react-virtual-3.10.8.tgz", @@ -3898,15 +4095,15 @@ } }, "node_modules/@tiptap/core": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.6.6.tgz", - "integrity": "sha512-VO5qTsjt6rwworkuo0s5AqYMfDA0ZwiTiH6FHKFSu2G/6sS7HKcc/LjPq+5Legzps4QYdBDl3W28wGsGuS1GdQ==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-2.9.1.tgz", + "integrity": "sha512-tifnLL/ARzQ6/FGEJjVwj9UT3v+pENdWHdk9x6F3X0mB1y0SeCjV21wpFLYESzwNdBPAj8NMp8Behv7dBnhIfw==", "funding": { "type": "github", "url": "https://github.com/sponsors/ueberdosis" }, "peerDependencies": { - "@tiptap/pm": "^2.6.6" + "@tiptap/pm": "^2.7.0" } }, "node_modules/@tiptap/extension-blockquote": { @@ -4151,6 +4348,20 @@ "@tiptap/core": "^2.5.9" } }, + "node_modules/@tiptap/extension-mention": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/extension-mention/-/extension-mention-2.9.1.tgz", + "integrity": "sha512-2IzunpivdNtDNdtAXwRiQbNhTm87zrbkhz1cCE+2y9pWiX1QLXyx0HQq/DIAjxp6v7y4sIh+5UTUTFlH7vD9wQ==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0", + "@tiptap/suggestion": "^2.7.0" + } + }, "node_modules/@tiptap/extension-ordered-list": { "version": "2.5.9", "resolved": "https://registry.npmjs.org/@tiptap/extension-ordered-list/-/extension-ordered-list-2.5.9.tgz", @@ -4237,13 +4448,13 @@ } }, "node_modules/@tiptap/pm": { - "version": "2.6.6", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.6.6.tgz", - "integrity": "sha512-56FGLPn3fwwUlIbLs+BO21bYfyqP9fKyZQbQyY0zWwA/AG2kOwoXaRn7FOVbjP6CylyWpFJnpRRmgn694QKHEg==", + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-2.9.1.tgz", + "integrity": "sha512-mvV86fr7kEuDYEApQ2uMPCKL2uagUE0BsXiyyz3KOkY1zifyVm1fzdkscb24Qy1GmLzWAIIihA+3UHNRgYdOlQ==", "dependencies": { "prosemirror-changeset": "^2.2.1", "prosemirror-collab": "^1.3.1", - "prosemirror-commands": "^1.5.2", + "prosemirror-commands": "^1.6.0", "prosemirror-dropcursor": "^1.8.1", "prosemirror-gapcursor": "^1.3.2", "prosemirror-history": "^1.4.1", @@ -4251,14 +4462,14 @@ "prosemirror-keymap": "^1.2.2", "prosemirror-markdown": "^1.13.0", "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.22.2", + "prosemirror-model": "^1.22.3", "prosemirror-schema-basic": "^1.2.3", "prosemirror-schema-list": "^1.4.1", "prosemirror-state": "^1.4.3", "prosemirror-tables": "^1.4.0", - "prosemirror-trailing-node": "^2.0.9", - "prosemirror-transform": "^1.9.0", - "prosemirror-view": "^1.33.9" + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.0", + "prosemirror-view": "^1.34.3" }, "funding": { "type": "github", @@ -4316,6 +4527,20 @@ "url": "https://github.com/sponsors/ueberdosis" } }, + "node_modules/@tiptap/suggestion": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@tiptap/suggestion/-/suggestion-2.9.1.tgz", + "integrity": "sha512-MMxwpbtocxUsbmc8qtFY1AQYNTW5i/M4aNSv9zsKKRISaS5hMD7XVrw2eod0x0yEqZU3izLiPDZPmgr8glF+jQ==", + "peer": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^2.7.0", + "@tiptap/pm": "^2.7.0" + } + }, "node_modules/@transistorsoft/capacitor-background-fetch": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/@transistorsoft/capacitor-background-fetch/-/capacitor-background-fetch-6.0.1.tgz", @@ -4392,8 +4617,7 @@ "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", - "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", - "devOptional": true + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" }, "node_modules/@types/filesystem": { "version": "0.0.35", @@ -5338,7 +5562,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/cac": { @@ -6026,6 +6250,14 @@ "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", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -6148,11 +6380,62 @@ "csstype": "^3.0.2" } }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, "node_modules/dompurify": { "version": "3.1.6", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.1.6.tgz", "integrity": "sha512-cTOAhc36AalkjtBpfG6O8JimdTMWNXjiePT2xQH/ppBGi/4uIpmj8eKyIkMJErXWARyINV/sB38yf8JCLF5pbQ==" }, + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.49", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.49.tgz", @@ -6249,7 +6532,6 @@ "version": "0.19.12", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz", "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==", - "devOptional": true, "hasInstallScript": true, "bin": { "esbuild": "bin/esbuild" @@ -6907,7 +7189,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "optional": true, "os": [ @@ -7191,6 +7472,39 @@ "node": ">=18" } }, + "node_modules/html-to-text": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/html-to-text/-/html-to-text-9.0.5.tgz", + "integrity": "sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==", + "dependencies": { + "@selderee/plugin-htmlparser2": "^0.11.0", + "deepmerge": "^4.3.1", + "dom-serializer": "^2.0.0", + "htmlparser2": "^8.0.2", + "selderee": "^0.11.0" + }, + "engines": { + "node": ">=14" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -7852,6 +8166,14 @@ "node": ">=6" } }, + "node_modules/leac": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/leac/-/leac-0.6.0.tgz", + "integrity": "sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -8243,7 +8565,6 @@ "version": "3.3.7", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", - "devOptional": true, "funding": [ { "type": "github", @@ -10910,6 +11231,18 @@ "url": "https://github.com/inikulin/parse5?sponsor=1" } }, + "node_modules/parseley": { + "version": "0.12.1", + "resolved": "https://registry.npmjs.org/parseley/-/parseley-0.12.1.tgz", + "integrity": "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==", + "dependencies": { + "leac": "^0.6.0", + "peberminta": "^0.9.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -10986,6 +11319,14 @@ "node": "*" } }, + "node_modules/peberminta": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", + "integrity": "sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==", + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", @@ -11037,7 +11378,6 @@ "version": "8.4.37", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.37.tgz", "integrity": "sha512-7iB/v/r7Woof0glKLH8b1SPHrsX7uhdO+Geb41QpF/+mWZHU3uxxSlN+UXGVit1PawOYDToO+AbZzhBzWRDwbQ==", - "devOptional": true, "funding": [ { "type": "opencollective", @@ -11310,11 +11650,11 @@ } }, "node_modules/prosemirror-trailing-node": { - "version": "2.0.9", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-2.0.9.tgz", - "integrity": "sha512-YvyIn3/UaLFlFKrlJB6cObvUhmwFNZVhy1Q8OpW/avoTbD/Y7H5EcjK4AZFKhmuS6/N6WkGgt7gWtBWDnmFvHg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", "dependencies": { - "@remirror/core-constants": "^2.0.2", + "@remirror/core-constants": "3.0.0", "escape-string-regexp": "^4.0.0" }, "peerDependencies": { @@ -11335,17 +11675,17 @@ } }, "node_modules/prosemirror-transform": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.9.0.tgz", - "integrity": "sha512-5UXkr1LIRx3jmpXXNKDhv8OyAOeLTGuXNwdVfg8x27uASna/wQkr9p6fD3eupGOi4PLJfbezxTyi/7fSJypXHg==", + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.10.2.tgz", + "integrity": "sha512-2iUq0wv2iRoJO/zj5mv8uDUriOHWzXRnOTVgCzSXnktS/2iQRa3UUQwVlkBlYZFtygw6Nh1+X4mGqoYBINn5KQ==", "dependencies": { "prosemirror-model": "^1.21.0" } }, "node_modules/prosemirror-view": { - "version": "1.33.9", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.33.9.tgz", - "integrity": "sha512-xV1A0Vz9cIcEnwmMhKKFAOkfIp8XmJRnaZoPqNXrPS7EK5n11Ov8V76KhR0RsfQd/SIzmWY+bg+M44A2Lx/Nnw==", + "version": "1.36.0", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.36.0.tgz", + "integrity": "sha512-U0GQd5yFvV5qUtT41X1zCQfbw14vkbbKwLlQXhdylEmgpYVHkefXYcC4HHwWOfZa3x6Y8wxDLUBv7dxN5XQ3nA==", "dependencies": { "prosemirror-model": "^1.20.0", "prosemirror-state": "^1.0.0", @@ -12059,7 +12399,6 @@ "version": "4.13.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", "integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", - "devOptional": true, "dependencies": { "@types/estree": "1.0.5" }, @@ -12213,6 +12552,17 @@ "compute-scroll-into-view": "^3.0.2" } }, + "node_modules/selderee": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/selderee/-/selderee-0.11.0.tgz", + "integrity": "sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==", + "dependencies": { + "parseley": "^0.12.0" + }, + "funding": { + "url": "https://ko-fi.com/killymxi" + } + }, "node_modules/semver": { "version": "7.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz", @@ -12428,7 +12778,6 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", - "devOptional": true, "engines": { "node": ">=0.10.0" } @@ -12437,7 +12786,7 @@ "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "buffer-from": "^1.0.0", @@ -12448,7 +12797,7 @@ "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "devOptional": true, "peer": true, "engines": { "node": ">=0.10.0" @@ -12664,7 +13013,7 @@ "version": "5.36.0", "resolved": "https://registry.npmjs.org/terser/-/terser-5.36.0.tgz", "integrity": "sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==", - "dev": true, + "devOptional": true, "peer": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -12683,7 +13032,7 @@ "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, + "devOptional": true, "peer": true }, "node_modules/text-table": { @@ -13135,6 +13484,18 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/vfile": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/vfile/-/vfile-2.3.0.tgz", @@ -13170,7 +13531,6 @@ "version": "5.1.6", "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.6.tgz", "integrity": "sha512-yYIAZs9nVfRJ/AiOLCA91zzhjsHUgMjB+EigzFb6W2XTLO8JixBCKCjvhKZaye+NKYHCrkv3Oh50dH9EdLU2RA==", - "devOptional": true, "dependencies": { "esbuild": "^0.19.3", "postcss": "^8.4.35", @@ -13244,6 +13604,27 @@ "url": "https://opencollective.com/vitest" } }, + "node_modules/vite-plugin-top-level-await": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/vite-plugin-top-level-await/-/vite-plugin-top-level-await-1.4.4.tgz", + "integrity": "sha512-QyxQbvcMkgt+kDb12m2P8Ed35Sp6nXP+l8ptGrnHV9zgYDUpraO0CPdlqLSeBqvY2DToR52nutDG7mIHuysdiw==", + "dependencies": { + "@rollup/plugin-virtual": "^3.0.2", + "@swc/core": "^1.7.0", + "uuid": "^10.0.0" + }, + "peerDependencies": { + "vite": ">=2.8" + } + }, + "node_modules/vite-plugin-wasm": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-wasm/-/vite-plugin-wasm-3.3.0.tgz", + "integrity": "sha512-tVhz6w+W9MVsOCHzxo6SSMSswCeIw4HTrXEi6qL3IRzATl83jl09JVO1djBqPSwfjgnpVHNLYcaMbaDX5WB/pg==", + "peerDependencies": { + "vite": "^2 || ^3 || ^4 || ^5" + } + }, "node_modules/vitest": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.0.tgz", diff --git a/package.json b/package.json index ca68ced..210175b 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "@tiptap/extension-placeholder": "^2.6.2", "@tiptap/extension-text-style": "^2.5.9", "@tiptap/extension-underline": "^2.6.6", + "@tiptap/extension-mention": "^2.9.1", "@tiptap/pm": "^2.5.9", "@tiptap/react": "^2.5.9", "@tiptap/starter-kit": "^2.5.9", @@ -77,7 +78,11 @@ "short-unique-id": "^5.2.0", "slate": "^0.103.0", "slate-react": "^0.109.0", - "tiptap-extension-resize-image": "^1.1.8" + "tiptap-extension-resize-image": "^1.1.8", + "vite-plugin-top-level-await": "^1.4.4", + "vite-plugin-wasm": "^3.3.0", + "html-to-text": "^9.0.5", + "tippy.js": "^6.3.7" }, "devDependencies": { "@testing-library/dom": "^10.3.0", diff --git a/src/App.tsx b/src/App.tsx index 5280f97..b5a6df3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1002,7 +1002,7 @@ function App() { .sendMessage("decryptWallet", { password: walletToBeDownloadedPassword, wallet, - }) + },120000) .then((response) => { if (response && !response.error) { setRawWallet(wallet); @@ -1126,7 +1126,7 @@ function App() { .sendMessage("decryptWallet", { password: authenticatePassword, wallet: rawWallet, - }) + }, 120000) .then((response) => { if (response && !response.error) { setAuthenticatePassword(""); diff --git a/src/MessageQueueContext.tsx b/src/MessageQueueContext.tsx index f53871b..3d46e50 100644 --- a/src/MessageQueueContext.tsx +++ b/src/MessageQueueContext.tsx @@ -6,15 +6,15 @@ const MessageQueueContext = createContext(null); export const useMessageQueue = () => useContext(MessageQueueContext); const uid = new ShortUniqueId({ length: 8 }); -let messageQueue = []; // Global message queue export const MessageQueueProvider = ({ children }) => { + const messageQueueRef = useRef([]); const [queueChats, setQueueChats] = useState({}); // Stores chats and status for display const isProcessingRef = useRef(false); // To track if the queue is being processed - const maxRetries = 3; + const maxRetries = 2; const clearStatesMessageQueueProvider = useCallback(() => { setQueueChats({}); - messageQueue = []; + messageQueueRef.current = []; isProcessingRef.current = false; }, []); @@ -36,9 +36,9 @@ export const MessageQueueProvider = ({ children }) => { [groupDirectId]: [...(prev[groupDirectId] || []), chatData] })); - // Add the message to the global messageQueue - messageQueue = [ - ...messageQueue, + // Add the message to the global messageQueueRef.current + messageQueueRef.current = [ + ...messageQueueRef.current, { func: sendMessageFunc, identifier: tempId, groupDirectId, specialId: messageObj?.message?.specialId } ]; @@ -51,10 +51,10 @@ export const MessageQueueProvider = ({ children }) => { processQueue(newMessages, groupDirectId); }; - // Function to process the messageQueue and handle new messages + // Function to process the messageQueueRef.current and handle new messages const processQueue = useCallback(async (newMessages = [], groupDirectId) => { // Filter out any message in the queue that matches the specialId from newMessages - messageQueue = messageQueue.filter((msg) => { + messageQueueRef.current = messageQueueRef.current.filter((msg) => { return !newMessages.some(newMsg => newMsg?.specialId === msg?.specialId); }); @@ -68,7 +68,6 @@ export const MessageQueueProvider = ({ children }) => { return !newMessages.some(newMsg => newMsg?.specialId === chat?.message?.specialId); }); - updatedChats[groupDirectId] = updatedChats[groupDirectId].filter((chat) => { return chat?.status !== 'failed-permanent' }); @@ -82,12 +81,12 @@ export const MessageQueueProvider = ({ children }) => { }); // If currently processing or the queue is empty, return - if (isProcessingRef.current || messageQueue.length === 0) return; + if (isProcessingRef.current || messageQueueRef.current.length === 0) return; isProcessingRef.current = true; // Lock the queue for processing - while (messageQueue.length > 0) { - const currentMessage = messageQueue[0]; // Get the first message in the queue + while (messageQueueRef.current.length > 0) { + const currentMessage = messageQueueRef.current[0]; // Get the first message in the queue const { groupDirectId, identifier } = currentMessage; // Update the chat status to 'sending' @@ -105,11 +104,10 @@ export const MessageQueueProvider = ({ children }) => { }); try { - // Execute the function stored in the messageQueue + // Execute the function stored in the messageQueueRef.current await currentMessage.func(); - - // Remove the message from the messageQueue after successful sending - messageQueue = messageQueue.slice(1); // Slice here remains for successful messages + // Remove the message from the messageQueueRef.current after successful sending + messageQueueRef.current.shift(); // Slice here remains for successful messages // Remove the message from queueChats after success // setQueueChats((prev) => { @@ -138,10 +136,10 @@ export const MessageQueueProvider = ({ children }) => { // Max retries reached, set status to 'failed-permanent' updatedChats[groupDirectId][chatIndex].status = 'failed-permanent'; - // Remove the message from the messageQueue after max retries - messageQueue = messageQueue.slice(1); // Slice for failed messages after max retries + // Remove the message from the messageQueueRef.current after max retries + messageQueueRef.current.shift();// Slice for failed messages after max retries - // Remove the message from queueChats after failure + // // Remove the message from queueChats after failure // updatedChats[groupDirectId] = updatedChats[groupDirectId].filter( // (item) => item.identifier !== identifier // ); @@ -149,7 +147,7 @@ export const MessageQueueProvider = ({ children }) => { } return updatedChats; }); - } + } // Delay between processing each message to avoid overlap await new Promise((res) => setTimeout(res, 5000)); diff --git a/src/background-cases.ts b/src/background-cases.ts index 8136316..cd1196a 100644 --- a/src/background-cases.ts +++ b/src/background-cases.ts @@ -3,6 +3,7 @@ import { addEnteredQmailTimestamp, addTimestampEnterChat, addTimestampGroupAnnouncement, + addTimestampMention, addUserSettings, banFromGroup, cancelBan, @@ -29,6 +30,7 @@ import { getTempPublish, getTimestampEnterChat, getTimestampGroupAnnouncement, + getTimestampMention, getUserInfo, getUserSettings, handleActiveGroupDataFromSocket, @@ -1745,4 +1747,57 @@ export async function publishGroupEncryptedResourceCase(request, event) { event.origin ); } + } + + export async function getTimestampMentionCase(request, event) { + try { + const response = await getTimestampMention(); + + event.source.postMessage( + { + requestId: request.requestId, + action: "getTimestampMention", + payload: response, + type: "backgroundMessageResponse", + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: "getTimestampMention", + error: error?.message, + type: "backgroundMessageResponse", + }, + event.origin + ); + } + } + + export async function addTimestampMentionCase(request, event) { + try { + const { groupId, timestamp } = request.payload; + const response = await addTimestampMention({ groupId, timestamp }); + + event.source.postMessage( + { + requestId: request.requestId, + action: "addTimestampMention", + payload: response, + type: "backgroundMessageResponse", + }, + event.origin + ); + } catch (error) { + event.source.postMessage( + { + requestId: request.requestId, + action: "addTimestampMention", + error: error?.message, + type: "backgroundMessageResponse", + }, + event.origin + ); + } } \ No newline at end of file diff --git a/src/background.ts b/src/background.ts index 5b61c5d..792bb5f 100644 --- a/src/background.ts +++ b/src/background.ts @@ -66,6 +66,9 @@ import { inviteToGroupCase, joinGroupCase, kickFromGroupCase, + addTimestampMentionCase, + getTimestampMentionCase, + leaveGroupCase, ltcBalanceCase, makeAdminCase, @@ -95,6 +98,8 @@ import { import { getData, removeKeysAndLogout, storeData } from "./utils/chromeStorage"; import {BackgroundFetch} from '@transistorsoft/capacitor-background-fetch'; import { LocalNotifications } from '@capacitor/local-notifications'; +import ChatComputePowWorker from './chatComputePow.worker.js?worker'; + const uid = new ShortUniqueId({ length: 9, dictionary: 'number' }); const generateId = ()=> { @@ -378,6 +383,31 @@ function playNotificationSound() { // chrome.runtime.sendMessage({ action: "PLAY_NOTIFICATION_SOUND" }); } +const worker = new ChatComputePowWorker() + +export async function performPowTask(chatBytes, difficulty) { + return new Promise((resolve, reject) => { + worker.onmessage = (e) => { + if (e.data.error) { + reject(new Error(e.data.error)); + } else { + resolve(e.data); + } + }; + + worker.onerror = (err) => { + reject(err); + }; + + // Send the task to the worker + worker.postMessage({ + chatBytes, + path: `${import.meta.env.BASE_URL}memory-pow.wasm.full`, + difficulty, + }); + }); +} + const handleNotificationDirect = async (directs) => { let isFocused; const wallet = await getSaveWallet(); @@ -1352,7 +1382,6 @@ async function sendChatForBuyOrder({ qortAddress, recipientPublicKey, message }) }; const balance = await getBalanceInfo(); const hasEnoughBalance = +balance < 4 ? false : true; - const difficulty = 8; const jsonData = { addresses: message.addresses, foreignKey: message.foreignKey, @@ -1378,13 +1407,9 @@ async function sendChatForBuyOrder({ qortAddress, recipientPublicKey, message }) if (!hasEnoughBalance) { throw new Error('You must have at least 4 QORT to trade using the gateway.') } - const path = `${import.meta.env.BASE_URL}memory-pow.wasm.full`; - - const { nonce, chatBytesArray } = await computePow({ - chatBytes: tx.chatBytes, - path, - difficulty, - }); + const chatBytes = tx.chatBytes; + const difficulty = 8; + const { nonce, chatBytesArray } = await performPowTask(chatBytes, difficulty); let _response = await signChatFunc( chatBytesArray, nonce, @@ -1417,7 +1442,6 @@ export async function sendChatGroup({ }; // const balance = await getBalanceInfo(); // const hasEnoughBalance = +balance < 4 ? false : true; - const difficulty = 8; const txBody = { timestamp: Date.now(), @@ -1440,13 +1464,9 @@ export async function sendChatGroup({ // if (!hasEnoughBalance) { // throw new Error("Must have at least 4 QORT to send a chat message"); // } - const path = `${import.meta.env.BASE_URL}memory-pow.wasm.full`; - - const { nonce, chatBytesArray } = await computePow({ - chatBytes: tx.chatBytes, - path, - difficulty, - }); + const chatBytes = tx.chatBytes; + const difficulty = 8; + const { nonce, chatBytesArray } = await performPowTask(chatBytes, difficulty); let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); if (_response?.error) { throw new Error(_response?.message); @@ -1492,7 +1512,6 @@ export async function sendChatDirect({ // const balance = await getBalanceInfo(); // const hasEnoughBalance = +balance < 4 ? false : true; - const difficulty = 8; const finalJson = { message: messageText, @@ -1520,13 +1539,9 @@ export async function sendChatDirect({ // if (!hasEnoughBalance) { // throw new Error("Must have at least 4 QORT to send a chat message"); // } - const path = `${import.meta.env.BASE_URL}memory-pow.wasm.full`; - - const { nonce, chatBytesArray } = await computePow({ - chatBytes: tx.chatBytes, - path, - difficulty, - }); + const chatBytes = tx.chatBytes; + const difficulty = 8; + const { nonce, chatBytesArray } = await performPowTask(chatBytes, difficulty); let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); if (_response?.error) { @@ -2448,6 +2463,31 @@ async function setChatHeadsDirect(data) { }); } +export async function getTimestampMention() { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const key = `enter-mention-timestamp-${address}`; + const res = await getData(key).catch(() => null); + if (res) { + const parsedData = res; + return parsedData; + } else { + return {}; + } +} +export async function addTimestampMention({ groupId, timestamp }) { + const wallet = await getSaveWallet(); + const address = wallet.address0; + const data = await getTimestampMention(); + data[groupId] = timestamp; + return await new Promise((resolve, reject) => { + storeData(`enter-mention-timestamp-${address}`, data) + .then(() => resolve(true)) + .catch((error) => { + reject(new Error(error.message || "Error saving data")); + }); + }); +} export async function getTimestampEnterChat() { const wallet = await getSaveWallet(); @@ -2720,7 +2760,12 @@ function setupMessageListener() { case "registerName": registerNameCase(request, event); break; - + case "addTimestampMention": + addTimestampMentionCase(request, event); + break; + case "getTimestampMention": + getTimestampMentionCase(request, event); + break; case "makeAdmin": makeAdminCase(request, event); break; diff --git a/src/chatComputePow.worker.js b/src/chatComputePow.worker.js new file mode 100644 index 0000000..00f5f72 --- /dev/null +++ b/src/chatComputePow.worker.js @@ -0,0 +1,107 @@ +import { Sha256 } from 'asmcrypto.js'; +import wasmInit from './memory-pow.wasm?init'; + +let compute; // Exported compute function from Wasm +let memory; // WebAssembly.Memory instance +let heap; // Uint8Array view of the memory buffer +let brk = 512 * 1024; // Initial brk set to 512 KiB +const allocations = new Map(); // Track allocations by pointer +let workBufferPtr = null; // Reuse work buffer +const workBufferLength = 8 * 1024 * 1024; // 8 MiB + +// Load the WebAssembly module +async function loadWasm() { + try { + memory = new WebAssembly.Memory({ initial: 256, maximum: 256 }); // 16 MiB + heap = new Uint8Array(memory.buffer); + + const importObject = { + env: { + memory, // Pass memory to Wasm + abort: () => { throw new Error('Wasm abort called'); }, // Handle abort calls from Wasm + }, + }; + + const wasmModule = await wasmInit(importObject); + compute = wasmModule.exports.compute2; + console.log('Wasm loaded successfully:', compute); + } catch (error) { + console.error('Error loading Wasm:', error); + throw error; + } +} + +// Memory allocation function +function sbrk(size) { + const old = brk; + + // If a previous allocation exists for this size, reuse it + if (allocations.has(size)) { + return allocations.get(size); + } + + brk += size; + + // Grow memory if needed + if (brk > memory.buffer.byteLength) { + const pagesNeeded = Math.ceil((brk - memory.buffer.byteLength) / (64 * 1024)); // 64 KiB per page + console.log(`Growing memory by ${pagesNeeded} pages`); + try { + memory.grow(pagesNeeded); + heap = new Uint8Array(memory.buffer); // Update heap view + } catch (e) { + console.error('Failed to grow memory:', e); + throw new RangeError('WebAssembly.Memory.grow(): Maximum memory size exceeded'); + } + } + + allocations.set(size, old); // Track the allocation + return old; +} + +// Proof-of-Work computation function +async function computePow(chatBytes, difficulty) { + if (!compute) { + throw new Error('WebAssembly module not initialized. Call loadWasm first.'); + } + + const chatBytesArray = Uint8Array.from(Object.values(chatBytes)); + const chatBytesHash = new Sha256().process(chatBytesArray).finish().result; + + // Allocate memory for the hash + const hashPtr = sbrk(32); + const hashAry = new Uint8Array(memory.buffer, hashPtr, 32); + hashAry.set(chatBytesHash); + + // Reuse the work buffer if already allocated + if (!workBufferPtr) { + workBufferPtr = sbrk(workBufferLength); + } + + console.log('Starting POW computation...'); + const nonce = compute(hashPtr, workBufferPtr, workBufferLength, difficulty); + console.log('POW computation finished.'); + + return { nonce, chatBytesArray }; +} + +// Worker event listener +self.addEventListener('message', async (e) => { + const { chatBytes, difficulty } = e.data; + + try { + // Initialize Wasm if not already done + if (!compute) { + await loadWasm(); + } + + // Perform the POW computation + const result = await computePow(chatBytes, difficulty); + + // Send the result back to the main thread + self.postMessage(result); + } catch (error) { + console.error('Error in worker:', error); + self.postMessage({ error: error.message }); + } +}); diff --git a/src/components/Chat/ChatGroup.tsx b/src/components/Chat/ChatGroup.tsx index 92c327c..858a3fd 100644 --- a/src/components/Chat/ChatGroup.tsx +++ b/src/components/Chat/ChatGroup.tsx @@ -21,10 +21,12 @@ import { ReplyPreview } from './MessageItem' import { ExitIcon } from '../../assets/Icons/ExitIcon' import { RESOURCE_TYPE_NUMBER_GROUP_CHAT_REACTIONS } from '../../constants/resourceTypes' import { isExtMsg } from '../../background' +import MentionList from './MentionList' +import { ChatOptions } from './ChatOptions' const uid = new ShortUniqueId({ length: 5 }); -export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName, balance}) => { +export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, myAddress, handleNewEncryptionNotification, hide, handleSecretKeyCreationInProgress, triedToFetchSecretKey, myName, balance, getTimestampEnterChatParent}) => { const [messages, setMessages] = useState([]) const [chatReferences, setChatReferences] = useState({}) const [isSending, setIsSending] = useState(false) @@ -44,6 +46,63 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, const { queueChats, addToQueue, processWithNewMessages } = useMessageQueue(); const [, forceUpdate] = useReducer((x) => x + 1, 0); + + const lastReadTimestamp = useRef(null) + + + + const getTimestampEnterChat = async () => { + try { + return new Promise((res, rej) => { + window.sendMessage("getTimestampEnterChat") + .then((response) => { + if (!response?.error) { + if(response && selectedGroup && response[selectedGroup]){ + lastReadTimestamp.current = response[selectedGroup] + window.sendMessage("addTimestampEnterChat", { + timestamp: Date.now(), + groupId: selectedGroup + }).catch((error) => { + console.error("Failed to add timestamp:", error.message || "An error occurred"); + }); + + + setTimeout(() => { + getTimestampEnterChatParent(); + }, 200); + } + + res(response); + return; + } + rej(response.error); + }) + .catch((error) => { + rej(error.message || "An error occurred"); + }); + + }); + } catch (error) {} + }; + + useEffect(()=> { + getTimestampEnterChat() + }, []) + + + + const members = useMemo(() => { + const uniqueMembers = new Set(); + + messages.forEach((message) => { + if (message?.senderName) { + uniqueMembers.add(message?.senderName); + } + }); + + return Array.from(uniqueMembers); + }, [messages]); + const triggerRerender = () => { forceUpdate(); // Trigger re-render by updating the state }; @@ -211,16 +270,25 @@ export const ChatGroup = ({selectedGroup, secretKey, setSecretKey, getSecretKey, return organizedChatReferences; }); } else { + let firstUnreadFound = false; const formatted = combineUIAndExtensionMsgs .filter((rawItem) => !rawItem?.chatReference) - .map((item) => ({ - ...item, - id: item.signature, - text: item?.decryptedData?.message || "", - repliedTo: item?.repliedTo || item?.decryptedData?.repliedTo, - isNotEncrypted: !!item?.messageText, - unread: false, - })); + .map((item) => { + const divide = lastReadTimestamp.current && !firstUnreadFound && item.timestamp > lastReadTimestamp.current && myAddress !== item?.sender; + + if(divide){ + firstUnreadFound = true + } + return { + ...item, + id: item.signature, + text: item?.decryptedData?.message || "", + repliedTo: item?.repliedTo || item?.decryptedData?.repliedTo, + isNotEncrypted: !!item?.messageText, + unread: false, + divide + } + }); setMessages(formatted); setChatReferences((prev) => { @@ -621,7 +689,7 @@ const clearEditorContent = () => { left: hide && '-100000px', }}> - +
{ )} - - + + + + + {!isFocusedParent && ( + {}} members={members} myName={myName} selectedGroup={selectedGroup}/> +)} +
{ )} - { - if(isSending) return - sendMessage() - }} - style={{ - marginTop: 'auto', - alignSelf: 'center', - cursor: isSending ? 'default' : 'pointer', - background: isSending && 'rgba(0, 0, 0, 0.8)', - flexShrink: 0, - padding: isMobile && '5px', - - }} - > - {isSending && ( - - )} - {` Send`} - + {!isMobile && !isFocusedParent && ( + { + if(isSending) return + sendMessage() + }} + style={{ + marginTop: 'auto', + alignSelf: 'center', + cursor: isSending ? 'default' : 'pointer', + background: isSending && 'rgba(0, 0, 0, 0.8)', + flexShrink: 0, + padding: isMobile && '5px', + + }} + > + {isSending && ( + + )} + {` Send`} + + )} + {/* */} diff --git a/src/components/Chat/ChatList.tsx b/src/components/Chat/ChatList.tsx index 72c6e86..f028ba9 100644 --- a/src/components/Chat/ChatList.tsx +++ b/src/components/Chat/ChatList.tsx @@ -9,7 +9,18 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR const [messages, setMessages] = useState(initialMessages); const [showScrollButton, setShowScrollButton] = useState(false); const hasLoadedInitialRef = useRef(false); - const isAtBottomRef = useRef(true); + const [showScrollDownButton, setShowScrollDownButton] = useState(false); + + const showScrollButtonRef = useRef(showScrollButton); + const isScrollingRef = useRef(false); + const scrollTimeoutRef = useRef(null); + + // Update the ref whenever the state changes + useEffect(() => { + showScrollButtonRef.current = showScrollButton; + }, [showScrollButton]); + + // const [ref, inView] = useInView({ // threshold: 0.7 // }) @@ -40,27 +51,36 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR setMessages(totalMessages); setTimeout(() => { - const hasUnreadMessages = totalMessages.some((msg) => msg.unread && !msg?.chatReference); + const hasUnreadMessages = totalMessages.some((msg) => msg.unread && !msg?.chatReference && !msg?.isTemp); if (parentRef.current) { const { scrollTop, scrollHeight, clientHeight } = parentRef.current; const atBottom = scrollTop + clientHeight >= scrollHeight - 10; // Adjust threshold as needed if (!atBottom && hasUnreadMessages) { setShowScrollButton(hasUnreadMessages); + setShowScrollDownButton(false) + } else { handleMessageSeen(); } } if (!hasLoadedInitialRef.current) { - scrollToBottom(totalMessages); + const findDivideIndex = totalMessages.findIndex((item)=> !!item?.divide) + const divideIndex = findDivideIndex !== -1 ? findDivideIndex : undefined + scrollToBottom(totalMessages, divideIndex); hasLoadedInitialRef.current = true; } }, 500); }, [initialMessages, tempMessages]); - const scrollToBottom = (initialMsgs) => { + const scrollToBottom = (initialMsgs, divideIndex) => { const index = initialMsgs ? initialMsgs.length - 1 : messages.length - 1; if (rowVirtualizer) { - rowVirtualizer.scrollToIndex(index, { align: 'end' }); + if(divideIndex){ + rowVirtualizer.scrollToIndex(divideIndex, { align: 'start' }) + } else { + rowVirtualizer.scrollToIndex(index, { align: 'end' }) + + } } handleMessageSeen() }; @@ -84,9 +104,17 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR // }; + const sentNewMessageGroupFunc = useCallback(() => { - scrollToBottom(); + const { scrollHeight, scrollTop, clientHeight } = parentRef.current; + + // Check if the user is within 200px from the bottom + const distanceFromBottom = scrollHeight - scrollTop - clientHeight; + if (distanceFromBottom <= 700) { + scrollToBottom(); + } }, [messages]); + useEffect(() => { subscribeToEvent('sent-new-message-group', sentNewMessageGroupFunc); @@ -112,8 +140,63 @@ export const ChatList = ({ initialMessages, myAddress, tempMessages, chatId, onR (index) => messages[index].signature, [messages] ), + observeElementOffset: (instance, cb) => { + const offsetCheck = () => { + isScrollingRef.current = true; + + const { scrollHeight, scrollTop, clientHeight } = instance.scrollElement; + const atBottom = scrollHeight - scrollTop - clientHeight <= 300; + if(!isScrollingRef.current){ + setShowScrollDownButton(false) + } else + if(showScrollButtonRef.current){ + setShowScrollDownButton(false) + } else + if(atBottom){ + setShowScrollDownButton(false) + + } else { + setShowScrollDownButton(true) + + } + if (scrollTimeoutRef.current) { + clearTimeout(scrollTimeoutRef.current); // Clear the previous timeout + } + scrollTimeoutRef.current = setTimeout(() => { + isScrollingRef.current = false; // Mark scrolling as stopped + scrollTimeoutRef.current = null; // Clear the timeout reference + setShowScrollDownButton(false) + }, 2500); + cb(scrollTop); // Pass scroll offset to callback + // setShowScrollToBottom(!atBottom); + }; + + // Initial check and continuous monitoring + offsetCheck(); + instance.scrollElement.addEventListener('scroll', offsetCheck); + return () => instance.scrollElement.removeEventListener('scroll', offsetCheck); + }, }); + + + + const goToMessageFunc = useCallback((e)=> { + if(e.detail?.index){ + rowVirtualizer.scrollToIndex(e.detail?.index) + + } + }, []) + + + + useEffect(() => { + subscribeToEvent('goToMessage', goToMessageFunc); + return () => { + unsubscribeFromEvent('goToMessage', goToMessageFunc); + }; + }, [goToMessageFunc]); + return (
Scroll to Unread Messages + )} + {showScrollDownButton && ( + )}
diff --git a/src/components/Chat/ChatOptions.tsx b/src/components/Chat/ChatOptions.tsx new file mode 100644 index 0000000..76b019d --- /dev/null +++ b/src/components/Chat/ChatOptions.tsx @@ -0,0 +1,716 @@ +import { + Avatar, + Box, + ButtonBase, + InputBase, + MenuItem, + Select, + Typography, +} from "@mui/material"; +import React, { useEffect, useMemo, useRef, useState } from "react"; +import SearchIcon from "@mui/icons-material/Search"; +import { Spacer } from "../../common/Spacer"; +import AlternateEmailIcon from "@mui/icons-material/AlternateEmail"; +import CloseIcon from "@mui/icons-material/Close"; +import { + AppsSearchContainer, + AppsSearchLeft, + AppsSearchRight, +} from "../Apps/Apps-styles"; +import IconSearch from "../../assets/svgs/Search.svg"; +import IconClearInput from "../../assets/svgs/ClearInput.svg"; +import { + AutoSizer, + CellMeasurer, + CellMeasurerCache, + List, +} from "react-virtualized"; +import { getBaseApiReact, isMobile } from "../../App"; +import { MessageDisplay } from "./MessageDisplay"; +import { useVirtualizer } from "@tanstack/react-virtual"; +import { formatTimestamp } from "../../utils/time"; +import { ContextMenuMentions } from "../ContextMenuMentions"; +import { convert } from 'html-to-text'; +import { executeEvent } from "../../utils/events"; + +const extractTextFromHTML = (htmlString = '') => { + return convert(htmlString, { + wordwrap: false, // Disable word wrapping + })?.toLowerCase(); +}; +const cache = new CellMeasurerCache({ + fixedWidth: true, + defaultHeight: 50, +}); + +export const ChatOptions = ({ messages, goToMessage, members, myName, selectedGroup }) => { + const [mode, setMode] = useState("default"); + const [searchValue, setSearchValue] = useState(""); + const [selectedMember, setSelectedMember] = useState(0); + + const parentRef = useRef(); + const parentRefMentions = useRef(); + const [lastMentionTimestamp, setLastMentionTimestamp] = useState(null) + const [debouncedValue, setDebouncedValue] = useState(""); // Debounced value + + const getTimestampMention = async () => { + try { + return new Promise((res, rej) => { + window.sendMessage("getTimestampMention") + .then((response) => { + if (!response?.error) { + if(response && selectedGroup && response[selectedGroup]){ + setLastMentionTimestamp(response[selectedGroup]) + + + + + + } + + res(response); + return; + } + rej(response.error); + }) + .catch((error) => { + rej(error.message || "An error occurred"); + }); + + }); + } catch (error) {} + }; + + useEffect(()=> { + if(mode === 'mentions' && selectedGroup){ + window.sendMessage("addTimestampMention", { + timestamp: Date.now(), + groupId: selectedGroup + }).then((res)=> { + getTimestampMention() + }).catch((error) => { + console.error("Failed to add timestamp:", error.message || "An error occurred"); + }); + } + }, [mode, selectedGroup]) + + useEffect(()=> { + getTimestampMention() + }, []) + + // Debounce logic + useEffect(() => { + const handler = setTimeout(() => { + setDebouncedValue(searchValue); + }, 350); + + // Cleanup timeout if searchValue changes before the timeout completes + return () => { + clearTimeout(handler); + }; + }, [searchValue]); // Runs effect when searchValue changes + + const searchedList = useMemo(() => { + if (!debouncedValue?.trim()) { + if (selectedMember) { + return messages + .filter((message) => message?.senderName === selectedMember) + ?.sort((a, b) => b?.timestamp - a?.timestamp); + } + return []; + } + if (selectedMember) { + return messages + .filter( + (message) => + message?.senderName === selectedMember && + extractTextFromHTML(message?.decryptedData?.message)?.includes( + debouncedValue.toLowerCase() + ) + ) + ?.sort((a, b) => b?.timestamp - a?.timestamp); + } + return messages + .filter((message) => + extractTextFromHTML(message?.decryptedData?.message)?.includes(debouncedValue.toLowerCase()) + ) + ?.sort((a, b) => b?.timestamp - a?.timestamp); + }, [debouncedValue, messages, selectedMember]); + + const mentionList = useMemo(() => { + if(!messages || messages.length === 0 || !myName) return [] + + return messages + .filter((message) => + extractTextFromHTML(message?.decryptedData?.message)?.includes(`@${myName}`) + ) + ?.sort((a, b) => b?.timestamp - a?.timestamp); + }, [messages, myName]); + + const rowVirtualizer = useVirtualizer({ + count: searchedList.length, + getItemKey: React.useCallback( + (index) => searchedList[index].signature, + [searchedList] + ), + getScrollElement: () => parentRef.current, + estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed + overscan: 10, // Number of items to render outside the visible area to improve smoothness + }); + + const rowVirtualizerMentions = useVirtualizer({ + count: mentionList.length, + getItemKey: React.useCallback( + (index) => mentionList[index].signature, + [mentionList] + ), + getScrollElement: () => parentRefMentions.current, + estimateSize: () => 80, // Provide an estimated height of items, adjust this as needed + overscan: 10, // Number of items to render outside the visible area to improve smoothness + }); + + + + if (mode === "mentions") { + return ( + + + { + setMode("default"); + }} + sx={{ + cursor: "pointer", + color: "white", + }} + /> + + + + + + {mentionList?.length === 0 && ( + + No results + + )} + +
+
+
+
+ {rowVirtualizerMentions.getVirtualItems().map((virtualRow) => { + const index = virtualRow.index; + let message = mentionList[index]; + return ( +
+ + + + + {message?.senderName?.charAt(0)} + + + {message?.senderName} + + + + + {formatTimestamp(message.timestamp)} + { + const findMsgIndex = messages.findIndex( + (item) => + item?.signature === message?.signature + ); + if (findMsgIndex !== -1) { + if(isMobile){ + setMode("default"); + executeEvent('goToMessage', {index: findMsgIndex}) + + } else { + goToMessage(findMsgIndex); + + } + } + }} + > +

" + } + /> +
+
+
+ ); + })} +
+
+
+
+
+
+
+ ); + } + + if (mode === "search") { + return ( + + + { + setMode("default"); + }} + sx={{ + cursor: "pointer", + color: "white", + }} + /> + + + + + + setSearchValue(e.target.value)} + sx={{ ml: 1, flex: 1 }} + placeholder="Search chat text" + inputProps={{ + "aria-label": "Search for apps", + fontSize: "16px", + fontWeight: 400, + }} + /> + + + {searchValue && ( + { + setSearchValue(""); + }} + > + + + )} + + + + + {!!selectedMember && ( + { + setSelectedMember(0); + }} + sx={{ + cursor: "pointer", + color: "white", + }} + /> + )} + + + + {debouncedValue && searchedList?.length === 0 && ( + + No results + + )} + +
+
+
+
+ {rowVirtualizer.getVirtualItems().map((virtualRow) => { + const index = virtualRow.index; + let message = searchedList[index]; + return ( +
+ + + + + {message?.senderName?.charAt(0)} + + + {message?.senderName} + + + + + {formatTimestamp(message.timestamp)} + { + const findMsgIndex = messages.findIndex( + (item) => + item?.signature === message?.signature + ); + if (findMsgIndex !== -1) { + if(isMobile){ + setMode("default"); + executeEvent('goToMessage', {index: findMsgIndex}) + + } else { + goToMessage(findMsgIndex); + + } + } + }} + > +

" + } + /> +
+
+
+ ); + })} +
+
+
+
+
+
+
+ ); + } + return ( + + + { + setMode("search") + }}> + + + + { + setMode("mentions") + setSearchValue('') + setSelectedMember(0) + }}> + 0 && (!lastMentionTimestamp || lastMentionTimestamp < mentionList[0]?.timestamp) ? 'var(--unread)' : 'white' + }} /> + + + + + ); +}; diff --git a/src/components/Chat/MentionList.tsx b/src/components/Chat/MentionList.tsx new file mode 100644 index 0000000..85f6890 --- /dev/null +++ b/src/components/Chat/MentionList.tsx @@ -0,0 +1,69 @@ +import React, { + forwardRef, useEffect, useImperativeHandle, + useState, + } from 'react' + + export default forwardRef((props, ref) => { + const [selectedIndex, setSelectedIndex] = useState(0) + + const selectItem = index => { + const item = props.items[index] + + if (item) { + props.command(item) + } + } + + const upHandler = () => { + setSelectedIndex((selectedIndex + props.items.length - 1) % props.items.length) + } + + const downHandler = () => { + setSelectedIndex((selectedIndex + 1) % props.items.length) + } + + const enterHandler = () => { + selectItem(selectedIndex) + } + + useEffect(() => setSelectedIndex(0), [props.items]) + + useImperativeHandle(ref, () => ({ + onKeyDown: ({ event }) => { + if (event.key === 'ArrowUp') { + upHandler() + return true + } + + if (event.key === 'ArrowDown') { + downHandler() + return true + } + + if (event.key === 'Enter') { + enterHandler() + return true + } + + return false + }, + })) + + return ( +
+ {props.items.length + ? props.items.map((item, index) => ( + + )) + :
No result
+ } +
+ ) + }) + \ No newline at end of file diff --git a/src/components/Chat/MessageItem.tsx b/src/components/Chat/MessageItem.tsx index e304dc0..ed5492f 100644 --- a/src/components/Chat/MessageItem.tsx +++ b/src/components/Chat/MessageItem.tsx @@ -45,6 +45,12 @@ export const MessageItem = ({ return ( + <> + {message?.divide && ( +
+ Unread messages below +
+ )}
*/} {/* {!message.unread && Seen} */}
+ ); }; diff --git a/src/components/Chat/TipTap.tsx b/src/components/Chat/TipTap.tsx index 7c9ce5c..e436ed1 100644 --- a/src/components/Chat/TipTap.tsx +++ b/src/components/Chat/TipTap.tsx @@ -1,5 +1,5 @@ -import React, { useEffect, useRef, useState } from "react"; -import { EditorProvider, useCurrentEditor } from "@tiptap/react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; +import { EditorProvider, useCurrentEditor, useEditor } from "@tiptap/react"; import StarterKit from "@tiptap/starter-kit"; import { Color } from "@tiptap/extension-color"; import ListItem from "@tiptap/extension-list-item"; @@ -22,10 +22,28 @@ import RedoIcon from "@mui/icons-material/Redo"; import FormatHeadingIcon from "@mui/icons-material/FormatSize"; import DeveloperModeIcon from "@mui/icons-material/DeveloperMode"; import Compressor from "compressorjs"; - +import Mention from '@tiptap/extension-mention'; import ImageResize from "tiptap-extension-resize-image"; // Import the ResizeImage extension import { isMobile } from "../../App"; +import tippy from "tippy.js"; +import "tippy.js/dist/tippy.css"; +import Popover from '@mui/material/Popover'; +import List from '@mui/material/List'; +import ListItemMui from '@mui/material/ListItem'; +import ListItemButton from '@mui/material/ListItemButton'; +import ListItemText from '@mui/material/ListItemText'; +import { ReactRenderer } from '@tiptap/react' +import MentionList from './MentionList.jsx' +function textMatcher(doc, from) { + const textBeforeCursor = doc.textBetween(0, from, ' ', ' '); + const match = textBeforeCursor.match(/@[\w]*$/); // Match '@' followed by valid characters + if (!match) return null; + + const start = from - match[0].length; + const query = match[0]; + return { start, query }; +} const MenuBar = ({ setEditorRef, isChat }) => { const { editor } = useCurrentEditor(); const fileInputRef = useRef(null); @@ -279,8 +297,10 @@ export default ({ isFocusedParent, overrideMobile, customEditorHeight, + membersWithNames, + enableMentions }) => { - const [isFocused, setIsFocused] = useState(false); + const extensionsFiltered = isChat ? extensions.filter((item) => item?.name !== "image") : extensions; @@ -290,6 +310,32 @@ export default ({ setEditorRef(editorInstance); }; + // const users = [ + // { id: 1, label: 'Alice' }, + // { id: 2, label: 'Bob' }, + // { id: 3, label: 'Charlie' }, + // ]; + + + + const users = useMemo(()=> { + return (membersWithNames || [])?.map((item)=> { + return { + id: item, + label: item + } + }) + }, [membersWithNames]) + + + + + + const usersRef = useRef([]); + useEffect(() => { + usersRef.current = users; // Keep users up-to-date + }, [users]); + const handleFocus = () => { if (!isMobile) return; setIsFocusedParent(true); @@ -302,14 +348,91 @@ export default ({ } }; + const additionalExtensions = useMemo(()=> { + if(!enableMentions) return [] + return [ + Mention.configure({ + HTMLAttributes: { + class: 'mention', + }, + suggestion: { + items: ({ query }) => { + if (!query) return usersRef?.current; + return usersRef?.current?.filter((user) => + user.label.toLowerCase().includes(query.toLowerCase()) + ); + }, + render: () => { + let popup; // Reference to the Tippy.js instance + let component; + + return { + onStart: props => { + component = new ReactRenderer(MentionList, { + props, + editor: props.editor, + }) + + if (!props.clientRect) { + return + } + + popup = tippy('body', { + getReferenceClientRect: props.clientRect, + appendTo: () => document.body, + content: component.element, + showOnCreate: true, + interactive: true, + trigger: 'manual', + placement: 'bottom-start', + }) + }, + + onUpdate(props) { + component.updateProps(props) + + if (!props.clientRect) { + return + } + + popup[0].setProps({ + getReferenceClientRect: props.clientRect, + }) + }, + + onKeyDown(props) { + if (props.event.key === 'Escape') { + popup[0].hide() + + return true + } + + return component.ref?.onKeyDown(props) + }, + + onExit() { + popup[0].destroy() + component.destroy() + }, + } + }, + }, + }) + ] + }, [enableMentions]) + return ( +
) } - extensions={extensionsFiltered} + extensions={[...extensionsFiltered, ...additionalExtensions + ]} content={content} onCreate={({ editor }) => { editor.on("focus", handleFocus); // Listen for focus event @@ -323,10 +446,10 @@ export default ({ attributes: { class: "tiptap-prosemirror", style: - isMobile && + isMobile ? `overflow: auto; min-height: ${ customEditorHeight ? "200px" : "0px" - }; max-height:calc(100svh - ${customEditorHeight || "140px"})`, + }; max-height:calc(100svh - ${customEditorHeight || "140px"})`: `overflow: auto; max-height: 250px`, }, handleKeyDown(view, event) { if (!disableEnter && event.key === "Enter") { @@ -348,5 +471,7 @@ export default ({ }, }} /> +
+ ); }; diff --git a/src/components/Chat/styles.css b/src/components/Chat/styles.css index 7c65eb0..161eb13 100644 --- a/src/components/Chat/styles.css +++ b/src/components/Chat/styles.css @@ -71,6 +71,7 @@ margin: 1.5rem 0; padding: 0.75rem 1rem; outline: none; + text-wrap: wrap; } .tiptap pre code { @@ -123,3 +124,51 @@ .isReply p { font-size: 12px !important; } + + +.tiptap .mention { + box-decoration-break: clone; + color: lightblue; + padding: 0.1rem 0.3rem; +} + + +.unread-divider { + width: 90%; + color: white; + border-bottom: 1px solid white; + display: flex; + justify-content: center; + border-radius: 2px; +} + +.mention-item { + cursor: pointer; +} + +.dropdown-menu { + display: flex; + flex-direction: column; + gap: 0.1rem; + padding: 0.4rem; + position: relative; + max-height: 200px; + overflow: auto; + + button { + align-items: center; + background-color: transparent; + display: flex; + gap: 0.25rem; + text-align: left; + font-size: 16px; + width: 100%; + border: none; + color: white; + cursor: pointer; + &:hover, + &:hover.is-selected { + background-color: gray; + } + } +} \ No newline at end of file diff --git a/src/components/ContextMenuMentions.tsx b/src/components/ContextMenuMentions.tsx new file mode 100644 index 0000000..8042c1a --- /dev/null +++ b/src/components/ContextMenuMentions.tsx @@ -0,0 +1,130 @@ +import React, { useState, useRef, useMemo, useEffect } from "react"; +import { + ListItemIcon, + Menu, + MenuItem, + Typography, + styled, +} from "@mui/material"; + +import { executeEvent } from "../utils/events"; + +const CustomStyledMenu = styled(Menu)(({ theme }) => ({ + "& .MuiPaper-root": { + backgroundColor: "#f9f9f9", + borderRadius: "12px", + padding: theme.spacing(1), + boxShadow: "0 5px 15px rgba(0, 0, 0, 0.2)", + }, + "& .MuiMenuItem-root": { + fontSize: "14px", // Smaller font size for the menu item text + color: "#444", + transition: "0.3s background-color", + "&:hover": { + backgroundColor: "#f0f0f0", // Explicit hover state + }, + }, +})); + +export const ContextMenuMentions = ({ + children, + groupId, + getTimestampMention +}) => { + const [menuPosition, setMenuPosition] = useState(null); + const longPressTimeout = useRef(null); + const preventClick = useRef(false); // Flag to prevent click after long-press or right-click + + + + // Handle right-click (context menu) for desktop + const handleContextMenu = (event) => { + event.preventDefault(); + event.stopPropagation(); // Prevent parent click + + // Set flag to prevent any click event after right-click + preventClick.current = true; + + setMenuPosition({ + mouseX: event.clientX, + mouseY: event.clientY, + }); + }; + + // Handle long-press for mobile + const handleTouchStart = (event) => { + longPressTimeout.current = setTimeout(() => { + preventClick.current = true; // Prevent the next click after long-press + event.stopPropagation(); // Prevent parent click + setMenuPosition({ + mouseX: event.touches[0].clientX, + mouseY: event.touches[0].clientY, + }); + }, 500); // Long press duration + }; + + const handleTouchEnd = (event) => { + clearTimeout(longPressTimeout.current); + + if (preventClick.current) { + event.preventDefault(); + event.stopPropagation(); // Prevent synthetic click after long-press + preventClick.current = false; // Reset the flag + } + }; + + + const handleClose = (e) => { + e.preventDefault(); + e.stopPropagation(); + setMenuPosition(null); + }; + + const addTimestamp = ()=> { + window.sendMessage("addTimestampMention", { + timestamp: Date.now(), + groupId + }).then((res)=> { + getTimestampMention() + }).catch((error) => { + console.error("Failed to add timestamp:", error.message || "An error occurred"); + }); + } + + return ( +
+ {children} + + { + e.stopPropagation(); + }} + > + { + handleClose(e); + addTimestamp() + }} + > + + Unmark + + + +
+ ); +}; diff --git a/src/components/Group/Group.tsx b/src/components/Group/Group.tsx index 320fed6..ed60ea7 100644 --- a/src/components/Group/Group.tsx +++ b/src/components/Group/Group.tsx @@ -1874,17 +1874,17 @@ export const Group = ({ // getTimestampEnterChat(); }, 200); - window.sendMessage("addTimestampEnterChat", { - timestamp: Date.now(), - groupId: group.groupId, - }).catch((error) => { - console.error("Failed to add timestamp:", error.message || "An error occurred"); - }); + // window.sendMessage("addTimestampEnterChat", { + // timestamp: Date.now(), + // groupId: group.groupId, + // }).catch((error) => { + // console.error("Failed to add timestamp:", error.message || "An error occurred"); + // }); - setTimeout(() => { - getTimestampEnterChat(); - }, 200); + // setTimeout(() => { + // getTimestampEnterChat(); + // }, 200); }} @@ -2308,6 +2308,8 @@ export const Group = ({ triedToFetchSecretKey={triedToFetchSecretKey} myName={userInfo?.name} balance={balance} + getTimestampEnterChatParent={getTimestampEnterChat} + /> )} {firstSecretKeyInCreation && diff --git a/src/index.css b/src/index.css index aaa8836..b836ea0 100644 --- a/src/index.css +++ b/src/index.css @@ -40,6 +40,7 @@ body { margin: 0px; + overflow: hidden; } .image-container { diff --git a/src/memory-pow.wasm b/src/memory-pow.wasm new file mode 100644 index 0000000000000000000000000000000000000000..073b179cab68d0eabc80c9b6ba9590d7e59c2e4f GIT binary patch literal 3399 zcma)8O^h2w7Jk)LZMWV2X^%(AL?e{yPWTICoC!lh_)Qx{$?z8zmR*v=cG`B&j3;*Y z(6%Ru6pazWh(8HMk#>cIGzfum1o5-d0tYS}cW)~M7ovsT6A@exD*+1cb&nl;5(8!R zd-dM;{;FQPy1;EU833?p^#s5P)@9ujqKgALTO-nCYf7GR&tFY6y=D-eQ9$7d6$Il- z;+ABW*-t+&^e)&>Ygf@20Mau(`46TS9Oo2fbgFKn;Z)l}*aFZ`2d$-g80@`$-wLoT zey3US!lMfhd2X%lFCO>8pmp0`P_7B=n***@gXT)7?d=7TahzH!aF$%Z*6=t>qv*C> z$Mb7k$vVzrBdE9yr{cCe&UMFG^4yhJ5{SmDi)9rfwcSd?i^W9x2cdFc`lC!;lOW+{ z4i+P3^ej$Xvy|R`t|6w7IAw{RrIvTUu%Az%j!UIncn%E=6qrBeA?$S!9JC zX4mCJ&rC^X3aMBi4x*EgDVaV?Wu#=bXJxQb>;$@Ck;`bO$#l=kVW%)yO6JI2lNkyI zc6ZqVT!$W&ZmN<|H5q)-qoqgjii3C*$jqU0k(^nS_5$|u^5x`HQdyy`Lw5?Dk)!zp zB@(n@8G}oHgk}kB$4=Zc9o|v(nVMJWEIM_6A$VN@5I{pPq8Zpi49AX`!4c#Nwgmoy z599m~$dV%V!Kt{!VEtb>QNKWv_>RbbkwB6NjmY9HoUo}7vuUKe7>PF^P2Gy*KO{~Q zy2a0;2!a-<(B9K`x(gzus}>0M~fcD+o+Y>%cCJ56N6fJalRHm{t6|B>e{c)UT1 zwq~BdxoFA%M=`2yUxYq`CdUcAgVLwoN6{hiKqP*SqEF)8k+0wY(Q@t~)W9bisdLB< zk=d=3|BC)?)E_2&Dcas4Vh?K-F!?RcwPfxbO3G_U?xBbZFCo+L`6`j#cx4+w zmHq*Lq7)mCfJ*@DqY^^V*lf$8uzkc-`BiB+%s){WPQuoQ(eF2*>R1#@7_C+tYf38{ zAIN|`>*uxMr(d21=cg_~4$;0Q2j`7TkVnm^eiBHNi1b&Z2Ie;qqu;1s0{!UMkDrd@ zI745z*(S5$Ga6}pj>(Peaecp4!RBn&st9*sWVQWDf3JRA!_#2&zdIwZQ)+Bybub1W zp0P$8_Q;WV$+4yiz6<@;&|5RHNg7u<`Smea%WnM1Lf2pP=s=8K_+R6FU&>8mGmo`b z>#u3|iqbE@tyDs9l@-|axuVK*#@6XvfjzMm4~H!wnZXJNq$AM*Ug z_R^37mdJlladpWJqvhR!6*r&~r5JH-d0q{OJdC01D~-Arf*<%*59)r+I}LDqY9$P6 loht6VqEQ< { publicKey: uint8PublicKey, }; - const difficulty = 8; const tx = await createTransaction(18, keyPair, { timestamp: sendTimestamp, recipient: recipient, @@ -1074,15 +1074,9 @@ export const sendChatMessage = async (data, isFromExtension) => { isEncrypted: 1, isText: 1, }); - const path = `${import.meta.env.BASE_URL}memory-pow.wasm.full`; - - - const { nonce, chatBytesArray } = await computePow({ - chatBytes: tx.chatBytes, - path, - difficulty, - }); - + const chatBytes = tx.chatBytes; + const difficulty = 8; + const { nonce, chatBytesArray } = await performPowTask(chatBytes, difficulty); let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); if (_response?.error) { throw new Error(_response?.message); @@ -1102,7 +1096,6 @@ export const sendChatMessage = async (data, isFromExtension) => { publicKey: uint8PublicKey, }; - const difficulty = 8; const txBody = { timestamp: Date.now(), @@ -1121,14 +1114,9 @@ export const sendChatMessage = async (data, isFromExtension) => { // if (!hasEnoughBalance) { // throw new Error("Must have at least 4 QORT to send a chat message"); // } - const path = `${import.meta.env.BASE_URL}memory-pow.wasm.full`; - - - const { nonce, chatBytesArray } = await computePow({ - chatBytes: tx.chatBytes, - path, - difficulty, - }); + const chatBytes = tx.chatBytes; + const difficulty = 8; + const { nonce, chatBytesArray } = await performPowTask(chatBytes, difficulty); let _response = await signChatFunc(chatBytesArray, nonce, null, keyPair); if (_response?.error) { throw new Error(_response?.message); @@ -1401,6 +1389,10 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE throw new Error(errorMsg); } + const isGateway = await isRunningGateway() + + if(data?.coin === 'ARRR' && isGateway) throw new Error('Cannot view ARRR balance through the gateway. Please use your local node.') + const value = (await getPermission(`qAPPAutoWalletBalance-${appInfo?.name}-${data.coin}`)) || false; let skip = false; if (value) { @@ -1454,7 +1446,7 @@ export const getWalletBalance = async (data, bypassPermission?: boolean, isFromE case "BTC": _url = await createEndpoint(`/crosschain/btc/walletbalance`); - _body = parsedData.derivedMasterPublicKey; + _body = parsedData.btcPublicKey; break; case "LTC": _url = await createEndpoint(`/crosschain/ltc/walletbalance`); @@ -2095,6 +2087,7 @@ export const sendCoin = async (data, isFromExtension) => { text1: "Do you give this application permission to send coins?", text2: `To: ${recipient}`, highlightedText: `${amount} ${checkCoin}`, + fee: fee * QORT_DECIMALS }, isFromExtension); const { accepted } = resPermission; @@ -2119,7 +2112,7 @@ export const sendCoin = async (data, isFromExtension) => { const btcWalletBalanceDecimals = Number(btcWalletBalance) const btcAmountDecimals = Number(amount) * QORT_DECIMALS const fee = feePerByte * 500 // default 0.00050000 - if (btcAmountDecimals + (fee * QORT_DECIMALS) > btcWalletBalanceDecimals) { + if (btcAmountDecimals + fee > btcWalletBalanceDecimals) { throw new Error("INSUFFICIENT_FUNDS") } @@ -2127,7 +2120,7 @@ export const sendCoin = async (data, isFromExtension) => { text1: "Do you give this application permission to send coins?", text2: `To: ${recipient}`, highlightedText: `${amount} ${checkCoin}`, - fee: fee + foreignFee: `${fee} BTC` }, isFromExtension); const { accepted } = resPermission; @@ -2136,7 +2129,7 @@ export const sendCoin = async (data, isFromExtension) => { xprv58: xprv58, receivingAddress: recipient, bitcoinAmount: amount, - feePerByte: feePerByte * QORT_DECIMALS + feePerByte: feePerByte } const url = await createEndpoint(`/crosschain/btc/send`); @@ -2177,14 +2170,14 @@ export const sendCoin = async (data, isFromExtension) => { const ltcAmountDecimals = Number(amount) * QORT_DECIMALS const balance = (Number(ltcWalletBalance) / 1e8).toFixed(8) const fee = feePerByte * 1000 // default 0.00030000 - if (ltcAmountDecimals + (fee * QORT_DECIMALS) > ltcWalletBalanceDecimals) { + if (ltcAmountDecimals + fee > ltcWalletBalanceDecimals) { throw new Error("Insufficient Funds!") } const resPermission = await getUserPermission({ text1: "Do you give this application permission to send coins?", text2: `To: ${recipient}`, highlightedText: `${amount} ${checkCoin}`, - fee: fee + foreignFee: `${fee} LTC` }, isFromExtension); const { accepted } = resPermission; @@ -2194,7 +2187,7 @@ export const sendCoin = async (data, isFromExtension) => { xprv58: xprv58, receivingAddress: recipient, litecoinAmount: amount, - feePerByte: feePerByte * QORT_DECIMALS + feePerByte: feePerByte } const response = await fetch(url, { method: 'POST', @@ -2232,7 +2225,7 @@ export const sendCoin = async (data, isFromExtension) => { const dogeAmountDecimals = Number(amount) * QORT_DECIMALS const balance = (Number(dogeWalletBalance) / 1e8).toFixed(8) const fee = feePerByte * 5000 // default 0.05000000 - if (dogeAmountDecimals + (fee * QORT_DECIMALS) > dogeWalletBalanceDecimals) { + if (dogeAmountDecimals + fee > dogeWalletBalanceDecimals) { let errorMsg = "Insufficient Funds!" throw new Error(errorMsg) } @@ -2241,7 +2234,7 @@ export const sendCoin = async (data, isFromExtension) => { text1: "Do you give this application permission to send coins?", text2: `To: ${recipient}`, highlightedText: `${amount} ${checkCoin}`, - fee: fee + foreignFee: `${fee} DOGE` }, isFromExtension); const { accepted } = resPermission; @@ -2250,7 +2243,7 @@ export const sendCoin = async (data, isFromExtension) => { xprv58: xprv58, receivingAddress: recipient, dogecoinAmount: amount, - feePerByte: feePerByte * QORT_DECIMALS + feePerByte: feePerByte } const url = await createEndpoint(`/crosschain/doge/send`); @@ -2287,7 +2280,7 @@ export const sendCoin = async (data, isFromExtension) => { const dgbWalletBalanceDecimals = Number(dgbWalletBalance) const dgbAmountDecimals = Number(amount) * QORT_DECIMALS const fee = feePerByte * 500 // default 0.00005000 - if (dgbAmountDecimals + (fee * QORT_DECIMALS) > dgbWalletBalanceDecimals) { + if (dgbAmountDecimals + fee > dgbWalletBalanceDecimals) { let errorMsg = "Insufficient Funds!" throw new Error(errorMsg) } @@ -2296,7 +2289,7 @@ export const sendCoin = async (data, isFromExtension) => { text1: "Do you give this application permission to send coins?", text2: `To: ${recipient}`, highlightedText: `${amount} ${checkCoin}`, - fee: fee + foreignFee: `${fee} DGB` }, isFromExtension); const { accepted } = resPermission; @@ -2305,7 +2298,7 @@ export const sendCoin = async (data, isFromExtension) => { xprv58: xprv58, receivingAddress: recipient, digibyteAmount: amount, - feePerByte: feePerByte * QORT_DECIMALS + feePerByte: feePerByte } const url = await createEndpoint(`/crosschain/dgb/send`); @@ -2344,7 +2337,7 @@ export const sendCoin = async (data, isFromExtension) => { const rvnAmountDecimals = Number(amount) * QORT_DECIMALS const balance = (Number(rvnWalletBalance) / 1e8).toFixed(8) const fee = feePerByte * 500 // default 0.00562500 - if (rvnAmountDecimals + (fee * QORT_DECIMALS) > rvnWalletBalanceDecimals) { + if (rvnAmountDecimals + fee > rvnWalletBalanceDecimals) { let errorMsg = "Insufficient Funds!" throw new Error(errorMsg) @@ -2354,7 +2347,7 @@ export const sendCoin = async (data, isFromExtension) => { text1: "Do you give this application permission to send coins?", text2: `To: ${recipient}`, highlightedText: `${amount} ${checkCoin}`, - fee: fee + foreignFee: `${fee} RVN` }, isFromExtension); const { accepted } = resPermission; @@ -2363,7 +2356,7 @@ export const sendCoin = async (data, isFromExtension) => { xprv58: xprv58, receivingAddress: recipient, ravencoinAmount: amount, - feePerByte: feePerByte * QORT_DECIMALS + feePerByte: feePerByte } const url = await createEndpoint(`/crosschain/rvn/send`); @@ -2399,7 +2392,7 @@ export const sendCoin = async (data, isFromExtension) => { const arrrWalletBalanceDecimals = Number(arrrWalletBalance) const arrrAmountDecimals = Number(amount) * QORT_DECIMALS const fee = 0.00010000 - if (arrrAmountDecimals + (fee * QORT_DECIMALS) > arrrWalletBalanceDecimals) { + if (arrrAmountDecimals + fee > arrrWalletBalanceDecimals) { let errorMsg = "Insufficient Funds!" throw new Error(errorMsg) } @@ -2408,7 +2401,7 @@ export const sendCoin = async (data, isFromExtension) => { text1: "Do you give this application permission to send coins?", text2: `To: ${recipient}`, highlightedText: `${amount} ${checkCoin}`, - fee: fee + foreignFee: `${fee} ARRR` }, isFromExtension); const { accepted } = resPermission; diff --git a/vite.config.ts b/vite.config.ts index afd40ab..903efbb 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -4,14 +4,12 @@ import react from '@vitejs/plugin-react'; // Import path module for resolving file paths import { resolve } from 'path'; import fixReactVirtualized from 'esbuild-plugin-react-virtualized' - +import wasm from 'vite-plugin-wasm'; +import topLevelAwait from 'vite-plugin-top-level-await'; export default defineConfig({ - test: { - environment: 'jsdom', - globals: true, - setupFiles: ['./src/test/setup.ts'] - }, - plugins: [react()], + + assetsInclude: ['**/*.wasm'], + plugins: [react(), wasm(), topLevelAwait()], build: { rollupOptions: { // Specify multiple entry points for Rollup