fix: consume enriched account comments directly (#1178)

* fix: consume enriched account comments directly

* fix(account comments): use empty lookup patch
This commit is contained in:
Tommaso Casaburi
2026-06-24 16:23:42 +07:00
committed by GitHub
parent dff0aef8f8
commit f1022fc2d9
22 changed files with 157 additions and 269 deletions
+1 -1
View File
@@ -9,7 +9,7 @@
"private": true, "private": true,
"dependencies": { "dependencies": {
"@bbob/parser": "4.3.1", "@bbob/parser": "4.3.1",
"@bitsocial/bitsocial-react-hooks": "0.1.19", "@bitsocial/bitsocial-react-hooks": "0.1.21",
"@bitsocial/bso-resolver": "0.0.8", "@bitsocial/bso-resolver": "0.0.8",
"@capacitor/app": "7.0.1", "@capacitor/app": "7.0.1",
"@capacitor/browser": "7.0.5", "@capacitor/browser": "7.0.5",
+3 -3
View File
@@ -1,7 +1,7 @@
import { lazy, Suspense, useEffect } from 'react'; import { lazy, Suspense, useEffect } from 'react';
import { useShallow } from 'zustand/react/shallow'; import { useShallow } from 'zustand/react/shallow';
import { Navigate, Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom'; import { Navigate, Outlet, Route, Routes, useLocation, useParams } from 'react-router-dom';
import { useAccount, useCommunity } from '@bitsocial/bitsocial-react-hooks'; import { useAccount, useAccountComment, useCommunity } from '@bitsocial/bitsocial-react-hooks';
import { initSnow, removeSnow, shouldShowSnow } from './lib/snow'; import { initSnow, removeSnow, shouldShowSnow } from './lib/snow';
import { isAllView, isCatalogView, isModView, isSubscriptionsView } from './lib/utils/view-utils'; import { isAllView, isCatalogView, isModView, isSubscriptionsView } from './lib/utils/view-utils';
import { preloadReplyModal, preloadThemeAssets } from './lib/utils/preload-utils'; import { preloadReplyModal, preloadThemeAssets } from './lib/utils/preload-utils';
@@ -16,8 +16,8 @@ import { useDirectories } from './hooks/use-directories';
import { useBrowserPureP2PAccountUpgrade } from './hooks/use-browser-pure-p2p-account-upgrade'; import { useBrowserPureP2PAccountUpgrade } from './hooks/use-browser-pure-p2p-account-upgrade';
import { useCommunityIdentifier } from './hooks/use-community-identifiers'; import { useCommunityIdentifier } from './hooks/use-community-identifiers';
import { useResolvedCommunityAddress, useResolvedDirectoryBoardPath } from './hooks/use-resolved-community-address'; import { useResolvedCommunityAddress, useResolvedDirectoryBoardPath } from './hooks/use-resolved-community-address';
import useSafeAccountComment from './hooks/use-safe-account-comment';
import useSuspendOffscreenMediaPlayback from './hooks/use-suspend-offscreen-media-playback'; import useSuspendOffscreenMediaPlayback from './hooks/use-suspend-offscreen-media-playback';
import { normalizeAccountCommentIndex } from './lib/utils/account-comment-index-utils';
import { getCommentCommunityAddress } from './lib/utils/comment-utils'; import { getCommentCommunityAddress } from './lib/utils/comment-utils';
import { import {
getBoardPath, getBoardPath,
@@ -82,7 +82,7 @@ const BoardLayout = () => {
const directories = useDirectories(); const directories = useDirectories();
const communityAddress = useResolvedCommunityAddress(boardIdentifier); const communityAddress = useResolvedCommunityAddress(boardIdentifier);
const { boardPath: resolvedDirectoryBoardPath, isDirectoryCandidate } = useResolvedDirectoryBoardPath(boardIdentifier); const { boardPath: resolvedDirectoryBoardPath, isDirectoryCandidate } = useResolvedDirectoryBoardPath(boardIdentifier);
const pendingPost = useSafeAccountComment({ commentIndex: accountCommentIndex }); const pendingPost = useAccountComment({ commentIndex: normalizeAccountCommentIndex(accountCommentIndex) });
const pendingPostCommunityAddress = getCommentCommunityAddress(pendingPost); const pendingPostCommunityAddress = getCommentCommunityAddress(pendingPost);
const { closeCreateBoardModal } = useCreateBoardModalStore(); const { closeCreateBoardModal } = useCreateBoardModalStore();
const isOnPostRoute = isPostRoute(pathname); const isOnPostRoute = isPostRoute(pathname);
@@ -1,6 +1,6 @@
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { useAccount, useComment, useSubscribe } from '@bitsocial/bitsocial-react-hooks'; import { useAccount, useAccountComment, useComment, useSubscribe } from '@bitsocial/bitsocial-react-hooks';
import { isAllView, isCatalogView, isModView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isCatalogView, isModView, isModQueueView, isPendingPostView, isPostPageView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { usePostPageNumber } from '../../hooks/use-post-page-number'; import { usePostPageNumber } from '../../hooks/use-post-page-number';
import { useDirectories, useDirectoryByAddress } from '../../hooks/use-directories'; import { useDirectories, useDirectoryByAddress } from '../../hooks/use-directories';
@@ -9,7 +9,7 @@ import { useFilteredDirectoryAddresses } from '../../hooks/use-filtered-director
import { getBoardPath, isDirectoryRoute } from '../../lib/utils/route-utils'; import { getBoardPath, isDirectoryRoute } from '../../lib/utils/route-utils';
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address'; import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
import { useCommunityIdentifier } from '../../hooks/use-community-identifiers'; import { useCommunityIdentifier } from '../../hooks/use-community-identifiers';
import useSafeAccountComment from '../../hooks/use-safe-account-comment'; import { normalizeAccountCommentIndex } from '../../lib/utils/account-comment-index-utils';
import useHiddenCatalogThreads from '../../hooks/use-hidden-catalog-threads'; import useHiddenCatalogThreads from '../../hooks/use-hidden-catalog-threads';
import useCatalogFiltersStore from '../../stores/use-catalog-filters-store'; import useCatalogFiltersStore from '../../stores/use-catalog-filters-store';
import useCatalogStyleStore from '../../stores/use-catalog-style-store'; import useCatalogStyleStore from '../../stores/use-catalog-style-store';
@@ -532,7 +532,7 @@ export const MobileBoardButtons = () => {
const isInModView = isModView(location.pathname); const isInModView = isModView(location.pathname);
const isInModQueueView = isModQueueView(location.pathname); const isInModQueueView = isModQueueView(location.pathname);
const accountComment = useSafeAccountComment({ commentIndex: params?.accountCommentIndex }); const accountComment = useAccountComment({ commentIndex: normalizeAccountCommentIndex(params?.accountCommentIndex) });
const resolvedAddress = useResolvedCommunityAddress(); const resolvedAddress = useResolvedCommunityAddress();
const communityAddress = resolvedAddress || accountComment?.communityAddress; const communityAddress = resolvedAddress || accountComment?.communityAddress;
@@ -646,7 +646,7 @@ export const PostPageStats = () => {
const autoUpdateEnabled = useThreadLiveUpdatesStore((state) => state.enabled); const autoUpdateEnabled = useThreadLiveUpdatesStore((state) => state.enabled);
const commentCid = params?.commentCid as string | undefined; const commentCid = params?.commentCid as string | undefined;
const resolvedAddress = useResolvedCommunityAddress(); const resolvedAddress = useResolvedCommunityAddress();
const accountComment = useSafeAccountComment({ commentIndex: params?.accountCommentIndex }); const accountComment = useAccountComment({ commentIndex: normalizeAccountCommentIndex(params?.accountCommentIndex) });
const communityAddress = resolvedAddress || accountComment?.communityAddress; const communityAddress = resolvedAddress || accountComment?.communityAddress;
const communityIdentifier = useCommunityIdentifier(communityAddress); const communityIdentifier = useCommunityIdentifier(communityAddress);
@@ -716,7 +716,7 @@ export const CatalogSearchResultsLabel = () => {
export const DesktopBoardButtons = () => { export const DesktopBoardButtons = () => {
const params = useParams(); const params = useParams();
const location = useLocation(); const location = useLocation();
const accountComment = useSafeAccountComment({ commentIndex: params?.accountCommentIndex }); const accountComment = useAccountComment({ commentIndex: normalizeAccountCommentIndex(params?.accountCommentIndex) });
const resolvedAddress = useResolvedCommunityAddress(); const resolvedAddress = useResolvedCommunityAddress();
const communityAddress = resolvedAddress || accountComment?.communityAddress; const communityAddress = resolvedAddress || accountComment?.communityAddress;
const isInCatalogView = isCatalogView(location.pathname, params); const isInCatalogView = isCatalogView(location.pathname, params);
+3 -3
View File
@@ -1,7 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useLocation, useParams, useNavigate } from 'react-router-dom'; import { useLocation, useParams, useNavigate } from 'react-router-dom';
import { useCommunity } from '@bitsocial/bitsocial-react-hooks'; import { useAccountComment, useCommunity } from '@bitsocial/bitsocial-react-hooks';
import { accountsStore as useAccountsStore } from '../../lib/bitsocial-internals/stores'; import { accountsStore as useAccountsStore } from '../../lib/bitsocial-internals/stores';
import getShortAddress from '../../lib/get-short-address'; import getShortAddress from '../../lib/get-short-address';
import { useCommunityIdentifier } from '../../hooks/use-community-identifiers'; import { useCommunityIdentifier } from '../../hooks/use-community-identifiers';
@@ -11,7 +11,7 @@ import { isArchiveRoute, isDirectoryListRoute } from '../../lib/utils/route-util
import styles from './board-header.module.css'; import styles from './board-header.module.css';
import { useDirectories } from '../../hooks/use-directories'; import { useDirectories } from '../../hooks/use-directories';
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address'; import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
import useSafeAccountComment from '../../hooks/use-safe-account-comment'; import { normalizeAccountCommentIndex } from '../../lib/utils/account-comment-index-utils';
import useIsMobile from '../../hooks/use-is-mobile'; import useIsMobile from '../../hooks/use-is-mobile';
import useIsCommunityOffline from '../../hooks/use-is-community-offline'; import useIsCommunityOffline from '../../hooks/use-is-community-offline';
import { shouldShowSnow } from '../../lib/snow'; import { shouldShowSnow } from '../../lib/snow';
@@ -54,7 +54,7 @@ const BoardHeader = () => {
const isInModView = isModView(location.pathname); const isInModView = isModView(location.pathname);
const isInArchiveView = isArchiveRoute(location.pathname); const isInArchiveView = isArchiveRoute(location.pathname);
const isInDirectoryListView = isDirectoryListRoute(location.pathname); const isInDirectoryListView = isDirectoryListRoute(location.pathname);
const accountComment = useSafeAccountComment({ commentIndex: params?.accountCommentIndex }); const accountComment = useAccountComment({ commentIndex: normalizeAccountCommentIndex(params?.accountCommentIndex) });
const resolvedAddress = useResolvedCommunityAddress(); const resolvedAddress = useResolvedCommunityAddress();
const communityAddress = resolvedAddress || accountComment?.communityAddress; const communityAddress = resolvedAddress || accountComment?.communityAddress;
+3 -3
View File
@@ -1,13 +1,14 @@
import { useEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useMemo, useRef, useState } from 'react';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useAccountComment } from '@bitsocial/bitsocial-react-hooks';
import getShortAddress from '../../lib/get-short-address'; import getShortAddress from '../../lib/get-short-address';
import { accountsStore as useAccountsStore } from '../../lib/bitsocial-internals/stores'; import { accountsStore as useAccountsStore } from '../../lib/bitsocial-internals/stores';
import { isAllView, isCatalogView, isModView, isSubscriptionsView } from '../../lib/utils/view-utils'; import { isAllView, isCatalogView, isModView, isSubscriptionsView } from '../../lib/utils/view-utils';
import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses'; import { useAccountCommunityAddresses } from '../../hooks/use-account-community-addresses';
import { useDirectories, DirectoryCommunity } from '../../hooks/use-directories'; import { useDirectories, DirectoryCommunity } from '../../hooks/use-directories';
import { useBoardPath, useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address'; import { useBoardPath, useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
import useSafeAccountComment from '../../hooks/use-safe-account-comment'; import { normalizeAccountCommentIndex } from '../../lib/utils/account-comment-index-utils';
import { getBoardPath, extractDirectoryFromTitle } from '../../lib/utils/route-utils'; import { getBoardPath, extractDirectoryFromTitle } from '../../lib/utils/route-utils';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils'; import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import useCreateBoardModalStore from '../../stores/use-create-board-modal-store'; import useCreateBoardModalStore from '../../stores/use-create-board-modal-store';
@@ -418,8 +419,7 @@ const BoardsBarMobile = ({ communityAddress }: { communityAddress?: string }) =>
const BoardsBar = () => { const BoardsBar = () => {
const params = useParams(); const params = useParams();
const commentIndex = params?.accountCommentIndex ? parseInt(params.accountCommentIndex, 10) : undefined; const accountComment = useAccountComment({ commentIndex: normalizeAccountCommentIndex(params?.accountCommentIndex) });
const accountComment = useSafeAccountComment({ commentIndex });
const resolvedCommunityAddress = useResolvedCommunityAddress(); const resolvedCommunityAddress = useResolvedCommunityAddress();
const communityAddress = resolvedCommunityAddress || getCommentCommunityAddress(accountComment); const communityAddress = resolvedCommunityAddress || getCommentCommunityAddress(accountComment);
+2 -3
View File
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom'; import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso'; import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { Comment, useEditedComment, useReplies, useAccount } from '@bitsocial/bitsocial-react-hooks'; import { Comment, useAccount, useAccountComment, useEditedComment, useReplies } from '@bitsocial/bitsocial-react-hooks';
import getShortAddress from '../../lib/get-short-address'; import getShortAddress from '../../lib/get-short-address';
import styles from '../../views/post/post.module.css'; import styles from '../../views/post/post.module.css';
import { CommentMediaInfo, getHasThumbnail, getMediaDimensions, getPostMediaTypeLabel, getYouTubeEmbedPostMediaFileLink } from '../../lib/utils/media-utils'; import { CommentMediaInfo, getHasThumbnail, getMediaDimensions, getPostMediaTypeLabel, getYouTubeEmbedPostMediaFileLink } from '../../lib/utils/media-utils';
@@ -22,7 +22,6 @@ import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import useHide from '../../hooks/use-hide'; import useHide from '../../hooks/use-hide';
import useStateString from '../../hooks/use-state-string'; import useStateString from '../../hooks/use-state-string';
import useScrollToReply from '../../hooks/use-scroll-to-reply'; import useScrollToReply from '../../hooks/use-scroll-to-reply';
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
import { useCurrentTime } from '../../hooks/use-current-time'; import { useCurrentTime } from '../../hooks/use-current-time';
import { useBoardPseudonymityMode } from '../../hooks/use-board-pseudonymity-mode'; import { useBoardPseudonymityMode } from '../../hooks/use-board-pseudonymity-mode';
import CommentContent from '../comment-content/comment-content'; import CommentContent from '../comment-content/comment-content';
@@ -689,7 +688,7 @@ const Reply = ({
postsByAuthorInThread, postsByAuthorInThread,
disableDeferredLayout, disableDeferredLayout,
}: PostProps & { directRepliesByParentCid?: Map<string, Comment[]>; postsByAuthorInThread?: Map<string, number>; disableDeferredLayout?: boolean }) => { }: PostProps & { directRepliesByParentCid?: Map<string, Comment[]>; postsByAuthorInThread?: Map<string, number>; disableDeferredLayout?: boolean }) => {
const accountReply = useSafeAccountComment({ const accountReply = useAccountComment({
commentCid: reply?.cid, commentCid: reply?.cid,
commentIndex: typeof reply?.index === 'number' ? reply.index : undefined, commentIndex: typeof reply?.index === 'number' ? reply.index : undefined,
}); });
+4 -4
View File
@@ -2,7 +2,7 @@ import { type ReactNode, useCallback, useEffect, useRef, useState } from 'react'
import { Trans, useTranslation } from 'react-i18next'; import { Trans, useTranslation } from 'react-i18next';
import type { TFunction } from 'i18next'; import type { TFunction } from 'i18next';
import { Link, useLocation, useNavigate, useParams } from 'react-router-dom'; import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
import { Comment, setAccount, useAccount, useEditedComment } from '@bitsocial/bitsocial-react-hooks'; import { Comment, setAccount, useAccount, useAccountComment, useEditedComment } from '@bitsocial/bitsocial-react-hooks';
import getShortAddress from '../../lib/get-short-address'; import getShortAddress from '../../lib/get-short-address';
import { communitiesPagesStore as useCommunitiesPagesStore } from '../../lib/bitsocial-internals/stores'; import { communitiesPagesStore as useCommunitiesPagesStore } from '../../lib/bitsocial-internals/stores';
import { getDisplayMediaInfoType, getLinkMediaInfo, getTwimgMediaFilePublishUrl } from '../../lib/utils/media-utils'; import { getDisplayMediaInfoType, getLinkMediaInfo, getTwimgMediaFilePublishUrl } from '../../lib/utils/media-utils';
@@ -41,7 +41,7 @@ import { useDirectoryEntry } from '../../hooks/use-directory-entry';
import { useCommunityField } from '../../hooks/use-stable-community'; import { useCommunityField } from '../../hooks/use-stable-community';
import useIsMobile from '../../hooks/use-is-mobile'; import useIsMobile from '../../hooks/use-is-mobile';
import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address'; import { useResolvedCommunityAddress } from '../../hooks/use-resolved-community-address';
import useSafeAccountComment from '../../hooks/use-safe-account-comment'; import { normalizeAccountCommentIndex } from '../../lib/utils/account-comment-index-utils';
import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame'; import useFetchGifFirstFrame from '../../hooks/use-fetch-gif-first-frame';
import { useYouTubeThumbnailLinkConversion } from '../../hooks/use-youtube-thumbnail-link-conversion'; import { useYouTubeThumbnailLinkConversion } from '../../hooks/use-youtube-thumbnail-link-conversion';
import usePublishSubmissionGuard from '../../hooks/use-publish-submission-guard'; import usePublishSubmissionGuard from '../../hooks/use-publish-submission-guard';
@@ -544,7 +544,7 @@ const PostFormTable = ({ closeForm, postCid }: { closeForm: () => void; postCid:
const [url, setUrl] = useState(''); const [url, setUrl] = useState('');
const author = account?.author || {}; const author = account?.author || {};
const { displayName } = author || {}; const { displayName } = author || {};
const accountComment = useSafeAccountComment({ commentIndex: params?.accountCommentIndex }); const accountComment = useAccountComment({ commentIndex: normalizeAccountCommentIndex(params?.accountCommentIndex) });
const resolvedAddress = useResolvedCommunityAddress(); const resolvedAddress = useResolvedCommunityAddress();
const communityAddress = resolvedAddress || accountComment?.communityAddress; const communityAddress = resolvedAddress || accountComment?.communityAddress;
const { setPublishPostOptions, postIndex, publishPost, publishPostError, publishPostOptions, resetPublishPostOptions } = usePublishPost({ const { setPublishPostOptions, postIndex, publishPost, publishPostError, publishPostOptions, resetPublishPostOptions } = usePublishPost({
@@ -1028,7 +1028,7 @@ const PostForm = () => {
const [showForm, setShowForm] = useState(false); const [showForm, setShowForm] = useState(false);
const accountComment = useSafeAccountComment({ commentIndex: params?.accountCommentIndex }); const accountComment = useAccountComment({ commentIndex: normalizeAccountCommentIndex(params?.accountCommentIndex) });
const resolvedAddress = useResolvedCommunityAddress(); const resolvedAddress = useResolvedCommunityAddress();
const communityAddress = resolvedAddress || accountComment?.communityAddress; const communityAddress = resolvedAddress || accountComment?.communityAddress;
+2 -3
View File
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom'; import { Link, useLocation, useNavigationType, useParams } from 'react-router-dom';
import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso'; import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
import { Comment, useEditedComment, useReplies, useAccount } from '@bitsocial/bitsocial-react-hooks'; import { Comment, useAccount, useAccountComment, useEditedComment, useReplies } from '@bitsocial/bitsocial-react-hooks';
import getShortAddress from '../../lib/get-short-address'; import getShortAddress from '../../lib/get-short-address';
import styles from '../../views/post/post.module.css'; import styles from '../../views/post/post.module.css';
import { shouldShowSnow } from '../../lib/snow'; import { shouldShowSnow } from '../../lib/snow';
@@ -21,7 +21,6 @@ import useCountLinksInReplies from '../../hooks/use-count-links-in-replies';
import useHide from '../../hooks/use-hide'; import useHide from '../../hooks/use-hide';
import useStateString from '../../hooks/use-state-string'; import useStateString from '../../hooks/use-state-string';
import useScrollToReply from '../../hooks/use-scroll-to-reply'; import useScrollToReply from '../../hooks/use-scroll-to-reply';
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
import { useCurrentTime } from '../../hooks/use-current-time'; import { useCurrentTime } from '../../hooks/use-current-time';
import { useBoardPseudonymityMode } from '../../hooks/use-board-pseudonymity-mode'; import { useBoardPseudonymityMode } from '../../hooks/use-board-pseudonymity-mode';
import CommentContent from '../comment-content/comment-content'; import CommentContent from '../comment-content/comment-content';
@@ -464,7 +463,7 @@ const Reply = ({
postsByAuthorInThread, postsByAuthorInThread,
disableDeferredLayout, disableDeferredLayout,
}: PostProps & { directRepliesByParentCid?: Map<string, Comment[]>; postsByAuthorInThread?: Map<string, number>; disableDeferredLayout?: boolean }) => { }: PostProps & { directRepliesByParentCid?: Map<string, Comment[]>; postsByAuthorInThread?: Map<string, number>; disableDeferredLayout?: boolean }) => {
const accountReply = useSafeAccountComment({ const accountReply = useAccountComment({
commentCid: reply?.cid, commentCid: reply?.cid,
commentIndex: typeof reply?.index === 'number' ? reply.index : undefined, commentIndex: typeof reply?.index === 'number' ? reply.index : undefined,
}); });
@@ -1,10 +1,9 @@
import { useEffect, useRef, useState } from 'react'; import { useEffect, useRef, useState } from 'react';
import { createPortal } from 'react-dom'; import { createPortal } from 'react-dom';
import { Link, useLocation, useNavigate } from 'react-router-dom'; import { Link, useLocation, useNavigate } from 'react-router-dom';
import { Comment, useAccount } from '@bitsocial/bitsocial-react-hooks'; import { Comment, useAccount, useAccountComment } from '@bitsocial/bitsocial-react-hooks';
import { useFloating, offset, shift, size, autoUpdate, Placement } from '@floating-ui/react'; import { useFloating, offset, shift, size, autoUpdate, Placement } from '@floating-ui/react';
import { useDirectories } from '../../hooks/use-directories'; import { useDirectories } from '../../hooks/use-directories';
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
import { getBoardPath } from '../../lib/utils/route-utils'; import { getBoardPath } from '../../lib/utils/route-utils';
import { formatQuoteNumber, getQuoteTargetAvailability, shouldShowFloatingQuotePreview } from '../../lib/utils/quote-link-utils'; import { formatQuoteNumber, getQuoteTargetAvailability, shouldShowFloatingQuotePreview } from '../../lib/utils/quote-link-utils';
import { findPreferredScrollTarget, getThreadTopNavigationState, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils'; import { findPreferredScrollTarget, getThreadTopNavigationState, scrollThreadContainerToTop } from '../../lib/utils/thread-scroll-utils';
@@ -90,7 +89,7 @@ const scrollToReplyOnPage = (cid: string) => {
const useIsOwnQuotelink = (quotelinkReply?: Comment) => { const useIsOwnQuotelink = (quotelinkReply?: Comment) => {
const account = useAccount(); const account = useAccount();
const ownQuotelink = useSafeAccountComment({ commentCid: quotelinkReply?.cid }); const ownQuotelink = useAccountComment({ commentCid: quotelinkReply?.cid });
const quotedAuthorAddress = quotelinkReply?.author?.address; const quotedAuthorAddress = quotelinkReply?.author?.address;
const accountAuthorAddress = account?.author?.address; const accountAuthorAddress = account?.author?.address;
@@ -1,102 +0,0 @@
import * as React from 'react';
import { createElement } from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import useSafeAccountComment from '../use-safe-account-comment';
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
const act = (React as { act?: (cb: () => void | Promise<void>) => void | Promise<void> }).act as (cb: () => void | Promise<void>) => void | Promise<void>;
const testState = vi.hoisted(() => ({
account: undefined as { id?: string; name?: string } | undefined,
accountCommentResult: { cid: 'account-comment' } as { cid?: string },
calls: [] as Array<{ accountName?: string; commentCid?: string; commentIndex?: number }>,
options: undefined as { accountName?: string; commentCid?: string; commentIndex?: number | string } | undefined,
}));
vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
useAccount: (options?: { accountName?: string }) => {
if (!options?.accountName) {
return testState.account;
}
return testState.account?.name === options.accountName ? testState.account : undefined;
},
useAccountComment: (options?: { accountName?: string; commentCid?: string; commentIndex?: number }) => {
testState.calls.push(options || {});
return testState.accountCommentResult;
},
}));
let container: HTMLDivElement;
let latestValue: ReturnType<typeof useSafeAccountComment>;
let root: Root;
const HookHarness = () => {
latestValue = useSafeAccountComment(testState.options);
return null;
};
const renderHook = () => {
act(() => {
root.render(createElement(HookHarness));
});
};
describe('useSafeAccountComment', () => {
beforeEach(() => {
testState.account = undefined;
testState.accountCommentResult = { cid: 'account-comment' };
testState.calls = [];
testState.options = undefined;
container = document.createElement('div');
document.body.appendChild(container);
root = createRoot(container);
});
afterEach(() => {
act(() => root.unmount());
container.remove();
});
it('uses a sentinel lookup when there is no active account and no usable lookup input', () => {
renderHook();
expect(testState.calls).toEqual([{ commentIndex: -1 }]);
expect(latestValue?.cid).toBe('account-comment');
});
it('normalizes numeric comment indices before delegating to useAccountComment', () => {
testState.options = { commentIndex: '7' };
renderHook();
expect(testState.calls).toEqual([{ commentIndex: 7 }]);
});
it('falls back to the sentinel lookup for malformed string indices', () => {
testState.options = { commentIndex: '7abc' };
renderHook();
expect(testState.calls).toEqual([{ commentIndex: -1 }]);
});
it('falls back to the sentinel lookup when cid lookup is requested before an account exists', () => {
testState.options = { commentCid: 'reply-cid' };
renderHook();
expect(testState.calls).toEqual([{ commentIndex: -1 }]);
});
it('passes comment cid lookups through once the active account exists', () => {
testState.account = { id: 'account-1', name: 'Account 1' };
testState.options = { commentCid: 'reply-cid' };
renderHook();
expect(testState.calls).toEqual([{ commentCid: 'reply-cid' }]);
});
});
-47
View File
@@ -1,47 +0,0 @@
import { useMemo } from 'react';
import { useAccount, useAccountComment } from '@bitsocial/bitsocial-react-hooks';
type SafeAccountCommentOptions = {
accountName?: string;
commentCid?: string;
commentIndex?: number | string;
};
const EMPTY_ACCOUNT_COMMENT_LOOKUP = Object.freeze({ commentIndex: -1 as const });
const normalizeCommentIndex = (commentIndex: SafeAccountCommentOptions['commentIndex']) => {
if (commentIndex === undefined || commentIndex === null || commentIndex === '') {
return undefined;
}
const normalizedCommentIndex = Number(commentIndex);
return Number.isInteger(normalizedCommentIndex) && normalizedCommentIndex >= 0 ? normalizedCommentIndex : undefined;
};
const useSafeAccountComment = (options?: SafeAccountCommentOptions) => {
const account = useAccount(options?.accountName ? { accountName: options.accountName } : undefined);
const normalizedCommentIndex = normalizeCommentIndex(options?.commentIndex);
const safeOptions = useMemo(() => {
if (typeof normalizedCommentIndex === 'number') {
return {
...(options?.accountName ? { accountName: options.accountName } : {}),
commentIndex: normalizedCommentIndex,
};
}
if (options?.commentCid && account?.id) {
return {
...(options?.accountName ? { accountName: options.accountName } : {}),
commentCid: options.commentCid,
};
}
return EMPTY_ACCOUNT_COMMENT_LOOKUP;
}, [account?.id, normalizedCommentIndex, options?.accountName, options?.commentCid]);
return useAccountComment(safeOptions);
};
export default useSafeAccountComment;
+3 -3
View File
@@ -1,5 +1,6 @@
import { useCallback, useEffect, useMemo } from 'react'; import { useCallback, useEffect, useMemo } from 'react';
import { useLocation, useParams } from 'react-router-dom'; import { useLocation, useParams } from 'react-router-dom';
import { useAccountComment } from '@bitsocial/bitsocial-react-hooks';
import { isAllView, isModView, isSubscriptionsView } from '../lib/utils/view-utils'; import { isAllView, isModView, isSubscriptionsView } from '../lib/utils/view-utils';
import useThemeStore from '../stores/use-theme-store'; import useThemeStore from '../stores/use-theme-store';
import { useDirectories } from './use-directories'; import { useDirectories } from './use-directories';
@@ -7,8 +8,8 @@ import { useResolvedCommunityAddress } from './use-resolved-community-address';
import useSpecialThemeStore from '../stores/use-special-theme-store'; import useSpecialThemeStore from '../stores/use-special-theme-store';
import { getActiveSpecialTheme, getSpecialThemeClass } from '../lib/utils/time-utils'; import { getActiveSpecialTheme, getSpecialThemeClass } from '../lib/utils/time-utils';
import { isSfwBoard, updateFavicon } from '../lib/update-favicon'; import { isSfwBoard, updateFavicon } from '../lib/update-favicon';
import useSafeAccountComment from './use-safe-account-comment';
import { getCommentCommunityAddress } from '../lib/utils/comment-utils'; import { getCommentCommunityAddress } from '../lib/utils/comment-utils';
import { normalizeAccountCommentIndex } from '../lib/utils/account-comment-index-utils';
const themeClasses = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon', 'spooky']; const themeClasses = ['yotsuba', 'yotsuba-b', 'futaba', 'burichan', 'tomorrow', 'photon', 'spooky'];
@@ -23,8 +24,7 @@ const useTheme = (): [string, (theme: string) => void] => {
const location = useLocation(); const location = useLocation();
const params = useParams<{ boardIdentifier?: string }>(); const params = useParams<{ boardIdentifier?: string }>();
const pendingPostParams = useParams<{ accountCommentIndex?: string }>(); const pendingPostParams = useParams<{ accountCommentIndex?: string }>();
const pendingPostCommentIndex = pendingPostParams?.accountCommentIndex ? parseInt(pendingPostParams.accountCommentIndex, 10) : undefined; const pendingPost = useAccountComment({ commentIndex: normalizeAccountCommentIndex(pendingPostParams?.accountCommentIndex) });
const pendingPost = useSafeAccountComment({ commentIndex: pendingPostCommentIndex });
const pendingPostCommunityAddress = getCommentCommunityAddress(pendingPost); const pendingPostCommunityAddress = getCommentCommunityAddress(pendingPost);
const { isEnabled, setIsEnabled } = useSpecialThemeStore(); const { isEnabled, setIsEnabled } = useSpecialThemeStore();
@@ -0,0 +1,20 @@
import { describe, expect, it } from 'vitest';
import { normalizeAccountCommentIndex } from '../account-comment-index-utils';
describe('normalizeAccountCommentIndex', () => {
it('accepts non-negative integer numbers and strings', () => {
expect(normalizeAccountCommentIndex(0)).toBe(0);
expect(normalizeAccountCommentIndex(7)).toBe(7);
expect(normalizeAccountCommentIndex('0')).toBe(0);
expect(normalizeAccountCommentIndex('7')).toBe(7);
});
it('rejects missing, negative, fractional, and malformed values', () => {
expect(normalizeAccountCommentIndex(undefined)).toBeUndefined();
expect(normalizeAccountCommentIndex(null)).toBeUndefined();
expect(normalizeAccountCommentIndex('')).toBeUndefined();
expect(normalizeAccountCommentIndex(-1)).toBeUndefined();
expect(normalizeAccountCommentIndex('1.5')).toBeUndefined();
expect(normalizeAccountCommentIndex('1abc')).toBeUndefined();
});
});
@@ -1,51 +0,0 @@
import type { Account } from '@bitsocial/bitsocial-react-hooks';
type CommentAuthor = {
address?: string;
avatar?: unknown;
displayName?: string;
flair?: unknown;
shortAddress?: string;
[key: string]: unknown;
};
type AccountCommentWithAuthor = {
accountId?: string;
author?: CommentAuthor;
};
export const mergeDefinedFields = <T extends object>(base: T | undefined, override: T | undefined): T | undefined => {
if (!override) return base;
const merged = { ...base } as Record<string, unknown>;
for (const [key, value] of Object.entries(override)) {
if (value !== undefined) {
merged[key] = value;
}
}
return merged as T;
};
export function restoreActiveAccountAuthor<T extends object>(accountComment: T, account: Account | undefined): T;
export function restoreActiveAccountAuthor<T extends object>(accountComment: T | undefined, account: Account | undefined): T | undefined;
export function restoreActiveAccountAuthor<T extends object>(accountComment: T | undefined, account: Account | undefined): T | undefined {
const comment = accountComment as AccountCommentWithAuthor | undefined;
if (!comment || comment.author?.address || !account?.id || comment.accountId !== account.id || !account.author?.address) {
return accountComment;
}
const accountAuthor = {
address: account.author.address,
shortAddress: account.author.shortAddress,
displayName: account.author.displayName,
avatar: account.author.avatar,
flair: account.author.flair,
};
return {
...accountComment,
author: mergeDefinedFields(comment.author, accountAuthor),
} as T;
}
@@ -0,0 +1,8 @@
export const normalizeAccountCommentIndex = (commentIndex: number | string | null | undefined): number | undefined => {
if (commentIndex === undefined || commentIndex === null || commentIndex === '') {
return undefined;
}
const normalizedCommentIndex = Number(commentIndex);
return Number.isInteger(normalizedCommentIndex) && normalizedCommentIndex >= 0 ? normalizedCommentIndex : undefined;
};
+19 -1
View File
@@ -142,6 +142,24 @@ const getScopedAccountComments = (options?: { commentIndices?: number[]; communi
return scopedComments; return scopedComments;
}; };
const enrichAccountCommentAuthor = (comment: TestComment): TestComment => {
if (comment.author?.address || !comment.accountId || comment.accountId !== testState.account.id || !testState.account.author?.address) {
return comment;
}
const accountAuthor = testState.account.author;
return {
...comment,
author: {
...accountAuthor,
...comment.author,
address: accountAuthor.address,
...(accountAuthor.shortAddress ? { shortAddress: accountAuthor.shortAddress } : {}),
},
};
};
const getScopedFeed = (options?: { filter?: { filter: (comment: TestComment) => boolean }; newerThan?: number; postsPerPage?: number }) => { const getScopedFeed = (options?: { filter?: { filter: (comment: TestComment) => boolean }; newerThan?: number; postsPerPage?: number }) => {
let scopedFeed = [...testState.feed]; let scopedFeed = [...testState.feed];
@@ -165,7 +183,7 @@ vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
useAccount: () => testState.account, useAccount: () => testState.account,
useAccountComments: (options?: { commentIndices?: number[]; communityAddress?: string; newerThan?: number; sortType?: 'new' | 'old' }) => { useAccountComments: (options?: { commentIndices?: number[]; communityAddress?: string; newerThan?: number; sortType?: 'new' | 'old' }) => {
testState.accountCommentsCalls.push(options); testState.accountCommentsCalls.push(options);
return { accountComments: getScopedAccountComments(options) }; return { accountComments: getScopedAccountComments(options).map(enrichAccountCommentAuthor) };
}, },
useFeed: (options?: { useFeed: (options?: {
communities?: unknown[]; communities?: unknown[];
+1 -6
View File
@@ -27,7 +27,6 @@ import { getPageSlice } from '../../lib/utils/board-feed-pagination';
import { getPageFromFeedPath, isDirectoryBoard, normalizeMultiboardFeedPath, stripPageFromFeedPath } from '../../lib/utils/route-utils'; import { getPageFromFeedPath, isDirectoryBoard, normalizeMultiboardFeedPath, stripPageFromFeedPath } from '../../lib/utils/route-utils';
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils'; import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils'; import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import { restoreActiveAccountAuthor } from '../../lib/utils/account-comment-author-utils';
import { getNonokoPendingAccountCommentIndex } from '../../lib/utils/post-options-utils'; import { getNonokoPendingAccountCommentIndex } from '../../lib/utils/post-options-utils';
import { getRawBoardThreadState } from '../../lib/utils/raw-board-thread-state'; import { getRawBoardThreadState } from '../../lib/utils/raw-board-thread-state';
import { getSearchWithTimeFilter, getTimeFilterSuggestion, type TimeFilterSuggestion } from '../../lib/utils/time-filter-utils'; import { getSearchWithTimeFilter, getTimeFilterSuggestion, type TimeFilterSuggestion } from '../../lib/utils/time-filter-utils';
@@ -482,15 +481,11 @@ const Board = ({ feedCacheKey, viewType, boardIdentifier: boardIdentifierProp, t
[recentAccountComments, communityAddress, feedCids, nowSeconds], [recentAccountComments, communityAddress, feedCids, nowSeconds],
); );
const localAccountComments = useMemo(() => { const localAccountComments = useMemo(() => {
const comments = (() => {
if (!nonokoPendingAccountComment) return filteredComments; if (!nonokoPendingAccountComment) return filteredComments;
if (!nonokoPendingAccountComment.cid) return [nonokoPendingAccountComment, ...filteredComments]; if (!nonokoPendingAccountComment.cid) return [nonokoPendingAccountComment, ...filteredComments];
return [nonokoPendingAccountComment, ...filteredComments.filter((comment) => comment.cid !== nonokoPendingAccountComment.cid)]; return [nonokoPendingAccountComment, ...filteredComments.filter((comment) => comment.cid !== nonokoPendingAccountComment.cid)];
})(); }, [nonokoPendingAccountComment, filteredComments]);
return comments.map((comment) => restoreActiveAccountAuthor(comment, account));
}, [nonokoPendingAccountComment, filteredComments, account]);
const sortedFeed = useMemo(() => sortBoardActiveFeed(feed), [feed]); const sortedFeed = useMemo(() => sortBoardActiveFeed(feed), [feed]);
const canShowRecentLocalAccountComments = !isSingleCommunityBoard || sortedFeed.length > 0 || isRawBoardThreadStateFullyLoaded; const canShowRecentLocalAccountComments = !isSingleCommunityBoard || sortedFeed.length > 0 || isRawBoardThreadStateFullyLoaded;
@@ -15,6 +15,7 @@ type TestComment = {
const testState = vi.hoisted(() => ({ const testState = vi.hoisted(() => ({
accountCommentIndex: undefined as string | undefined, accountCommentIndex: undefined as string | undefined,
accountCommentLookupOptions: undefined as { commentIndex?: number } | undefined,
accountComments: [] as TestComment[], accountComments: [] as TestComment[],
challengeCount: 0, challengeCount: 0,
directories: [] as Array<{ address: string; title?: string }>, directories: [] as Array<{ address: string; title?: string }>,
@@ -41,7 +42,10 @@ vi.mock('react-router-dom', async () => {
vi.mock('@bitsocial/bitsocial-react-hooks', () => ({ vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
useAccount: () => undefined, useAccount: () => undefined,
useAccountComment: () => testState.post, useAccountComment: (options?: { commentIndex?: number }) => {
testState.accountCommentLookupOptions = options;
return testState.post;
},
useAccountComments: () => ({ useAccountComments: () => ({
accountComments: testState.accountComments, accountComments: testState.accountComments,
}), }),
@@ -60,10 +64,11 @@ vi.mock('../../../stores/use-challenges-store', () => ({
})); }));
vi.mock('../../../stores/use-failed-post-retry-store', () => ({ vi.mock('../../../stores/use-failed-post-retry-store', () => ({
default: (selector: (state: { retryingAccountCommentIndex: number | null }) => unknown) => selector({ retryingAccountCommentIndex: testState.retryingAccountCommentIndex }), default: (selector: (state: { retryingAccountCommentIndex: number | null }) => unknown) =>
selector({ retryingAccountCommentIndex: testState.retryingAccountCommentIndex }),
})); }));
vi.mock('../../post', () => ({ vi.mock('../../post/post', () => ({
Post: ({ post }: { post?: TestComment }) => createElement('div', { 'data-testid': 'post-view' }, post?.cid ?? 'no-post'), Post: ({ post }: { post?: TestComment }) => createElement('div', { 'data-testid': 'post-view' }, post?.cid ?? 'no-post'),
})); }));
@@ -91,6 +96,7 @@ describe('PendingPost', () => {
beforeEach(() => { beforeEach(() => {
vi.clearAllMocks(); vi.clearAllMocks();
testState.accountCommentIndex = undefined; testState.accountCommentIndex = undefined;
testState.accountCommentLookupOptions = undefined;
testState.accountComments = []; testState.accountComments = [];
testState.challengeCount = 0; testState.challengeCount = 0;
testState.directories = []; testState.directories = [];
@@ -127,6 +133,16 @@ describe('PendingPost', () => {
expect(testState.navigateMock).not.toHaveBeenCalledWith('/not-found', { replace: true }); expect(testState.navigateMock).not.toHaveBeenCalledWith('/not-found', { replace: true });
}); });
it('passes normalized numeric-string pending indices to the account comment lookup', async () => {
testState.accountCommentIndex = '01';
testState.accountComments = [{}, {}];
await renderPendingPost();
expect(testState.accountCommentLookupOptions).toEqual({ commentIndex: 1 });
expect(testState.navigateMock).not.toHaveBeenCalledWith('/not-found', { replace: true });
});
it('redirects invalid pending indices to not found', async () => { it('redirects invalid pending indices to not found', async () => {
testState.accountCommentIndex = '-1'; testState.accountCommentIndex = '-1';
testState.accountComments = [{}, {}]; testState.accountComments = [{}, {}];
+7 -11
View File
@@ -1,13 +1,13 @@
import { useEffect, useRef } from 'react'; import { useEffect, useRef } from 'react';
import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useLocation, useNavigate, useParams } from 'react-router-dom';
import { useAccountComments } from '@bitsocial/bitsocial-react-hooks'; import { useAccountComment, useAccountComments } from '@bitsocial/bitsocial-react-hooks';
import { useDirectories } from '../../hooks/use-directories'; import { useDirectories } from '../../hooks/use-directories';
import useSafeAccountComment from '../../hooks/use-safe-account-comment'; import { normalizeAccountCommentIndex } from '../../lib/utils/account-comment-index-utils';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils'; import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import { getBoardPath } from '../../lib/utils/route-utils'; import { getBoardPath } from '../../lib/utils/route-utils';
import useChallengesStore from '../../stores/use-challenges-store'; import useChallengesStore from '../../stores/use-challenges-store';
import useFailedPostRetryStore from '../../stores/use-failed-post-retry-store'; import useFailedPostRetryStore from '../../stores/use-failed-post-retry-store';
import { Post } from '../post'; import { Post } from '../post/post';
type PendingAccountComment = { type PendingAccountComment = {
index?: number; index?: number;
@@ -46,9 +46,9 @@ const PendingPost = () => {
const { accountComments } = useAccountComments(); const { accountComments } = useAccountComments();
const { accountCommentIndex } = useParams<{ accountCommentIndex?: string }>(); const { accountCommentIndex } = useParams<{ accountCommentIndex?: string }>();
const location = useLocation(); const location = useLocation();
const normalizedAccountCommentIndex = accountCommentIndex === undefined ? undefined : Number(accountCommentIndex); const normalizedAccountCommentIndex = normalizeAccountCommentIndex(accountCommentIndex);
const hasNormalizedAccountCommentIndex = normalizedAccountCommentIndex !== undefined && !Number.isNaN(normalizedAccountCommentIndex); const hasNormalizedAccountCommentIndex = normalizedAccountCommentIndex !== undefined;
const post = useSafeAccountComment({ commentIndex: accountCommentIndex }); const post = useAccountComment({ commentIndex: normalizedAccountCommentIndex });
const postCommunityAddress = getCommentCommunityAddress(post); const postCommunityAddress = getCommentCommunityAddress(post);
const hasAddressablePost = Boolean(post?.cid || postCommunityAddress); const hasAddressablePost = Boolean(post?.cid || postCommunityAddress);
const navigate = useNavigate(); const navigate = useNavigate();
@@ -70,11 +70,7 @@ const PendingPost = () => {
}, [normalizedAccountCommentIndex, pendingBoardPath]); }, [normalizedAccountCommentIndex, pendingBoardPath]);
const isValidAccountCommentIndex = const isValidAccountCommentIndex =
!accountCommentIndex || !accountCommentIndex || (hasNormalizedAccountCommentIndex && hasPendingAccountCommentIndex(accountComments, normalizedAccountCommentIndex));
(hasNormalizedAccountCommentIndex &&
normalizedAccountCommentIndex >= 0 &&
Number.isInteger(normalizedAccountCommentIndex) &&
hasPendingAccountCommentIndex(accountComments, normalizedAccountCommentIndex));
useEffect(() => { useEffect(() => {
// A retry deletes this pending row before republishing, briefly invalidating the index. Stay put; // A retry deletes this pending row before republishing, briefly invalidating the index. Stay put;
+22 -2
View File
@@ -69,6 +69,26 @@ const testState = vi.hoisted(() => ({
evictThreadRefreshCachesMock: vi.fn(), evictThreadRefreshCachesMock: vi.fn(),
})); }));
const activeAccount = {
author: { address: 'account-author' },
id: 'active-account',
};
const enrichAccountCommentAuthor = (comment: TestComment | undefined): TestComment | undefined => {
if (!comment || comment.author?.address || comment.accountId !== activeAccount.id) {
return comment;
}
return {
...comment,
author: {
...activeAccount.author,
...comment.author,
address: activeAccount.author.address,
},
};
};
vi.mock('react-i18next', () => ({ vi.mock('react-i18next', () => ({
useTranslation: () => ({ useTranslation: () => ({
t: (key: string) => key, t: (key: string) => key,
@@ -84,8 +104,8 @@ vi.mock('react-router-dom', async () => {
}); });
vi.mock('@bitsocial/bitsocial-react-hooks', () => ({ vi.mock('@bitsocial/bitsocial-react-hooks', () => ({
useAccount: () => ({ id: 'active-account', author: { address: 'account-author' } }), useAccount: () => activeAccount,
useAccountComment: ({ commentCid }: { commentCid?: string }) => (commentCid ? testState.accountCommentsByCid[commentCid] : undefined), useAccountComment: ({ commentCid }: { commentCid?: string }) => (commentCid ? enrichAccountCommentAuthor(testState.accountCommentsByCid[commentCid]) : undefined),
useComment: ({ commentCid, autoUpdate, community }: { commentCid?: string; autoUpdate?: boolean; community?: { name?: string; publicKey?: string } }) => { useComment: ({ commentCid, autoUpdate, community }: { commentCid?: string; autoUpdate?: boolean; community?: { name?: string; publicKey?: string } }) => {
testState.useCommentCalls.push({ commentCid, autoUpdate, community }); testState.useCommentCalls.push({ commentCid, autoUpdate, community });
return commentCid ? testState.commentsByCid[commentCid] : undefined; return commentCid ? testState.commentsByCid[commentCid] : undefined;
+25 -7
View File
@@ -1,6 +1,15 @@
import { memo, useEffect, useMemo, useRef } from 'react'; import { memo, useEffect, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { type Comment, type CommunityIdentifier, type Role, useAccount, useComment, useEditedComment, useCommunity, useReplies } from '@bitsocial/bitsocial-react-hooks'; import {
type Comment,
type CommunityIdentifier,
type Role,
useAccountComment,
useComment,
useEditedComment,
useCommunity,
useReplies,
} from '@bitsocial/bitsocial-react-hooks';
import { communitiesPagesStore as useCommunitiesPagesStore } from '../../lib/bitsocial-internals/stores'; import { communitiesPagesStore as useCommunitiesPagesStore } from '../../lib/bitsocial-internals/stores';
import { useCommunityField } from '../../hooks/use-stable-community'; import { useCommunityField } from '../../hooks/use-stable-community';
import { useLocation, useNavigate, useParams } from 'react-router-dom'; import { useLocation, useNavigate, useParams } from 'react-router-dom';
@@ -11,7 +20,6 @@ import { useCommunityIdentifier } from '../../hooks/use-community-identifiers';
import { isCommentArchived } from '../../lib/utils/comment-moderation-utils'; import { isCommentArchived } from '../../lib/utils/comment-moderation-utils';
import { areSameBoardAddress, isDirectoryBoard } from '../../lib/utils/route-utils'; import { areSameBoardAddress, isDirectoryBoard } from '../../lib/utils/route-utils';
import { getCommentCommunityAddress } from '../../lib/utils/comment-utils'; import { getCommentCommunityAddress } from '../../lib/utils/comment-utils';
import { mergeDefinedFields, restoreActiveAccountAuthor } from '../../lib/utils/account-comment-author-utils';
import useIsMobile from '../../hooks/use-is-mobile'; import useIsMobile from '../../hooks/use-is-mobile';
import ErrorDisplay from '../../components/error-display/error-display'; import ErrorDisplay from '../../components/error-display/error-display';
import { PageFooterDesktop, ThreadFooterFirstRow, ThreadFooterStyleRow, ThreadFooterMobile } from '../../components/footer/footer'; import { PageFooterDesktop, ThreadFooterFirstRow, ThreadFooterStyleRow, ThreadFooterMobile } from '../../components/footer/footer';
@@ -21,7 +29,6 @@ import { getRequestedThreadTopCid, scrollThreadContainerToTop } from '../../lib/
import { evictThreadRefreshCaches } from '../../lib/utils/thread-refresh-cache-utils'; import { evictThreadRefreshCaches } from '../../lib/utils/thread-refresh-cache-utils';
import { REPLIES_PER_PAGE } from '../../lib/constants'; import { REPLIES_PER_PAGE } from '../../lib/constants';
import useThreadLiveUpdatesStore from '../../stores/use-thread-live-updates-store'; import useThreadLiveUpdatesStore from '../../stores/use-thread-live-updates-store';
import useSafeAccountComment from '../../hooks/use-safe-account-comment';
import type { QueuedCommentRouteState } from '../../lib/utils/mod-queue-utils'; import type { QueuedCommentRouteState } from '../../lib/utils/mod-queue-utils';
import type { ReplyVirtualizationMode } from '../../lib/utils/pretext-height-estimates'; import type { ReplyVirtualizationMode } from '../../lib/utils/pretext-height-estimates';
import styles from './post.module.css'; import styles from './post.module.css';
@@ -102,6 +109,19 @@ const mergeCommentFallback = (comment: CommentWithRefresh | undefined, fallback:
}; };
}; };
const mergeDefinedFields = <T extends object>(base: T | undefined, override: T | undefined): T | undefined => {
if (!override) return base;
const merged = { ...base } as Record<string, unknown>;
for (const [key, value] of Object.entries(override)) {
if (value !== undefined) {
merged[key] = value;
}
}
return merged as T;
};
const mergeLocalCommentAuthor = (comment: CommentWithRefresh | undefined, localComment: CommentWithRefresh | undefined): CommentWithRefresh | undefined => { const mergeLocalCommentAuthor = (comment: CommentWithRefresh | undefined, localComment: CommentWithRefresh | undefined): CommentWithRefresh | undefined => {
if (!localComment?.author) return comment; if (!localComment?.author) return comment;
if (!comment) return localComment; if (!comment) return localComment;
@@ -130,9 +150,7 @@ const mergeLocalAccountComment = (comment: CommentWithRefresh | undefined, accou
const useCommentWithFeedCache = (options: { commentCid: string | undefined; autoUpdate?: boolean; community?: CommunityIdentifier }): CommentWithRefresh | undefined => { const useCommentWithFeedCache = (options: { commentCid: string | undefined; autoUpdate?: boolean; community?: CommunityIdentifier }): CommentWithRefresh | undefined => {
const comment = useComment(options); const comment = useComment(options);
const cachedComment = useCommunitiesPagesStore((state) => state.comments[options?.commentCid || '']); const cachedComment = useCommunitiesPagesStore((state) => state.comments[options?.commentCid || '']);
const account = useAccount(); const accountComment = useAccountComment({ commentCid: options.commentCid }) as CommentWithRefresh | undefined;
const accountComment = useSafeAccountComment({ commentCid: options.commentCid }) as CommentWithRefresh | undefined;
const accountCommentWithAuthor = useMemo(() => restoreActiveAccountAuthor(accountComment, account), [accountComment, account]);
const commentWithFeedCache = useMemo(() => { const commentWithFeedCache = useMemo(() => {
if (!cachedComment || comment?.timestamp) return comment; if (!cachedComment || comment?.timestamp) return comment;
@@ -145,7 +163,7 @@ const useCommentWithFeedCache = (options: { commentCid: string | undefined; auto
} as CommentWithRefresh; } as CommentWithRefresh;
}, [comment, cachedComment]); }, [comment, cachedComment]);
return useMemo(() => mergeLocalAccountComment(commentWithFeedCache, accountCommentWithAuthor), [commentWithFeedCache, accountCommentWithAuthor]); return useMemo(() => mergeLocalAccountComment(commentWithFeedCache, accountComment), [commentWithFeedCache, accountComment]);
}; };
const mergeRepliesWithQueuedReply = (replies: Comment[], queuedReply: CommentWithRefresh | undefined): Comment[] => { const mergeRepliesWithQueuedReply = (replies: Comment[], queuedReply: CommentWithRefresh | undefined): Comment[] => {
+5 -5
View File
@@ -10,7 +10,7 @@ __metadata:
resolution: "5chan@workspace:." resolution: "5chan@workspace:."
dependencies: dependencies:
"@bbob/parser": "npm:4.3.1" "@bbob/parser": "npm:4.3.1"
"@bitsocial/bitsocial-react-hooks": "npm:0.1.19" "@bitsocial/bitsocial-react-hooks": "npm:0.1.21"
"@bitsocial/bso-resolver": "npm:0.0.8" "@bitsocial/bso-resolver": "npm:0.0.8"
"@capacitor/android": "npm:7.4.5" "@capacitor/android": "npm:7.4.5"
"@capacitor/app": "npm:7.0.1" "@capacitor/app": "npm:7.0.1"
@@ -1582,9 +1582,9 @@ __metadata:
languageName: node languageName: node
linkType: hard linkType: hard
"@bitsocial/bitsocial-react-hooks@npm:0.1.19": "@bitsocial/bitsocial-react-hooks@npm:0.1.21":
version: 0.1.19 version: 0.1.21
resolution: "@bitsocial/bitsocial-react-hooks@npm:0.1.19" resolution: "@bitsocial/bitsocial-react-hooks@npm:0.1.21"
dependencies: dependencies:
"@bitsocial/bso-resolver": "npm:0.0.8" "@bitsocial/bso-resolver": "npm:0.0.8"
"@pkcprotocol/pkc-js": "npm:0.0.48" "@pkcprotocol/pkc-js": "npm:0.0.48"
@@ -1603,7 +1603,7 @@ __metadata:
zustand: "npm:4.0.0" zustand: "npm:4.0.0"
peerDependencies: peerDependencies:
react: ">=16.8" react: ">=16.8"
checksum: 10c0/5645249211c4b0d91b0e9f6c91b75703da15ff5dbf37bc2eab2353959818bb0d2803ee649fae30530a42298ec55c34e2f3900c71025c89af35a0314d622f42a3 checksum: 10c0/02f9faf7069b6752dc814c6484964bf7cfeb3739690a1ca268d0d3f641d7d79d95015d27cbdf6b2f5656f41ff993a149aafcec2f0f367dea105c088c76b737b7
languageName: node languageName: node
linkType: hard linkType: hard