diff --git a/package.json b/package.json index 8c27d50d273..4c85ffeab24 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "start:prod": "NODE_ENV=production node build/server.js" }, "dependencies": { - "@ecency/ns-query": "^1.1.8", + "@ecency/ns-query": "^1.2.3", "@ecency/render-helper": "^2.2.32", "@ecency/render-helper-amp": "^1.1.0", "@emoji-mart/data": "^1.1.2", diff --git a/src/common/app.tsx b/src/common/app.tsx index 54c52d2dc10..cc3cc475f31 100644 --- a/src/common/app.tsx +++ b/src/common/app.tsx @@ -121,6 +121,7 @@ const App = (props: any) => { -
+
{currentFormattedDate}
diff --git a/src/common/features/chats/components/chat-popup/chat-popup-contacts-and-channels.tsx b/src/common/features/chats/components/chat-popup/chat-popup-contacts-and-channels.tsx index 3cc0e786453..ef75f77a416 100644 --- a/src/common/features/chats/components/chat-popup/chat-popup-contacts-and-channels.tsx +++ b/src/common/features/chats/components/chat-popup/chat-popup-contacts-and-channels.tsx @@ -5,6 +5,7 @@ import { Button } from "@ui/button"; import { useChannelsQuery, useDirectContactsQuery, useKeysQuery } from "@ecency/ns-query"; import { ChatSidebarDirectContact } from "../chats-sidebar/chat-sidebar-direct-contact"; import { ChatSidebarChannel } from "../chats-sidebar/chat-sidebar-channel"; +import { useComposedContactsAndChannelsQuery } from "../../queries"; interface Props { communityClicked: (v: string) => void; @@ -18,44 +19,35 @@ export function ChatPopupContactsAndChannels({ setShowSearchUser }: Props) { const { privateKey } = useKeysQuery(); + const { data: directContacts } = useDirectContactsQuery(); const { data: channels } = useChannelsQuery(); + const composedContactsAndChannels = useComposedContactsAndChannelsQuery(); return ( <> {(directContacts?.length !== 0 || (channels?.length !== 0 && channels?.length !== 0)) && privateKey ? ( <> - {channels?.length !== 0 && ( - <> -
- {_t("chat.communities")} -
- {channels?.map((channel) => ( - communityClicked(channel.communityName!)} - isLink={false} - /> - ))} - {directContacts?.length !== 0 && ( -
- {_t("chat.dms")} -
- )} - + {composedContactsAndChannels.map((contactOrChannel) => + "id" in contactOrChannel ? ( + communityClicked(contactOrChannel.communityName!)} + isLink={false} + /> + ) : ( + userClicked(contactOrChannel.name)} + key={contactOrChannel.pubkey} + /> + ) )} - {directContacts?.map((user) => ( - userClicked(user.name)} - key={user.pubkey} - /> - ))} ) : !privateKey ? ( diff --git a/src/common/features/chats/components/chats-sidebar/chat-sidebar-channel.tsx b/src/common/features/chats/components/chats-sidebar/chat-sidebar-channel.tsx index 972cbd19e86..70f50319385 100644 --- a/src/common/features/chats/components/chats-sidebar/chat-sidebar-channel.tsx +++ b/src/common/features/chats/components/chats-sidebar/chat-sidebar-channel.tsx @@ -13,6 +13,7 @@ import { import { _t } from "../../../../i18n"; import { Badge } from "@ui/badge"; import { useCommunityCache } from "../../../../core"; +import { UilUsersAlt } from "@iconscout/react-unicons"; interface Props { username: string; @@ -42,6 +43,9 @@ export function ChatSidebarChannel({ const content = ( <> +
+ +
{channel.name}
@@ -63,7 +67,7 @@ export function ChatSidebarChannel({ return isLink ? ( setShowContextMenu(v)} @@ -162,7 +163,6 @@ export function ChatSidebarDirectContact({ contact, onClick, isLink = true }: Pr onTouchStart={() => setHoldStarted(true)} onTouchEnd={() => { setHoldStarted(false); - console.log("touch ended"); setTimeout(() => { if (!holdStarted) { @@ -186,7 +186,7 @@ export function ChatSidebarDirectContact({ contact, onClick, isLink = true }: Pr setShowContextMenu(true); }} className={classNameObject({ - "flex items-center text-dark-200 gap-3 p-3 border-b border-[--border-color] last:border-0 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer": + "flex items-center text-dark-200 gap-3 p-3 last:border-0 hover:bg-gray-100 dark:hover:bg-gray-800 cursor-pointer": true, "bg-gray-100 dark:bg-gray-800": receiverPubKey === contact.pubkey })} diff --git a/src/common/features/chats/components/chats-sidebar/index.tsx b/src/common/features/chats/components/chats-sidebar/index.tsx index e8e74c6ccda..9b0a1bd53da 100644 --- a/src/common/features/chats/components/chats-sidebar/index.tsx +++ b/src/common/features/chats/components/chats-sidebar/index.tsx @@ -12,7 +12,7 @@ import { useChannelsQuery, useDirectContactsQuery } from "@ecency/ns-query"; -import { useSearchUsersQuery } from "../../queries"; +import { useComposedContactsAndChannelsQuery, useSearchUsersQuery } from "../../queries"; import { useSearchCommunitiesQuery } from "../../queries/search-communities-query"; import { Community } from "../../../../store/communities"; import { Reputations } from "../../../../api/hive"; @@ -32,6 +32,7 @@ export default function ChatsSideBar(props: Props) { const { data: directContacts } = useDirectContactsQuery(); const { data: channels } = useChannelsQuery(); + const composedContactsAndChannels = useComposedContactsAndChannelsQuery(); const chatsSideBarRef = React.createRef(); const [selectedAccount, setSelectedAccount] = useState(""); @@ -75,27 +76,22 @@ export default function ChatsSideBar(props: Props) { )) ) : ( <> - {channels?.length !== 0 && ( -
- {_t("chat.communities")} -
+ {composedContactsAndChannels.map((contactOrChannel) => + "id" in contactOrChannel ? ( + + ) : ( + + ) )} - {channels?.map((channel) => ( - - ))} - {directContacts?.length !== 0 && ( -
- {_t("chat.direct-messages")} -
- )} - {directContacts?.map((contact) => ( - - ))} )}
diff --git a/src/common/features/chats/queries/composed-contacts-and-channels-query.ts b/src/common/features/chats/queries/composed-contacts-and-channels-query.ts new file mode 100644 index 00000000000..ce41cbfeb64 --- /dev/null +++ b/src/common/features/chats/queries/composed-contacts-and-channels-query.ts @@ -0,0 +1,73 @@ +import { + useChannelsQuery, + useDirectContactsQuery, + useKeysQuery, + useNostrGetUserProfileQuery +} from "@ecency/ns-query"; +import { useMemo } from "react"; +import { isAfter } from "date-fns"; + +export function useComposedContactsAndChannelsQuery() { + const { publicKey } = useKeysQuery(); + const { data: contactsData } = useDirectContactsQuery(); + const { data: channelsData } = useChannelsQuery(); + + const { data: activeUserNostrProfiles } = useNostrGetUserProfileQuery(publicKey); + + const channelsLastSeenDate = useMemo( + () => + channelsData.reduce>((acc, channel) => { + if (activeUserNostrProfiles?.[0] && channel) { + const channelsLastSeenTimestamps = + activeUserNostrProfiles?.[0].channelsLastSeenDate ?? {}; + const lastSeenTimestamp = channelsLastSeenTimestamps[channel.id]; + return { + ...acc, + [channel.id]: lastSeenTimestamp + }; + } + return acc; + }, {}), + [channelsData, activeUserNostrProfiles] + ); + + return useMemo( + () => + [...(contactsData ?? []), ...channelsData] + .sort((a, b) => { + let lastSeenDateA: Date | undefined; + if ("lastSeenDate" in a) { + lastSeenDateA = a.lastSeenDate; + } else if ("id" in a) { + lastSeenDateA = channelsLastSeenDate[a.id]; + } + + let lastSeenDateB: Date | undefined; + if ("lastSeenDate" in b) { + lastSeenDateB = b.lastSeenDate; + } else if ("id" in b) { + lastSeenDateB = channelsLastSeenDate[b.id]; + } + + if ( + lastSeenDateA instanceof Date && + lastSeenDateB instanceof Date && + isAfter(lastSeenDateA, lastSeenDateB) + ) { + return -1; + } + return 0; + }) + .sort((a, b) => { + if ("pinned" in a && "pinned" in b) { + return +(b.pinned ?? false) - +(a.pinned ?? false); + } else if ("pinned" in a) { + return -1; + } else if ("pinned" in b) { + return 1; + } + return 0; + }), + [contactsData, channelsData, channelsLastSeenDate] + ); +} diff --git a/src/common/features/chats/queries/index.ts b/src/common/features/chats/queries/index.ts index a25d33f77de..8d31b3adf8a 100644 --- a/src/common/features/chats/queries/index.ts +++ b/src/common/features/chats/queries/index.ts @@ -1 +1,2 @@ export * from "./search-users-query"; +export * from "./composed-contacts-and-channels-query"; diff --git a/yarn.lock b/yarn.lock index 742c84d7524..28195478ce3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1381,18 +1381,16 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@ecency/ns-query@^1.1.8": - version "1.1.8" - resolved "https://registry.yarnpkg.com/@ecency/ns-query/-/ns-query-1.1.8.tgz#5e224c5aa0116f143a0734fc3723916e10ca90f3" - integrity sha512-EEIEsPlKmHr3UyrOSk9/d8TbcrUeCdvqFYb+2TVdIJOQKYJA4YB2z4AWjUfIafbYpX/XAnP6aDH6ZqrPaKRSlA== +"@ecency/ns-query@^1.2.3": + version "1.2.3" + resolved "https://registry.yarnpkg.com/@ecency/ns-query/-/ns-query-1.2.3.tgz#057ec5d77fc5b379d05743bcd95f44971e19d2b1" + integrity sha512-5WiEmOlDu50ceeBdQkphhhMOggtdU/v2LOb832qTwUG6hrCj0YecU37uxDpGKYW4zIk3u5D4BKBZZR8BxCHTuA== dependencies: "@tanstack/react-query" "^4.29.7" axios "^0.21.2" date-fns "^2.30.0" nostr-tools "^1.17.0" react "^16.8.6" - react-dom "^16.8.6" - react-use "^17.4.1" "@ecency/render-helper-amp@^1.1.0": version "1.1.0" @@ -1893,7 +1891,7 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz#b6461fb0c2964356c469e115f504c95ad97ab88c" integrity sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w== -"@jridgewell/sourcemap-codec@^1.4.14", "@jridgewell/sourcemap-codec@^1.4.15": +"@jridgewell/sourcemap-codec@^1.4.14": version "1.4.15" resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32" integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg== @@ -5003,11 +5001,6 @@ csstype@^3.0.6: resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.1.tgz#841b532c45c758ee546a11d5bd7b7b473c8c30b9" integrity sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw== -csstype@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/csstype/-/csstype-3.1.2.tgz#1d4bf9d572f11c14031f0436e1c10bc1f571f50b" - integrity sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ== - currency-symbol-map@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/currency-symbol-map/-/currency-symbol-map-4.0.4.tgz#3cfba625974dd3f86822d327ecbd10248695e95e" @@ -7109,14 +7102,6 @@ inline-style-prefixer@^6.0.0: css-in-js-utils "^3.1.0" fast-loops "^1.1.3" -inline-style-prefixer@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-7.0.0.tgz#991d550735d42069f528ac1bcdacd378d1305442" - integrity sha512-I7GEdScunP1dQ6IM2mQWh6v0mOYdYmH3Bp31UecKdrcUgcURTcctSe1IECdUznSHKSmsHtjrT3CwCPI1pyxfUQ== - dependencies: - css-in-js-utils "^3.1.0" - fast-loops "^1.1.3" - inquirer@7.0.4: version "7.0.4" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.4.tgz#99af5bde47153abca23f5c7fc30db247f39da703" @@ -8923,20 +8908,6 @@ nano-css@^5.3.1: stacktrace-js "^2.0.2" stylis "^4.0.6" -nano-css@^5.6.1: - version "5.6.1" - resolved "https://registry.yarnpkg.com/nano-css/-/nano-css-5.6.1.tgz#964120cb1af6cccaa6d0717a473ccd876b34c197" - integrity sha512-T2Mhc//CepkTa3X4pUhKgbEheJHYAxD0VptuqFhDbGMUWVV2m+lkNiW/Ieuj35wrfC8Zm0l7HvssQh7zcEttSw== - dependencies: - "@jridgewell/sourcemap-codec" "^1.4.15" - css-tree "^1.1.2" - csstype "^3.1.2" - fastest-stable-stringify "^2.0.2" - inline-style-prefixer "^7.0.0" - rtl-css-js "^1.16.1" - stacktrace-js "^2.0.2" - stylis "^4.3.0" - nanoid@^3.1.22, nanoid@^3.3.6: version "3.3.6" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c" @@ -10969,26 +10940,6 @@ react-use@^17.4.0: ts-easing "^0.2.0" tslib "^2.1.0" -react-use@^17.4.1: - version "17.4.2" - resolved "https://registry.yarnpkg.com/react-use/-/react-use-17.4.2.tgz#26d43c774ccb733a17a87be62e12fbcbc5654173" - integrity sha512-1jPtmWLD8OJJNYCdYLJEH/HM+bPDfJuyGwCYeJFgPmWY8ttwpgZnW5QnzgM55CYUByUiTjHxsGOnEpLl6yQaoQ== - dependencies: - "@types/js-cookie" "^2.2.6" - "@xobotyi/scrollbar-width" "^1.9.5" - copy-to-clipboard "^3.3.1" - fast-deep-equal "^3.1.3" - fast-shallow-equal "^1.0.0" - js-cookie "^2.2.1" - nano-css "^5.6.1" - react-universal-interface "^0.6.2" - resize-observer-polyfill "^1.5.1" - screenfull "^5.1.0" - set-harmonic-interval "^1.0.1" - throttle-debounce "^3.0.1" - ts-easing "^0.2.0" - tslib "^2.1.0" - react-virtualized@^9.22.3: version "9.22.3" resolved "https://registry.yarnpkg.com/react-virtualized/-/react-virtualized-9.22.3.tgz#f430f16beb0a42db420dbd4d340403c0de334421" @@ -11483,13 +11434,6 @@ rtl-css-js@^1.14.0: dependencies: "@babel/runtime" "^7.1.2" -rtl-css-js@^1.16.1: - version "1.16.1" - resolved "https://registry.yarnpkg.com/rtl-css-js/-/rtl-css-js-1.16.1.tgz#4b48b4354b0ff917a30488d95100fbf7219a3e80" - integrity sha512-lRQgou1mu19e+Ya0LsTvKrVJ5TYUbqCVPAiImX3UfLTenarvPUl1QFdvu5Z3PYmHT9RCcwIfbjRQBntExyj3Zg== - dependencies: - "@babel/runtime" "^7.1.2" - run-async@^2.2.0, run-async@^2.4.0: version "2.4.1" resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.4.1.tgz#8440eccf99ea3e70bd409d49aab88e10c189a455" @@ -12505,11 +12449,6 @@ stylis@^4.0.6: resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.1.3.tgz#fd2fbe79f5fed17c55269e16ed8da14c84d069f7" integrity sha512-GP6WDNWf+o403jrEp9c5jibKavrtLW+/qYGhFxFrG8maXhwTBI7gLLhiBb0o7uFccWN+EOS9aMO6cGHWAO07OA== -stylis@^4.3.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.3.0.tgz#abe305a669fc3d8777e10eefcfc73ad861c5588c" - integrity sha512-E87pIogpwUsUwXw7dNyU4QDjdgVMy52m+XEOPEKUn161cCzWjjhPSQhByfd1CcNvrOLnXQ6OnnZDwnJrz/Z4YQ== - sucrase@^3.32.0: version "3.34.0" resolved "https://registry.yarnpkg.com/sucrase/-/sucrase-3.34.0.tgz#1e0e2d8fcf07f8b9c3569067d92fbd8690fb576f"