feat: add copy user ID menu item and rename copy link to copy direct link

Add 'Copy user ID' button to both desktop and mobile post menus, allowing users to copy the full author address. This provides a way to verify authenticity against the 8-char display ID. Also rename 'Copy link' to 'Copy direct link' for clarity. Update security comments to reflect the new approach and add translations for all 35 languages.
This commit is contained in:
plebeius
2026-02-10 13:45:55 +08:00
parent 24d329a329
commit 4a96f88efd
39 changed files with 149 additions and 74 deletions
+1 -1
View File
@@ -197,7 +197,7 @@ const PostInfo = ({
const alertThresholdSeconds = getAlertThresholdSeconds();
const isOverThreshold = isAwaitingApproval && timeWaiting > alertThresholdSeconds;
const userID = address && Plebbit.getShortAddress({ address }); // should not be shortened to less than 12 characters, because users can create unlimited addresses/IDs before authenticating or passing challenges, so if the ID is short enough they can spoof it to troll users with the same ID
const userID = address && Plebbit.getShortAddress({ address }); // shortened to 8 chars for display; users can verify the full user ID via "Copy user ID" in the post menu to guard against spoofing
const userIDBackgroundColor = hashStringToColor(userID);
const userIDTextColor = getTextColorForBackground(userIDBackgroundColor);
@@ -39,7 +39,7 @@ const CopyLinkButton = ({ cid, subplebbitAddress, linkType, onClose }: CopyLinkB
}
}}
>
<div className={styles.postMenuItem}>{t('copy_link')}</div>
<div className={styles.postMenuItem}>{t('copy_direct_link')}</div>
</div>
);
};
@@ -63,6 +63,25 @@ const CopyContentIdButton = ({ cid, onClose }: { cid: string; onClose: () => voi
);
};
const CopyUserIdButton = ({ address, onClose }: { address: string; onClose: () => void }) => {
const { t } = useTranslation();
return (
<div
onClick={async () => {
try {
await copyToClipboard(address);
} catch (error) {
console.error('Failed to copy user id', error);
} finally {
onClose();
}
}}
>
<div className={styles.postMenuItem}>{t('copy_user_id')}</div>
</div>
);
};
const ImageSearchButton = ({ url, onClose }: { url: string; onClose: () => void }) => {
const { t } = useTranslation();
const [isImageSearchMenuOpen, setIsImageSearchMenuOpen] = useState(false);
@@ -178,6 +197,7 @@ const PostMenuDesktop = ({ postMenu }: PostMenuDesktopProps) => {
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
{authorAddress && <CopyUserIdButton address={authorAddress} onClose={handleClose} />}
{!(isInPostPageView && postCid === cid) && (
<div
className={styles.postMenuItem}
@@ -47,7 +47,7 @@ const CopyLinkButton = ({ cid, subplebbitAddress, linkType, onClose }: CopyLinkB
}
}}
>
<div className={styles.postMenuItem}>{t('copy_link')}</div>
<div className={styles.postMenuItem}>{t('copy_direct_link')}</div>
</div>
);
};
@@ -71,6 +71,25 @@ const CopyContentIdButton = ({ cid, onClose }: { cid: string; onClose: () => voi
);
};
const CopyUserIdButton = ({ address, onClose }: { address: string; onClose: () => void }) => {
const { t } = useTranslation();
return (
<div
onClick={async () => {
try {
await copyToClipboard(address);
} catch (error) {
console.error('Failed to copy user id', error);
} finally {
onClose();
}
}}
>
<div className={styles.postMenuItem}>{t('copy_user_id')}</div>
</div>
);
};
const ImageSearchButtons = ({ url, onClose }: { url: string; onClose: () => void }) => {
const { t } = useTranslation();
return (
@@ -180,6 +199,7 @@ const PostMenuMobile = ({ postMenu, editMenuPost }: PostMenuMobileProps) => {
<div className={styles.postMenu} ref={refs.setFloating} style={floatingStyles} aria-labelledby={headingId} {...getFloatingProps()}>
{cid && subplebbitAddress && <CopyLinkButton cid={cid} subplebbitAddress={subplebbitAddress} linkType='thread' onClose={handleClose} />}
{cid && <CopyContentIdButton cid={cid} onClose={handleClose} />}
{authorAddress && <CopyUserIdButton address={authorAddress} onClose={handleClose} />}
{cid && subplebbitAddress && <HidePostButton cid={cid} isReply={!!parentCid} postCid={postCid} onClose={handleClose} />}
{cid && subplebbitAddress && authorAddress && <BlockUserButton address={authorAddress} />}
{cid && subplebbitAddress && !isInBoardView && <BlockBoardButton address={subplebbitAddress} />}
+1 -1
View File
@@ -173,7 +173,7 @@ const PostInfoAndMedia = ({ post, postReplyCount = 0, roles, threadNumber }: Pos
const pseudonymityMode = useSubplebbitField(subplebbitAddress, (sub) => sub?.features?.pseudonymityMode);
const showUserID = pseudonymityMode !== 'per-reply';
const userID = address && Plebbit.getShortAddress({ address }); // should not be shortened to less than 12 characters, because users can create unlimited addresses/IDs before authenticating or passing challenges, so if the ID is short enough they can spoof it to troll users with the same ID
const userID = address && Plebbit.getShortAddress({ address }); // shortened to 8 chars for display; users can verify the full user ID via "Copy user ID" in the post menu to guard against spoofing
const userIDBackgroundColor = hashStringToColor(userID);
const userIDTextColor = getTextColorForBackground(userIDBackgroundColor);