From c9f7d942a0b3d11fd05ab0f8dca0a96b104679cd Mon Sep 17 00:00:00 2001 From: Ren Amamiya <123083837+reyamir@users.noreply.github.com> Date: Sat, 17 Jun 2023 12:28:46 +0700 Subject: [PATCH] refactor channel --- package.json | 2 +- pnpm-lock.yaml | 98 +++++------ .../20230425024708_add_default_channels.sql | 19 --- .../20230617003135_add_channel_messages.sql | 15 ++ src-tauri/src/main.rs | 6 + src/app/channel/components/messageList.tsx | 72 --------- src/app/channel/components/messages/form.tsx | 41 ++--- .../components/messages/hideButton.tsx | 2 +- src/app/channel/components/messages/item.tsx | 15 +- .../components/messages/muteButton.tsx | 2 +- .../components/messages/replyButton.tsx | 2 +- src/app/channel/pages/index.page.tsx | 153 +++++++----------- src/app/chat/pages/index.page.tsx | 4 +- src/app/prefetch/pages/index.page.tsx | 50 +++++- src/libs/ndk.tsx | 2 +- src/libs/storage.tsx | 25 +++ src/shared/notes/preview/image.tsx | 2 +- src/shared/notes/preview/link.tsx | 2 +- src/shared/notes/preview/video.tsx | 2 +- src/stores/channels.tsx | 39 +++-- src/utils/parser.tsx | 13 +- 21 files changed, 275 insertions(+), 291 deletions(-) create mode 100644 src-tauri/migrations/20230617003135_add_channel_messages.sql delete mode 100644 src/app/channel/components/messageList.tsx diff --git a/package.json b/package.json index 07f587d1..0d610f32 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dependencies": { "@floating-ui/react": "^0.23.1", "@headlessui/react": "^1.7.15", - "@nostr-dev-kit/ndk": "^0.5.2", + "@nostr-dev-kit/ndk": "^0.5.3", "@tanstack/react-virtual": "3.0.0-beta.54", "@tauri-apps/api": "^1.4.0", "@vidstack/react": "^0.4.5", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ea43b9ed..41456656 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -8,8 +8,8 @@ dependencies: specifier: ^1.7.15 version: 1.7.15(react-dom@18.2.0)(react@18.2.0) '@nostr-dev-kit/ndk': - specifier: ^0.5.2 - version: 0.5.2(typescript@4.9.5) + specifier: ^0.5.3 + version: 0.5.3(typescript@4.9.5) '@tanstack/react-virtual': specifier: 3.0.0-beta.54 version: 3.0.0-beta.54(react@18.2.0) @@ -370,13 +370,13 @@ packages: requiresBuild: true optional: true - /@eslint-community/eslint-utils@4.4.0(eslint@8.42.0): + /@eslint-community/eslint-utils@4.4.0(eslint@8.43.0): resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 dependencies: - eslint: 8.42.0 + eslint: 8.43.0 eslint-visitor-keys: 3.4.1 dev: false @@ -402,8 +402,8 @@ packages: - supports-color dev: false - /@eslint/js@8.42.0: - resolution: {integrity: sha512-6SWlXpWU5AvId8Ac7zjzmIOqMOba/JWY8XZ4A7q7Gn1Vlfg/SFFIlrtHXt9nPn4op9ZPAkl91Jao+QQv3r/ukw==} + /@eslint/js@8.43.0: + resolution: {integrity: sha512-s2UHCoiXfxMvmfzqoN+vrQ84ahUSYde9qNO1MdxmoEhyHWsfmwOpFlwYV+ePJEVc7gFnATGUi376WowX1N7tFg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false @@ -571,20 +571,20 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 - /@nostr-dev-kit/ndk@0.5.2(typescript@4.9.5): - resolution: {integrity: sha512-ZTcEGHxe/Yoazr2SHB+6wQ/1ZkLvaJVdEFNOwg2Ll1lD6C7ttnP/rPD1ex17bQFaU//4yvsdgEREDJqo+mLedw==} + /@nostr-dev-kit/ndk@0.5.3(typescript@4.9.5): + resolution: {integrity: sha512-GLmuAoor4oMxxKjFeZ4viHR9XEI61m0wBm78vTUzY1Ev+bBdDzorv6heBz7TWmQirtoJ32r/zIgWdzHsHC6h3A==} dependencies: '@noble/hashes': 1.3.1 '@noble/secp256k1': 2.0.0 '@scure/base': 1.1.1 - '@typescript-eslint/eslint-plugin': 5.59.11(@typescript-eslint/parser@5.59.11)(eslint@8.42.0)(typescript@4.9.5) - '@typescript-eslint/parser': 5.59.11(eslint@8.42.0)(typescript@4.9.5) + '@typescript-eslint/eslint-plugin': 5.59.11(@typescript-eslint/parser@5.59.11)(eslint@8.43.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.59.11(eslint@8.43.0)(typescript@4.9.5) debug: 4.3.4 esbuild: 0.17.19 esbuild-plugin-alias: 0.2.1 - eslint: 8.42.0 - eslint-config-prettier: 8.8.0(eslint@8.42.0) - eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.11)(eslint@8.42.0) + eslint: 8.43.0 + eslint-config-prettier: 8.8.0(eslint@8.43.0) + eslint-plugin-import: 2.27.5(@typescript-eslint/parser@5.59.11)(eslint@8.43.0) esm-loader-typescript: 1.0.4 eventemitter3: 5.0.1 light-bolt11-decoder: 3.0.0 @@ -1023,7 +1023,7 @@ packages: resolution: {integrity: sha512-W8F4eoTIvzXeNrT3JroQPimZLXnlJA8smYygHZUKFPVoYwgs/OhJkA1VBhL3iSs57OQkuINqHlY4SmMT5wtnJg==} dev: true - /@typescript-eslint/eslint-plugin@5.59.11(@typescript-eslint/parser@5.59.11)(eslint@8.42.0)(typescript@4.9.5): + /@typescript-eslint/eslint-plugin@5.59.11(@typescript-eslint/parser@5.59.11)(eslint@8.43.0)(typescript@4.9.5): resolution: {integrity: sha512-XxuOfTkCUiOSyBWIvHlUraLw/JT/6Io1365RO6ZuI88STKMavJZPNMU0lFcUTeQXEhHiv64CbxYxBNoDVSmghg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1035,12 +1035,12 @@ packages: optional: true dependencies: '@eslint-community/regexpp': 4.5.1 - '@typescript-eslint/parser': 5.59.11(eslint@8.42.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.59.11(eslint@8.43.0)(typescript@4.9.5) '@typescript-eslint/scope-manager': 5.59.11 - '@typescript-eslint/type-utils': 5.59.11(eslint@8.42.0)(typescript@4.9.5) - '@typescript-eslint/utils': 5.59.11(eslint@8.42.0)(typescript@4.9.5) + '@typescript-eslint/type-utils': 5.59.11(eslint@8.43.0)(typescript@4.9.5) + '@typescript-eslint/utils': 5.59.11(eslint@8.43.0)(typescript@4.9.5) debug: 4.3.4 - eslint: 8.42.0 + eslint: 8.43.0 grapheme-splitter: 1.0.4 ignore: 5.2.4 natural-compare-lite: 1.4.0 @@ -1051,7 +1051,7 @@ packages: - supports-color dev: false - /@typescript-eslint/parser@5.59.11(eslint@8.42.0)(typescript@4.9.5): + /@typescript-eslint/parser@5.59.11(eslint@8.43.0)(typescript@4.9.5): resolution: {integrity: sha512-s9ZF3M+Nym6CAZEkJJeO2TFHHDsKAM3ecNkLuH4i4s8/RCPnF5JRip2GyviYkeEAcwGMJxkqG9h2dAsnA1nZpA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1065,7 +1065,7 @@ packages: '@typescript-eslint/types': 5.59.11 '@typescript-eslint/typescript-estree': 5.59.11(typescript@4.9.5) debug: 4.3.4 - eslint: 8.42.0 + eslint: 8.43.0 typescript: 4.9.5 transitivePeerDependencies: - supports-color @@ -1079,7 +1079,7 @@ packages: '@typescript-eslint/visitor-keys': 5.59.11 dev: false - /@typescript-eslint/type-utils@5.59.11(eslint@8.42.0)(typescript@4.9.5): + /@typescript-eslint/type-utils@5.59.11(eslint@8.43.0)(typescript@4.9.5): resolution: {integrity: sha512-LZqVY8hMiVRF2a7/swmkStMYSoXMFlzL6sXV6U/2gL5cwnLWQgLEG8tjWPpaE4rMIdZ6VKWwcffPlo1jPfk43g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -1090,9 +1090,9 @@ packages: optional: true dependencies: '@typescript-eslint/typescript-estree': 5.59.11(typescript@4.9.5) - '@typescript-eslint/utils': 5.59.11(eslint@8.42.0)(typescript@4.9.5) + '@typescript-eslint/utils': 5.59.11(eslint@8.43.0)(typescript@4.9.5) debug: 4.3.4 - eslint: 8.42.0 + eslint: 8.43.0 tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: @@ -1125,19 +1125,19 @@ packages: - supports-color dev: false - /@typescript-eslint/utils@5.59.11(eslint@8.42.0)(typescript@4.9.5): + /@typescript-eslint/utils@5.59.11(eslint@8.43.0)(typescript@4.9.5): resolution: {integrity: sha512-didu2rHSOMUdJThLk4aZ1Or8IcO3HzCw/ZvEjTTIfjIrcdd5cvSIwwDy2AOlE7htSNp7QIZ10fLMyRCveesMLg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.42.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.43.0) '@types/json-schema': 7.0.12 '@types/semver': 7.5.0 '@typescript-eslint/scope-manager': 5.59.11 '@typescript-eslint/types': 5.59.11 '@typescript-eslint/typescript-estree': 5.59.11(typescript@4.9.5) - eslint: 8.42.0 + eslint: 8.43.0 eslint-scope: 5.1.1 semver: 7.5.2 transitivePeerDependencies: @@ -1185,16 +1185,16 @@ packages: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} dev: false - /acorn-jsx@5.3.2(acorn@8.8.2): + /acorn-jsx@5.3.2(acorn@8.9.0): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.8.2 + acorn: 8.9.0 dev: false - /acorn@8.8.2: - resolution: {integrity: sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==} + /acorn@8.9.0: + resolution: {integrity: sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==} engines: {node: '>=0.4.0'} hasBin: true @@ -1415,7 +1415,7 @@ packages: hasBin: true dependencies: caniuse-lite: 1.0.30001503 - electron-to-chromium: 1.4.431 + electron-to-chromium: 1.4.433 node-releases: 2.0.12 update-browserslist-db: 1.0.11(browserslist@4.21.9) dev: true @@ -1780,8 +1780,8 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - /electron-to-chromium@1.4.431: - resolution: {integrity: sha512-m232JTVmCawA2vG+1azVxhKZ9Sv1Q//xxNv5PkP5rWxGgQE8c3CiZFrh8Xnp+d1NmNxlu3QQrGIfdeW5TtXX5w==} + /electron-to-chromium@1.4.433: + resolution: {integrity: sha512-MGO1k0w1RgrfdbLVwmXcDhHHuxCn2qRgR7dYsJvWFKDttvYPx6FNzCGG0c/fBBvzK2LDh3UV7Tt9awnHnvAAUQ==} dev: true /emoji-regex@8.0.0: @@ -1951,13 +1951,13 @@ packages: engines: {node: '>=10'} dev: false - /eslint-config-prettier@8.8.0(eslint@8.42.0): + /eslint-config-prettier@8.8.0(eslint@8.43.0): resolution: {integrity: sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.42.0 + eslint: 8.43.0 dev: false /eslint-formatter-pretty@4.1.0: @@ -1984,7 +1984,7 @@ packages: - supports-color dev: false - /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.59.11)(eslint-import-resolver-node@0.3.7)(eslint@8.42.0): + /eslint-module-utils@2.8.0(@typescript-eslint/parser@5.59.11)(eslint-import-resolver-node@0.3.7)(eslint@8.43.0): resolution: {integrity: sha512-aWajIYfsqCKRDgUfjEXNN/JlrzauMuSEy5sbd7WXbtW3EH6A6MpwEh42c7qD+MqQo9QMJ6fWLAeIJynx0g6OAw==} engines: {node: '>=4'} peerDependencies: @@ -2005,15 +2005,15 @@ packages: eslint-import-resolver-webpack: optional: true dependencies: - '@typescript-eslint/parser': 5.59.11(eslint@8.42.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.59.11(eslint@8.43.0)(typescript@4.9.5) debug: 3.2.7 - eslint: 8.42.0 + eslint: 8.43.0 eslint-import-resolver-node: 0.3.7 transitivePeerDependencies: - supports-color dev: false - /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.11)(eslint@8.42.0): + /eslint-plugin-import@2.27.5(@typescript-eslint/parser@5.59.11)(eslint@8.43.0): resolution: {integrity: sha512-LmEt3GVofgiGuiE+ORpnvP+kAm3h6MLZJ4Q5HCyHADofsb4VzXFsRiWj3c0OFiV+3DWFh0qg3v9gcPlfc3zRow==} engines: {node: '>=4'} peerDependencies: @@ -2023,15 +2023,15 @@ packages: '@typescript-eslint/parser': optional: true dependencies: - '@typescript-eslint/parser': 5.59.11(eslint@8.42.0)(typescript@4.9.5) + '@typescript-eslint/parser': 5.59.11(eslint@8.43.0)(typescript@4.9.5) array-includes: 3.1.6 array.prototype.flat: 1.3.1 array.prototype.flatmap: 1.3.1 debug: 3.2.7 doctrine: 2.1.0 - eslint: 8.42.0 + eslint: 8.43.0 eslint-import-resolver-node: 0.3.7 - eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.11)(eslint-import-resolver-node@0.3.7)(eslint@8.42.0) + eslint-module-utils: 2.8.0(@typescript-eslint/parser@5.59.11)(eslint-import-resolver-node@0.3.7)(eslint@8.43.0) has: 1.0.3 is-core-module: 2.12.1 is-glob: 4.0.3 @@ -2071,15 +2071,15 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: false - /eslint@8.42.0: - resolution: {integrity: sha512-ulg9Ms6E1WPf67PHaEY4/6E2tEn5/f7FXGzr3t9cBMugOmf1INYvuUwwh1aXQN4MfJ6a5K2iNwP3w4AColvI9A==} + /eslint@8.43.0: + resolution: {integrity: sha512-aaCpf2JqqKesMFGgmRPessmVKjcGXqdlAYLLC3THM8t5nBRZRQ+st5WM/hoJXkdioEXLLbXgclUpM0TXo5HX5Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@8.42.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@8.43.0) '@eslint-community/regexpp': 4.5.1 '@eslint/eslintrc': 2.0.3 - '@eslint/js': 8.42.0 + '@eslint/js': 8.43.0 '@humanwhocodes/config-array': 0.11.10 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 @@ -2132,8 +2132,8 @@ packages: resolution: {integrity: sha512-7OASN1Wma5fum5SrNhFMAMJxOUAbhyfQ8dQ//PJaJbNw0URTPWqIghHWt1MmAANKhHZIYOHruW4Kw4ruUWOdGw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.8.2 - acorn-jsx: 5.3.2(acorn@8.8.2) + acorn: 8.9.0 + acorn-jsx: 5.3.2(acorn@8.9.0) eslint-visitor-keys: 3.4.1 dev: false @@ -4686,7 +4686,7 @@ packages: '@brillout/json-serializer': 0.5.3 '@brillout/picocolors': 1.0.4 '@brillout/vite-plugin-import-build': 0.2.18 - acorn: 8.8.2 + acorn: 8.9.0 cac: 6.7.14 es-module-lexer: 0.10.5 esbuild: 0.17.19 diff --git a/src-tauri/migrations/20230425024708_add_default_channels.sql b/src-tauri/migrations/20230425024708_add_default_channels.sql index eff03bf9..3f7f42d9 100644 --- a/src-tauri/migrations/20230425024708_add_default_channels.sql +++ b/src-tauri/migrations/20230425024708_add_default_channels.sql @@ -18,25 +18,6 @@ VALUES 1681898574 ); -INSERT -OR IGNORE INTO channels ( - event_id, - pubkey, - name, - about, - picture, - created_at -) -VALUES - ( - "42224859763652914db53052103f0b744df79dfc4efef7e950fc0802fc3df3c5", - "460c25e682fda7832b52d1f22d3d22b3176d972f60dcdc3212ed8c92ef85065c", - "Amethyst Users", - "General discussion about the Amethyst Nostr client for Android", - "https://nostr.build/i/5970.png", - 1674092111 - ); - INSERT OR IGNORE INTO channels ( event_id, diff --git a/src-tauri/migrations/20230617003135_add_channel_messages.sql b/src-tauri/migrations/20230617003135_add_channel_messages.sql new file mode 100644 index 00000000..a9486778 --- /dev/null +++ b/src-tauri/migrations/20230617003135_add_channel_messages.sql @@ -0,0 +1,15 @@ +-- Add migration script here +CREATE TABLE + channel_messages ( + id INTEGER NOT NULL PRIMARY KEY, + channel_id TEXT NOT NULL, + event_id TEXT NOT NULL UNIQUE, + pubkey TEXT NOT NULL, + kind INTEGER NOT NULL, + content TEXT NOT NULL, + tags JSON, + mute BOOLEAN DEFAULT 0, + hide BOOLEAN DEFAULT 0, + created_at INTEGER NOT NULL, + FOREIGN KEY (channel_id) REFERENCES channels (event_id) + ); \ No newline at end of file diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index d1fbfa89..007543f1 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -93,6 +93,12 @@ fn main() { sql: include_str!("../migrations/20230521092300_add_block_model.sql"), kind: MigrationKind::Up, }, + Migration { + version: 20230617003135, + description: "add channel messages", + sql: include_str!("../migrations/20230617003135_add_channel_messages.sql"), + kind: MigrationKind::Up, + }, ], ) .build(), diff --git a/src/app/channel/components/messageList.tsx b/src/app/channel/components/messageList.tsx deleted file mode 100644 index adbe1b8c..00000000 --- a/src/app/channel/components/messageList.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { ChannelMessageItem } from "@app/channel/components/messages/item"; -import { useChannelMessages } from "@stores/channels"; -import { getHourAgo } from "@utils/date"; -import { useCallback, useRef } from "react"; -import { Virtuoso } from "react-virtuoso"; - -export function ChannelMessageList() { - const now = useRef(new Date()); - const virtuosoRef = useRef(null); - - const messages = useChannelMessages((state: any) => state.messages); - - const itemContent: any = useCallback( - (index: string | number) => { - return ; - }, - [messages], - ); - - const computeItemKey = useCallback( - (index: string | number) => { - return messages[index].id; - }, - [messages], - ); - - return ( -
- ( -
-
-
-
-
-
- {getHourAgo(24, now.current).toLocaleDateString("en-US", { - weekday: "long", - year: "numeric", - month: "long", - day: "numeric", - })} -
-
-
- ), - EmptyPlaceholder: () => ( -
-

- Nothing to see here yet -

-

- Be the first to share a message in this channel. -

-
- ), - }} - computeItemKey={computeItemKey} - initialTopMostItemIndex={messages.length - 1} - alignToBottom={true} - followOutput={true} - overscan={50} - increaseViewportBy={{ top: 200, bottom: 200 }} - className="scrollbar-hide h-full w-full overflow-y-auto" - /> -
- ); -} diff --git a/src/app/channel/components/messages/form.tsx b/src/app/channel/components/messages/form.tsx index a107521b..c764771b 100644 --- a/src/app/channel/components/messages/form.tsx +++ b/src/app/channel/components/messages/form.tsx @@ -1,6 +1,7 @@ import { UserReply } from "@app/channel/components/messages/userReply"; import { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk"; -import { CancelIcon } from "@shared/icons"; +import { CancelIcon, EnterIcon } from "@shared/icons"; +import { MediaUploader } from "@shared/mediaUploader"; import { RelayContext } from "@shared/relayProvider"; import { useActiveAccount } from "@stores/accounts"; import { useChannelMessages } from "@stores/channels"; @@ -17,7 +18,7 @@ export function ChannelMessageForm({ channelID }: { channelID: string }) { state.closeReply, ]); - const submitEvent = () => { + const submit = () => { let tags: string[][]; if (replyTo.id !== null) { @@ -51,7 +52,7 @@ export function ChannelMessageForm({ channelID }: { channelID: string }) { const handleEnterPress = (e) => { if (e.key === "Enter" && !e.shiftKey) { e.preventDefault(); - submitEvent(); + submit(); } }; @@ -60,11 +61,7 @@ export function ChannelMessageForm({ channelID }: { channelID: string }) { }; return ( -
+
{replyTo.id && (
@@ -92,23 +89,19 @@ export function ChannelMessageForm({ channelID }: { channelID: string }) { placeholder="Message" className={`relative ${ replyTo.id ? "h-36 pt-16" : "h-24 pt-3" - } w-full resize-none rounded-lg border border-black/5 px-3.5 pb-3 text-base shadow-input shadow-black/5 !outline-none placeholder:text-zinc-400 dark:bg-zinc-800 dark:text-white dark:shadow-black/10 dark:placeholder:text-zinc-500`} + } w-full resize-none rounded-md px-5 !outline-none bg-zinc-800 placeholder:text-zinc-500`} /> -
-
-
-
-
-
- -
+
+
+ +
diff --git a/src/app/channel/components/messages/hideButton.tsx b/src/app/channel/components/messages/hideButton.tsx index 0cbd0d0e..3b62ad66 100644 --- a/src/app/channel/components/messages/hideButton.tsx +++ b/src/app/channel/components/messages/hideButton.tsx @@ -53,7 +53,7 @@ export function MessageHideButton({ id }: { id: string }) { onClick={openModal} className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-800" > - + diff --git a/src/app/channel/components/messages/item.tsx b/src/app/channel/components/messages/item.tsx index 8dd8cd9e..db86dea8 100644 --- a/src/app/channel/components/messages/item.tsx +++ b/src/app/channel/components/messages/item.tsx @@ -2,22 +2,22 @@ import { MessageHideButton } from "@app/channel/components/messages/hideButton"; import { MessageMuteButton } from "@app/channel/components/messages/muteButton"; import { MessageReplyButton } from "@app/channel/components/messages/replyButton"; import { ChannelMessageUser } from "@app/channel/components/messages/user"; -import { NDKEvent } from "@nostr-dev-kit/ndk"; import { MentionNote } from "@shared/notes/mentions/note"; import { ImagePreview } from "@shared/notes/preview/image"; +import { LinkPreview } from "@shared/notes/preview/link"; import { VideoPreview } from "@shared/notes/preview/video"; import { parser } from "@utils/parser"; -import { useMemo } from "react"; +import { LumeEvent } from "@utils/types"; -export function ChannelMessageItem({ data }: { data: NDKEvent }) { - const content = useMemo(() => parser(data), [data]); +export function ChannelMessageItem({ data }: { data: LumeEvent }) { + const content = parser(data); return (
-

+

{content.parsed}

{Array.isArray(content.images) && content.images.length ? ( @@ -30,6 +30,11 @@ export function ChannelMessageItem({ data }: { data: NDKEvent }) { ) : ( <> )} + {Array.isArray(content.links) && content.links.length ? ( + + ) : ( + <> + )} {Array.isArray(content.notes) && content.notes.length ? ( content.notes.map((note: string) => ( diff --git a/src/app/channel/components/messages/muteButton.tsx b/src/app/channel/components/messages/muteButton.tsx index aae22fea..11e34d6a 100644 --- a/src/app/channel/components/messages/muteButton.tsx +++ b/src/app/channel/components/messages/muteButton.tsx @@ -53,7 +53,7 @@ export function MessageMuteButton({ pubkey }: { pubkey: string }) { onClick={() => openModal()} className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-800" > - + diff --git a/src/app/channel/components/messages/replyButton.tsx b/src/app/channel/components/messages/replyButton.tsx index 3f3f7fc3..e88bf952 100644 --- a/src/app/channel/components/messages/replyButton.tsx +++ b/src/app/channel/components/messages/replyButton.tsx @@ -20,7 +20,7 @@ export function MessageReplyButton({ onClick={() => createReply()} className="inline-flex h-7 w-7 items-center justify-center rounded hover:bg-zinc-800" > - + ); diff --git a/src/app/channel/pages/index.page.tsx b/src/app/channel/pages/index.page.tsx index c9c1e879..0fdaf82a 100644 --- a/src/app/channel/pages/index.page.tsx +++ b/src/app/channel/pages/index.page.tsx @@ -4,95 +4,73 @@ import { ChannelMessageForm } from "@app/channel/components/messages/form"; import { ChannelMetadata } from "@app/channel/components/metadata"; import { RelayContext } from "@shared/relayProvider"; import { useChannelMessages } from "@stores/channels"; -import { useVirtualizer } from "@tanstack/react-virtual"; -import { dateToUnix, getHourAgo } from "@utils/date"; +import { dateToUnix } from "@utils/date"; import { usePageContext } from "@utils/hooks/usePageContext"; +import { LumeEvent } from "@utils/types"; import { useCallback, useContext, useEffect, useRef } from "react"; +import { Virtuoso } from "react-virtuoso"; import useSWRSubscription from "swr/subscription"; -const now = new Date(); -const since = dateToUnix(getHourAgo(24, now)); - export function Page() { const ndk = useContext(RelayContext); const pageContext = usePageContext(); + const virtuosoRef = useRef(null); const searchParams: any = pageContext.urlParsed.search; const channelID = searchParams.id; - const [messages, addMessage, fetchMessages, clearMessages]: any = + const [messages, fetchMessages, addMessage, clearMessages] = useChannelMessages((state: any) => [ state.messages, - state.addMessage, state.fetch, + state.add, state.clear, ]); - useSWRSubscription(["channelMessagesSubscribe", channelID], () => { - // subscribe to channel - const sub = ndk.subscribe({ - "#e": [channelID], - kinds: [42], - since: dateToUnix(), - }); + useSWRSubscription( + channelID ? ["channelMessagesSubscribe", channelID] : null, + () => { + // subscribe to channel + const sub = ndk.subscribe( + { + "#e": [channelID], + kinds: [42], + since: dateToUnix(), + }, + { closeOnEose: false }, + ); - sub.addListener("event", (event) => { - addMessage(event); - }); + sub.addListener("event", (event: LumeEvent) => { + addMessage(channelID, event); + }); - return () => { - sub.stop(); - }; - }); + return () => { + sub.stop(); + }; + }, + ); useEffect(() => { - fetchMessages(ndk, channelID, since); + fetchMessages(channelID); + return () => { clearMessages(); }; }, [fetchMessages]); - const count = messages.length; - const reverseIndex = useCallback((index) => count - 1 - index, [count]); - const parentRef = useRef(); - const virtualizerRef = useRef(null); + const itemContent: any = useCallback( + (index: string | number) => { + return ; + }, + [messages], + ); - if ( - virtualizerRef.current && - count !== virtualizerRef.current.options.count - ) { - const delta = count - virtualizerRef.current.options.count; - const nextOffset = virtualizerRef.current.scrollOffset + delta * 200; - - virtualizerRef.current.scrollOffset = nextOffset; - virtualizerRef.current.scrollToOffset(nextOffset, { align: "start" }); - } - - const virtualizer = useVirtualizer({ - count, - getScrollElement: () => parentRef.current, - estimateSize: () => 200, - getItemKey: useCallback( - (index) => messages[reverseIndex(index)].id, - [messages, reverseIndex], - ), - overscan: 5, - scrollMargin: 50, - }); - - useEffect(() => { - virtualizerRef.current = virtualizer; - }, []); - - const items = virtualizer.getVirtualItems(); - - const [paddingTop, paddingBottom] = - items.length > 0 - ? [ - Math.max(0, items[0].start - virtualizer.options.scrollMargin), - Math.max(0, virtualizer.getTotalSize() - items[items.length - 1].end), - ] - : [0, 0]; + const computeItemKey = useCallback( + (index: string | number) => { + return messages[index].event_id; + }, + [messages], + ); return (
@@ -105,40 +83,23 @@ export function Page() {
-
- {!messages ? ( -

Loading...

- ) : ( -
- {items.map((item) => { - const index = reverseIndex(item.index); - const message = messages[index]; - - return ( -
- -
- ); - })} -
- )} -
-
+ {!messages ? ( +

Loading...

+ ) : ( + + )} +
diff --git a/src/app/chat/pages/index.page.tsx b/src/app/chat/pages/index.page.tsx index 764d7b5b..ac12e41b 100644 --- a/src/app/chat/pages/index.page.tsx +++ b/src/app/chat/pages/index.page.tsx @@ -18,11 +18,11 @@ export function Page() { const searchParams: any = pageContext.urlParsed.search; const pubkey = searchParams.pubkey; - const [fetchMessages, clear] = useChatMessages((state: any) => [ + const [add, fetchMessages, clear] = useChatMessages((state: any) => [ + state.add, state.fetch, state.clear, ]); - const add = useChatMessages((state: any) => state.add); useSWRSubscription(account !== pubkey ? ["chat", pubkey] : null, () => { const sub = ndk.subscribe({ diff --git a/src/app/prefetch/pages/index.page.tsx b/src/app/prefetch/pages/index.page.tsx index 69a9d1d0..37b30df4 100644 --- a/src/app/prefetch/pages/index.page.tsx +++ b/src/app/prefetch/pages/index.page.tsx @@ -1,5 +1,11 @@ import { prefetchEvents } from "@libs/ndk"; -import { countTotalNotes, createChat, createNote } from "@libs/storage"; +import { + countTotalNotes, + createChannelMessage, + createChat, + createNote, + getChannels, +} from "@libs/storage"; import { NDKFilter } from "@nostr-dev-kit/ndk"; import { LumeIcon } from "@shared/icons"; import { RelayContext } from "@shared/relayProvider"; @@ -98,12 +104,52 @@ export function Page() { } } + async function fetchChannelMessages() { + try { + const ids = []; + const channels: any = await getChannels(10, 0); + channels.forEach((channel) => { + ids.push(channel.event_id); + }); + + const since = + lastLogin === 0 ? dateToUnix(getHourAgo(48, now.current)) : lastLogin; + + const filter: NDKFilter = { + "#e": ids, + kinds: [42], + since: since, + }; + + const events = await prefetchEvents(ndk, filter); + events.forEach((event) => { + const channel_id = event.tags[0][1]; + if (channel_id) { + createChannelMessage( + channel_id, + event.id, + event.pubkey, + event.kind, + event.content, + event.tags, + event.created_at, + ); + } + }); + + return true; + } catch (e) { + console.log("error: ", e); + } + } + useEffect(() => { async function prefetch() { const notes = await fetchNotes(); if (notes) { const chats = await fetchChats(); - if (chats) { + const channels = await fetchChannelMessages(); + if (chats && channels) { navigate("/app/space", { overwriteLastHistoryEntry: true }); } } diff --git a/src/libs/ndk.tsx b/src/libs/ndk.tsx index 4e7304a0..33916bea 100644 --- a/src/libs/ndk.tsx +++ b/src/libs/ndk.tsx @@ -35,7 +35,7 @@ export async function prefetchEvents( }); relaySetSubscription.on("eose", () => { - setTimeout(() => resolve(new Set(events.values())), 3000); + setTimeout(() => resolve(new Set(events.values())), 5000); }); }); } diff --git a/src/libs/storage.tsx b/src/libs/storage.tsx index bc108fae..0c0a3bdc 100644 --- a/src/libs/storage.tsx +++ b/src/libs/storage.tsx @@ -303,6 +303,31 @@ export async function updateChannelMetadata(event_id: string, value: string) { ); } +// create channel messages +export async function createChannelMessage( + channel_id: string, + event_id: string, + pubkey: string, + kind: number, + content: string, + tags: string[][], + created_at: number, +) { + const db = await connect(); + return await db.execute( + "INSERT OR IGNORE INTO channel_messages (channel_id, event_id, pubkey, kind, content, tags, created_at) VALUES (?, ?, ?, ?, ?, ?, ?);", + [channel_id, event_id, pubkey, kind, content, tags, created_at], + ); +} + +// get channel messages by channel id +export async function getChannelMessages(channel_id: string) { + const db = await connect(); + return await db.select( + `SELECT * FROM channel_messages WHERE channel_id = "${channel_id}" ORDER BY created_at ASC;`, + ); +} + // get all chats by pubkey export async function getChatsByPubkey(pubkey: string) { const db = await connect(); diff --git a/src/shared/notes/preview/image.tsx b/src/shared/notes/preview/image.tsx index 706a213e..74ef90e0 100644 --- a/src/shared/notes/preview/image.tsx +++ b/src/shared/notes/preview/image.tsx @@ -2,7 +2,7 @@ import { Image } from "@shared/image"; export function ImagePreview({ urls }: { urls: string[] }) { return ( -
+
{urls.map((url) => (
diff --git a/src/shared/notes/preview/link.tsx b/src/shared/notes/preview/link.tsx index 84493c65..bf6818a9 100644 --- a/src/shared/notes/preview/link.tsx +++ b/src/shared/notes/preview/link.tsx @@ -6,7 +6,7 @@ export function LinkPreview({ urls }: { urls: string[] }) { const { data, error, isLoading } = useOpenGraph(urls[0]); return ( -
+
{error &&

failed to load

} {isLoading || !data ? (
diff --git a/src/shared/notes/preview/video.tsx b/src/shared/notes/preview/video.tsx index 1b7557fa..758c470b 100644 --- a/src/shared/notes/preview/video.tsx +++ b/src/shared/notes/preview/video.tsx @@ -5,7 +5,7 @@ export function VideoPreview({ urls }: { urls: string[] }) {
e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} - className="relative mt-2 flex w-full flex-col overflow-hidden rounded-lg bg-zinc-950" + className="relative mt-3 max-w-[420px] flex w-full flex-col overflow-hidden rounded-lg bg-zinc-950" > diff --git a/src/stores/channels.tsx b/src/stores/channels.tsx index 04b384be..e974f9c0 100644 --- a/src/stores/channels.tsx +++ b/src/stores/channels.tsx @@ -1,5 +1,9 @@ -import { getChannels } from "@libs/storage"; -import NDK, { NDKFilter } from "@nostr-dev-kit/ndk"; +import { + createChannelMessage, + getChannelMessages, + getChannels, +} from "@libs/storage"; +import { LumeEvent } from "@utils/types"; import { create } from "zustand"; import { immer } from "zustand/middleware/immer"; @@ -38,19 +42,28 @@ export const useChannelMessages = create( immer((set) => ({ messages: [], replyTo: { id: null, pubkey: null, content: null }, - fetch: async (ndk: NDK, id: string, since: number) => { - const filter: NDKFilter = { - "#e": [id], - kinds: [42], - since: since, - }; - const events = await ndk.fetchEvents(filter); - const array = [...events]; - set({ messages: array }); + fetch: async (id: string) => { + const events = await getChannelMessages(id); + set({ messages: events }); }, - add: (message: any) => { + add: (id, event: LumeEvent) => { set((state: any) => { - state.messages.push(message); + createChannelMessage( + id, + event.id, + event.pubkey, + event.kind, + event.content, + event.tags, + event.created_at, + ); + state.messages.push({ + event_id: event.id, + channel_id: id, + hide: 0, + mute: 0, + ...event, + }); }); }, openReply: (id: string, pubkey: string, content: string) => { diff --git a/src/utils/parser.tsx b/src/utils/parser.tsx index 2749b603..096aaf0d 100644 --- a/src/utils/parser.tsx +++ b/src/utils/parser.tsx @@ -4,8 +4,19 @@ import getUrls from "get-urls"; import { parseReferences } from "nostr-tools"; import reactStringReplace from "react-string-replace"; +function isJsonString(str) { + try { + JSON.parse(str); + } catch (e) { + return false; + } + return true; +} + export function parser(event: any) { - event.tags = destr(event.tags); + if (isJsonString(event.tags)) { + event["tags"] = destr(event.tags); + } const references = parseReferences(event); const urls = getUrls(event.content);