mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
feat: auto subscribe new accounts to specific default subplebbits
This commit is contained in:
@@ -5,7 +5,7 @@ import { Link, useLocation, useNavigate, useParams } from 'react-router-dom';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isCatalogView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
import styles from './topbar.module.css';
|
import styles from './topbar.module.css';
|
||||||
import useDefaultSubplebbits, { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||||
import _, { debounce } from 'lodash';
|
import _, { debounce } from 'lodash';
|
||||||
import { TimeFilter } from '../board-buttons';
|
import { TimeFilter } from '../board-buttons';
|
||||||
|
|
||||||
@@ -67,52 +67,36 @@ const SearchBar = ({ setShowSearchBar }: { setShowSearchBar: (show: boolean) =>
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderBoardsList = (subplebbits: any, isInCatalogView: boolean, subscriptions: string[]) =>
|
|
||||||
subplebbits?.length > 0 && (
|
|
||||||
<>
|
|
||||||
[
|
|
||||||
{subplebbits.map((sub: any, index: any) => (
|
|
||||||
<span key={index}>
|
|
||||||
{index === 0 ? null : ' '}
|
|
||||||
<Link to={`/p/${sub.address}${isInCatalogView ? '/catalog' : ''}`}>
|
|
||||||
{sub.address.endsWith('.eth') || sub.address.endsWith('.sol') ? sub.address : sub.address.slice(0, 10).concat('...')}
|
|
||||||
</Link>
|
|
||||||
{index !== subplebbits.length - 1 ? ' /' : null}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
]{' '}
|
|
||||||
{subscriptions?.length > 0 && (
|
|
||||||
<>
|
|
||||||
[
|
|
||||||
{subscriptions.map((address: any, index: any) => (
|
|
||||||
<span key={index}>
|
|
||||||
{index === 0 ? null : ' '}
|
|
||||||
<Link to={`/p/${address}${isInCatalogView ? '/catalog' : ''}`}>
|
|
||||||
{address.endsWith('.eth') || address.endsWith('.sol') ? address : address.slice(0, 10).concat('...')}
|
|
||||||
</Link>
|
|
||||||
{index !== subscriptions?.length - 1 ? ' /' : null}
|
|
||||||
</span>
|
|
||||||
))}
|
|
||||||
]{' '}
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
|
|
||||||
const TopBarDesktop = () => {
|
const TopBarDesktop = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const account = useAccount();
|
const account = useAccount();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const isInCatalogView = isCatalogView(location.pathname, params);
|
const isInCatalogView = isCatalogView(location.pathname, params);
|
||||||
const subplebbits = useDefaultSubplebbits();
|
|
||||||
const [showSearchBar, setShowSearchBar] = useState(false);
|
const [showSearchBar, setShowSearchBar] = useState(false);
|
||||||
|
|
||||||
|
const subscriptions = account?.subscriptions;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.boardNavDesktop}>
|
<div className={styles.boardNavDesktop}>
|
||||||
<span className={styles.boardList}>
|
<span className={styles.boardList}>
|
||||||
[<Link to='/p/all'>all</Link> / <Link to='/p/subscriptions'>subscriptions</Link>]{' '}
|
[<Link to='/p/all'>all</Link> / <Link to='/p/subscriptions'>subscriptions</Link>]{' '}
|
||||||
{renderBoardsList(subplebbits.slice(0, 15), isInCatalogView, account?.subscriptions)}[
|
{subscriptions?.length > 0 && (
|
||||||
|
<>
|
||||||
|
[
|
||||||
|
{subscriptions.map((address: any, index: any) => (
|
||||||
|
<span key={index}>
|
||||||
|
{index === 0 ? null : ' '}
|
||||||
|
<Link to={`/p/${address}${isInCatalogView ? '/catalog' : ''}`}>
|
||||||
|
{address.endsWith('.eth') || address.endsWith('.sol') ? address : address.slice(0, 10).concat('...')}
|
||||||
|
</Link>
|
||||||
|
{index !== subscriptions?.length - 1 ? ' /' : null}
|
||||||
|
</span>
|
||||||
|
))}
|
||||||
|
]{' '}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
[
|
||||||
<Link
|
<Link
|
||||||
className={styles.disabledButton}
|
className={styles.disabledButton}
|
||||||
to='boards/create'
|
to='boards/create'
|
||||||
|
|||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useAccount, setAccount } from '@plebbit/plebbit-react-hooks';
|
||||||
|
import { useAutoSubscribeStore } from '../stores/use-auto-subscribe-store';
|
||||||
|
import { getAutoSubscribeAddresses, useDefaultSubplebbits } from './use-default-subplebbits';
|
||||||
|
|
||||||
|
const AUTO_SUBSCRIBE_KEY_PREFIX = 'seedit-auto-subscribe-done-';
|
||||||
|
|
||||||
|
// Keep track of which accounts have been processed globally
|
||||||
|
const processedAccounts = new Set<string>();
|
||||||
|
|
||||||
|
export const useAutoSubscribe = () => {
|
||||||
|
const account = useAccount();
|
||||||
|
const accountAddress = account?.author?.address;
|
||||||
|
const defaultSubplebbits = useDefaultSubplebbits();
|
||||||
|
const { addCheckingAccount, removeCheckingAccount, isCheckingAccount } = useAutoSubscribeStore();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!accountAddress) return;
|
||||||
|
|
||||||
|
// Mark as checking immediately when account changes
|
||||||
|
addCheckingAccount(accountAddress);
|
||||||
|
|
||||||
|
const processAutoSubscribe = async () => {
|
||||||
|
if (!account || !defaultSubplebbits?.length) return;
|
||||||
|
|
||||||
|
if (processedAccounts.has(accountAddress)) {
|
||||||
|
removeCheckingAccount(accountAddress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const storageKey = AUTO_SUBSCRIBE_KEY_PREFIX + accountAddress;
|
||||||
|
const hasAutoSubscribed = localStorage.getItem(storageKey);
|
||||||
|
|
||||||
|
if (account.subscriptions?.length > 0 || hasAutoSubscribed) {
|
||||||
|
processedAccounts.add(accountAddress);
|
||||||
|
removeCheckingAccount(accountAddress);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const autoSubscribeAddresses = getAutoSubscribeAddresses();
|
||||||
|
if (autoSubscribeAddresses.length) {
|
||||||
|
try {
|
||||||
|
await setAccount({
|
||||||
|
...account,
|
||||||
|
subscriptions: autoSubscribeAddresses,
|
||||||
|
});
|
||||||
|
localStorage.setItem(storageKey, 'true');
|
||||||
|
processedAccounts.add(accountAddress);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Auto-subscribe error:', error);
|
||||||
|
}
|
||||||
|
removeCheckingAccount(accountAddress);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
processAutoSubscribe();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (accountAddress) removeCheckingAccount(accountAddress);
|
||||||
|
};
|
||||||
|
}, [account, accountAddress, defaultSubplebbits, addCheckingAccount, removeCheckingAccount]);
|
||||||
|
|
||||||
|
return { isCheckingSubscriptions: accountAddress ? isCheckingAccount(accountAddress) : true };
|
||||||
|
};
|
||||||
@@ -12,12 +12,14 @@ export interface MultisubSubplebbit {
|
|||||||
address: string;
|
address: string;
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
features?: string[];
|
features?: string[];
|
||||||
|
plebchanAutoSubscribe?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
let cacheSubplebbits: MultisubSubplebbit[] | null = null;
|
let cacheSubplebbits: MultisubSubplebbit[] | null = null;
|
||||||
let cacheMetadata: MultisubMetadata | null = null;
|
let cacheMetadata: MultisubMetadata | null = null;
|
||||||
|
let cacheAutoSubscribeAddresses: string[] | null = null;
|
||||||
|
|
||||||
const useDefaultSubplebbits = () => {
|
export const useDefaultSubplebbits = () => {
|
||||||
const [subplebbits, setSubplebbits] = useState<MultisubSubplebbit[]>([]);
|
const [subplebbits, setSubplebbits] = useState<MultisubSubplebbit[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -31,6 +33,12 @@ const useDefaultSubplebbits = () => {
|
|||||||
// { cache: 'no-cache' }
|
// { cache: 'no-cache' }
|
||||||
).then((res) => res.json());
|
).then((res) => res.json());
|
||||||
cacheSubplebbits = multisub.subplebbits;
|
cacheSubplebbits = multisub.subplebbits;
|
||||||
|
|
||||||
|
// Cache auto-subscribe addresses when we fetch subplebbits
|
||||||
|
cacheAutoSubscribeAddresses = multisub.subplebbits
|
||||||
|
.filter((sub: MultisubSubplebbit) => sub.plebchanAutoSubscribe && sub.address)
|
||||||
|
.map((sub: MultisubSubplebbit) => sub.address);
|
||||||
|
|
||||||
setSubplebbits(multisub.subplebbits);
|
setSubplebbits(multisub.subplebbits);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
@@ -72,4 +80,4 @@ export const useMultisubMetadata = () => {
|
|||||||
return cacheMetadata || metadata;
|
return cacheMetadata || metadata;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default useDefaultSubplebbits;
|
export const getAutoSubscribeAddresses = () => cacheAutoSubscribeAddresses || [];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import useThemeStore from '../stores/use-theme-store';
|
import useThemeStore from '../stores/use-theme-store';
|
||||||
import useDefaultSubplebbits from './use-default-subplebbits';
|
import { useDefaultSubplebbits } from './use-default-subplebbits';
|
||||||
import { isAllView, isHomeView, isNotFoundView, isPendingPostView, isSubscriptionsView } from '../lib/utils/view-utils';
|
import { isAllView, isHomeView, isNotFoundView, isPendingPostView, isSubscriptionsView } from '../lib/utils/view-utils';
|
||||||
import { nsfwTags } from '../views/home/home';
|
import { nsfwTags } from '../views/home/home';
|
||||||
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ const useIsSubplebbitOffline = (subplebbit: Subplebbit) => {
|
|||||||
const subplebbitOfflineStore = subplebbitOfflineState[address] || { initialLoad: true };
|
const subplebbitOfflineStore = subplebbitOfflineState[address] || { initialLoad: true };
|
||||||
const loadingStartTimestamp = subplebbitsLoadingStartTimestamps[0] || 0;
|
const loadingStartTimestamp = subplebbitsLoadingStartTimestamps[0] || 0;
|
||||||
|
|
||||||
const isLoading = subplebbitOfflineStore.initialLoad && (!updatedAt || Date.now() / 1000 - updatedAt >= 120 * 60) && Date.now() / 1000 - loadingStartTimestamp < 30;
|
const isLoading = subplebbitOfflineStore.initialLoad && (!updatedAt || Date.now() / 1000 - updatedAt >= 120 * 120) && Date.now() / 1000 - loadingStartTimestamp < 30;
|
||||||
|
|
||||||
const isOffline = !isLoading && ((updatedAt && updatedAt < Date.now() / 1000 - 120 * 60) || (!updatedAt && Date.now() / 1000 - loadingStartTimestamp >= 30));
|
const isOffline = !isLoading && ((updatedAt && updatedAt < Date.now() / 1000 - 120 * 120) || (!updatedAt && Date.now() / 1000 - loadingStartTimestamp >= 30));
|
||||||
|
|
||||||
const isOnline = updatedAt && Date.now() / 1000 - updatedAt < 120 * 60;
|
const isOnline = updatedAt && Date.now() / 1000 - updatedAt < 120 * 120;
|
||||||
const offlineIconClass = isLoading ? 'yellowOfflineIcon' : isOffline ? 'redOfflineIcon' : '';
|
const offlineIconClass = isLoading ? 'yellowOfflineIcon' : isOffline ? 'redOfflineIcon' : '';
|
||||||
|
|
||||||
const offlineTitle = isLoading
|
const offlineTitle = isLoading
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react';
|
|||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { isAllView, isSubscriptionsView } from '../lib/utils/view-utils';
|
import { isAllView, isSubscriptionsView } from '../lib/utils/view-utils';
|
||||||
import useThemeStore from '../stores/use-theme-store';
|
import useThemeStore from '../stores/use-theme-store';
|
||||||
import useDefaultSubplebbits from './use-default-subplebbits';
|
import { useDefaultSubplebbits } from './use-default-subplebbits';
|
||||||
import useInitialTheme from './use-initial-theme';
|
import useInitialTheme from './use-initial-theme';
|
||||||
import { nsfwTags } from '../views/home/home';
|
import { nsfwTags } from '../views/home/home';
|
||||||
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
import { useAccountComment } from '@plebbit/plebbit-react-hooks';
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import { create } from 'zustand';
|
||||||
|
|
||||||
|
interface AutoSubscribeState {
|
||||||
|
checkingAccounts: Set<string>;
|
||||||
|
addCheckingAccount: (address: string) => void;
|
||||||
|
removeCheckingAccount: (address: string) => void;
|
||||||
|
isCheckingAccount: (address: string) => boolean;
|
||||||
|
reset: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAutoSubscribeStore = create<AutoSubscribeState>((set, get) => ({
|
||||||
|
checkingAccounts: new Set(),
|
||||||
|
addCheckingAccount: (address) =>
|
||||||
|
set((state) => ({
|
||||||
|
checkingAccounts: new Set(state.checkingAccounts).add(address),
|
||||||
|
})),
|
||||||
|
removeCheckingAccount: (address) =>
|
||||||
|
set((state) => {
|
||||||
|
const newSet = new Set(state.checkingAccounts);
|
||||||
|
newSet.delete(address);
|
||||||
|
return { checkingAccounts: newSet };
|
||||||
|
}),
|
||||||
|
isCheckingAccount: (address) => get().checkingAccounts.has(address),
|
||||||
|
reset: () => set({ checkingAccounts: new Set() }),
|
||||||
|
}));
|
||||||
+22
-13
@@ -6,7 +6,6 @@ import { Trans, useTranslation } from 'react-i18next';
|
|||||||
import styles from './board.module.css';
|
import styles from './board.module.css';
|
||||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||||
import useFeedStateString from '../../hooks/use-feed-state-string';
|
|
||||||
import useReplyModal from '../../hooks/use-reply-modal';
|
import useReplyModal from '../../hooks/use-reply-modal';
|
||||||
import useTimeFilter from '../../hooks/use-time-filter';
|
import useTimeFilter from '../../hooks/use-time-filter';
|
||||||
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
||||||
@@ -20,6 +19,7 @@ import SubplebbitRules from '../../components/subplebbit-rules';
|
|||||||
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
|
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
|
||||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||||
import { shouldShowSnow } from '../../lib/snow';
|
import { shouldShowSnow } from '../../lib/snow';
|
||||||
|
import { useAutoSubscribe } from '../../hooks/use-auto-subscribe';
|
||||||
|
|
||||||
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
|
||||||
|
|
||||||
@@ -31,6 +31,7 @@ const threadsWithoutImagesFilter = (comment: Comment) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const Board = () => {
|
const Board = () => {
|
||||||
|
const { isCheckingSubscriptions } = useAutoSubscribe();
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
|
const { subplebbitAddress } = useParams<{ subplebbitAddress: string }>();
|
||||||
@@ -295,18 +296,26 @@ const Board = () => {
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{rules && !description && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
|
{rules && !description && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
|
||||||
<Virtuoso
|
{isCheckingSubscriptions ? (
|
||||||
increaseViewportBy={{ bottom: 1200, top: 1200 }}
|
<div className={styles.feed}>
|
||||||
totalCount={combinedFeed.length}
|
<div className={styles.footer}>
|
||||||
data={combinedFeed}
|
<LoadingEllipsis string={t('loading_feed')} />
|
||||||
itemContent={(index, post) => <Post index={index} post={post} openReplyModal={openReplyModal} />}
|
</div>
|
||||||
useWindowScroll={true}
|
</div>
|
||||||
components={{ Footer }}
|
) : (
|
||||||
endReached={loadMore}
|
<Virtuoso
|
||||||
ref={virtuosoRef}
|
increaseViewportBy={{ bottom: 1200, top: 1200 }}
|
||||||
restoreStateFrom={lastVirtuosoState}
|
totalCount={combinedFeed.length}
|
||||||
initialScrollTop={lastVirtuosoState?.scrollTop}
|
data={combinedFeed}
|
||||||
/>
|
itemContent={(index, post) => <Post index={index} post={post} openReplyModal={openReplyModal} />}
|
||||||
|
useWindowScroll={true}
|
||||||
|
components={{ Footer }}
|
||||||
|
endReached={loadMore}
|
||||||
|
ref={virtuosoRef}
|
||||||
|
restoreStateFrom={lastVirtuosoState}
|
||||||
|
initialScrollTop={lastVirtuosoState?.scrollTop}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -6,8 +6,7 @@ import { Virtuoso, VirtuosoHandle, StateSnapshot } from 'react-virtuoso';
|
|||||||
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
import { getCommentMediaInfo, getHasThumbnail } from '../../lib/utils/media-utils';
|
||||||
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
import { isAllView, isSubscriptionsView } from '../../lib/utils/view-utils';
|
||||||
import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
|
import useCatalogFeedRows from '../../hooks/use-catalog-feed-rows';
|
||||||
import useDefaultSubplebbits from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbits } from '../../hooks/use-default-subplebbits';
|
||||||
import useFeedStateString from '../../hooks/use-feed-state-string';
|
|
||||||
import useTimeFilter from '../../hooks/use-time-filter';
|
import useTimeFilter from '../../hooks/use-time-filter';
|
||||||
import useWindowWidth from '../../hooks/use-window-width';
|
import useWindowWidth from '../../hooks/use-window-width';
|
||||||
import useCatalogStyleStore from '../../stores/use-catalog-style-store';
|
import useCatalogStyleStore from '../../stores/use-catalog-style-store';
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Trans, useTranslation } from 'react-i18next';
|
|||||||
import { useSubplebbits } from '@plebbit/plebbit-react-hooks';
|
import { useSubplebbits } from '@plebbit/plebbit-react-hooks';
|
||||||
import styles from './home.module.css';
|
import styles from './home.module.css';
|
||||||
import packageJson from '../../../package.json';
|
import packageJson from '../../../package.json';
|
||||||
import useDefaultSubplebbits, { useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
import { useDefaultSubplebbits, useDefaultSubplebbitAddresses } from '../../hooks/use-default-subplebbits';
|
||||||
import useSubplebbitsStats from '../../hooks/use-subplebbits-stats';
|
import useSubplebbitsStats from '../../hooks/use-subplebbits-stats';
|
||||||
import PopularThreadsBox from './popular-threads-box';
|
import PopularThreadsBox from './popular-threads-box';
|
||||||
import BoardsList from './boards-list';
|
import BoardsList from './boards-list';
|
||||||
|
|||||||
Reference in New Issue
Block a user