mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix(quotes): handle cross-thread quotes (publish + hover preview) (#1153)
* fix(quotes): publish only same-thread quoted cids
Quoting a post from another thread failed to publish with "One or more
quoted CIDs are not under the same post". That message is the protocol
error ERR_QUOTED_CID_NOT_UNDER_POST from @pkcprotocol/pkc-js, not a custom
5chan error: a reply's quotedCids must all live under the reply's own
thread.
The reply publish path resolved every >>number quotelink to a CID through
the board-scoped number map and sent them all as quotedCids, so any
cross-thread quotelink made the protocol reject the whole post.
Track each comment's thread (postCid) in usePostNumberStore and filter the
merged quotedCids to same-thread entries before publishing. Cross-thread
quotelinks still render and navigate via their >>number text; only the
published metadata changes. Same-thread replies resolved on demand by the
external-quote resolver are preserved because the resolver registers what
it finds.
Regression introduced in 9f14a6326 (populate quotedCids when publishing
replies with quote references).
* fix(quotes): show hover preview for cross-thread quotelinks
Hovering a >>number quote whose thread was not loaded showed nothing: the
quotelink rendered as an inert span with no floating preview. NumberQuoteLink
only fell back to the lazy resolver when the number->cid mapping was unknown.
When the cid was known but the comment body was not cached, it rendered
ReplyQuotePreview with an undefined comment, which is treated as pending
resolution and rendered as inert text that never fetched the body (the lookup
used onlyIfCached).
Load the body lazily on hover instead: NumberQuoteLink keeps onlyIfCached until
the quotelink is hovered, and ReplyQuotePreview's pending quotelink is now
hoverable when its cid is known so it can request that fetch and prime the
floating-preview position. Once the body resolves the preview appears without
re-hovering. Same-thread quotes stay cached, so this never fetches for them.
* fix(quotes): filter the final quotedCids payload to same-thread
Apply the same-thread filter to the merged publish options rather than only the
derived subset, so any quotedCids carried on stored publishCommentOptions cannot
bypass the guard. Defense in depth for the protocol's same-thread requirement
(ERR_QUOTED_CID_NOT_UNDER_POST); addresses PR review feedback.
This commit is contained in:
@@ -10,7 +10,7 @@ import {
|
||||
hasEnoughPreviewReplies,
|
||||
sortRepliesForDisplay,
|
||||
} from '../replies-preview-utils';
|
||||
import { getQuotedCidsFromContent, mergeQuotedCids } from '../reply-quote-utils';
|
||||
import { filterSameThreadQuotedCids, getQuotedCidsFromContent, mergeQuotedCids } from '../reply-quote-utils';
|
||||
import { formatUserIDForDisplay, truncateWithEllipsisInMiddle } from '../string-utils';
|
||||
import { getActiveSpecialTheme, getFormattedDate, getFormattedTimeAgo, getSpecialThemeClass, isChristmas, isHalloween } from '../time-utils';
|
||||
|
||||
@@ -249,6 +249,18 @@ describe('misc utils', () => {
|
||||
expect(mergeQuotedCids(undefined, ['cid-12'])).toBeUndefined();
|
||||
});
|
||||
|
||||
it('keeps only same-thread quoted cids so cross-thread quotes are not published', () => {
|
||||
const cidToPostCid = { 'cid-12': 'thread-a', 'cid-45': 'thread-b', 'cid-op': 'thread-a' };
|
||||
// cid-12 and the OP (cid-op) are under thread-a; cid-45 lives under another thread and is dropped.
|
||||
expect(filterSameThreadQuotedCids(['cid-12', 'cid-45', 'cid-op'], cidToPostCid, 'thread-a')).toEqual(['cid-12', 'cid-op']);
|
||||
// Every quote points at another thread, so nothing is published.
|
||||
expect(filterSameThreadQuotedCids(['cid-45'], cidToPostCid, 'thread-a')).toBeUndefined();
|
||||
// Unknown thread mapping or missing inputs resolve to no quoted cids.
|
||||
expect(filterSameThreadQuotedCids(['cid-unknown'], cidToPostCid, 'thread-a')).toBeUndefined();
|
||||
expect(filterSameThreadQuotedCids(['cid-12'], cidToPostCid, undefined)).toBeUndefined();
|
||||
expect(filterSameThreadQuotedCids(undefined, cidToPostCid, 'thread-a')).toBeUndefined();
|
||||
});
|
||||
|
||||
it('formats ids, truncates long strings, and localizes time labels', () => {
|
||||
expect(formatUserIDForDisplay('board.eth')).toBe('board.eth');
|
||||
expect(formatUserIDForDisplay('averyverylongdomainname.eth', 12)).toBe('averyvery...');
|
||||
|
||||
@@ -11,6 +11,19 @@ export const getQuotedCidsFromContent = (content: string | undefined, numberToCi
|
||||
return cids.size > 0 ? [...cids] : undefined;
|
||||
};
|
||||
|
||||
// The protocol only accepts a reply whose quotedCids all live under the same post/thread
|
||||
// (ERR_QUOTED_CID_NOT_UNDER_POST), so drop cross-thread quotes here. The >>number text stays
|
||||
// in the content and still renders/navigates as a quotelink; only the published metadata changes.
|
||||
export const filterSameThreadQuotedCids = (
|
||||
quotedCids: string[] | undefined,
|
||||
cidToPostCid: Record<string, string> | undefined,
|
||||
threadPostCid: string | undefined,
|
||||
): string[] | undefined => {
|
||||
if (!quotedCids?.length || !cidToPostCid || !threadPostCid) return undefined;
|
||||
const sameThread = quotedCids.filter((cid) => cidToPostCid[cid] === threadPostCid);
|
||||
return sameThread.length > 0 ? sameThread : undefined;
|
||||
};
|
||||
|
||||
export const mergeQuotedCids = (publishCommentOptions: Record<string, any> | undefined, quotedCids: string[] | undefined) => {
|
||||
if (!publishCommentOptions || !quotedCids?.length) return publishCommentOptions;
|
||||
const currentQuotedCids = publishCommentOptions.quotedCids ?? [];
|
||||
|
||||
Reference in New Issue
Block a user