feat(posting): add scoped options and qst formatting (#1135)

* feat(posting): add scoped options and qst formatting

* fix(posting): address post options review feedback

* fix(posting): sync option content before publish

* fix(posting): handle final review nits
This commit is contained in:
Tommaso Casaburi
2026-05-21 22:12:41 +07:00
committed by GitHub
parent 73503eadd0
commit ff6b4cd201
12 changed files with 1119 additions and 124 deletions
@@ -137,4 +137,22 @@ describe('usePublishPost', () => {
expect(latestValue.publishPostError).toBe('blocked:unresolved');
expect(testState.publishCommentMock).not.toHaveBeenCalled();
});
it('publishes after synchronizing one-shot publish options', async () => {
await act(async () => {
latestValue.setPublishPostOptions({
content: 'Old body',
} as never);
});
await act(async () => {
latestValue.publishPost({
content: 'Fresh body',
} as never);
await Promise.resolve();
});
expect(testState.lastPublishOptions?.content).toBe('Fresh body');
expect(testState.publishCommentMock).toHaveBeenCalledTimes(1);
});
});
@@ -167,6 +167,25 @@ describe('usePublishReply', () => {
expect(testState.publishCommentMock).toHaveBeenCalledTimes(1);
});
it('publishes after synchronizing one-shot reply options', async () => {
await act(async () => {
latestValue.setPublishReplyOptions({
content: 'Old reply',
} as never);
});
await act(async () => {
await latestValue.publishReply({
content: 'Fresh reply',
} as never);
await Promise.resolve();
await new Promise((resolve) => setTimeout(resolve, 0));
});
expect(testState.lastPublishOptions?.content).toBe('Fresh reply');
expect(testState.publishCommentMock).toHaveBeenCalledTimes(1);
});
it('blocks publish when a same-board external quote cannot be resolved', async () => {
testState.resolveExternalQuoteTargetMock.mockResolvedValue(null);
+25 -1
View File
@@ -26,6 +26,8 @@ const usePublishPost = ({ communityAddress }: UsePublishPostOptions) => {
const addChallenge = useChallengesStore((state) => state.addChallenge);
const abandonPublishRef = useRef<(() => Promise<void>) | undefined>();
const [publishPostError, setPublishPostError] = useState<string | null>(null);
const [pendingPublishRequestId, setPendingPublishRequestId] = useState(0);
const startedPublishRequestIdRef = useRef(0);
const { blockedReason } = usePublishAuthorDomainGuard();
const abandonCurrentPublish = useCallback(async () => {
await abandonPublishRef.current?.();
@@ -90,7 +92,7 @@ const usePublishPost = ({ communityAddress }: UsePublishPostOptions) => {
setPublishPostError(null);
}, [author?.displayName, blockedReason, communityAddress, content, link, spoiler, title]);
const publishPost = useCallback(() => {
const startPublishPost = useCallback(() => {
if (blockedReason) {
setPublishPostError(getPublishAuthorDomainErrorMessage(blockedReason));
return;
@@ -100,6 +102,28 @@ const usePublishPost = ({ communityAddress }: UsePublishPostOptions) => {
return publishComment();
}, [blockedReason, publishComment]);
useEffect(() => {
if (pendingPublishRequestId === 0 || pendingPublishRequestId === startedPublishRequestIdRef.current) {
return;
}
startedPublishRequestIdRef.current = pendingPublishRequestId;
startPublishPost();
}, [pendingPublishRequestId, startPublishPost]);
const publishPost = useCallback(
(options?: Partial<Comment>) => {
if (options) {
setPublishPostOptions(options);
setPendingPublishRequestId((requestId) => requestId + 1);
return;
}
return startPublishPost();
},
[setPublishPostOptions, startPublishPost],
);
return {
setPublishPostOptions,
resetPublishPostOptions,
+75 -55
View File
@@ -41,6 +41,8 @@ const usePublishReply = ({ cid, communityAddress, postCid }: UsePublishReplyOpti
const startedPublishRequestIdRef = useRef(0);
const [resolvedExternalQuotedCids, setResolvedExternalQuotedCids] = useState<string[] | undefined>();
const [pendingPublishRequestId, setPendingPublishRequestId] = useState(0);
const [pendingSyncedPublishRequestId, setPendingSyncedPublishRequestId] = useState(0);
const startedSyncedPublishRequestIdRef = useRef(0);
const [isResolvingExternalQuotes, setIsResolvingExternalQuotes] = useState(false);
const [publishReplyError, setPublishReplyError] = useState<string | null>(null);
const [publishReplyStateMessage, setPublishReplyStateMessage] = useState<string | null>(null);
@@ -151,64 +153,82 @@ const usePublishReply = ({ cid, communityAddress, postCid }: UsePublishReplyOpti
publishComment();
}, [pendingPublishRequestId, publishComment]);
const publishReply = useCallback(async () => {
setPublishReplyError(null);
if (blockedReason) {
setPublishReplyStateMessage(null);
setPublishReplyError(getPublishAuthorDomainErrorMessage(blockedReason));
return;
}
if (publishResolvableQuoteReferences.length === 0) {
setResolvedExternalQuotedCids(undefined);
setPublishReplyStateMessage(null);
setPendingPublishRequestId((requestId) => requestId + 1);
return;
}
if (!account?.id) {
setPublishReplyError(t('external_quote_resolution_unavailable'));
return;
}
setIsResolvingExternalQuotes(true);
try {
const resolvedCids = new Set<string>();
for (const reference of publishResolvableQuoteReferences) {
const resolvedTarget = await resolveExternalQuoteTarget({
account,
directories,
onStatus: (status) => {
setPublishReplyStateMessage(getExternalQuoteStatusMessage(t, status));
},
reference,
});
if (!resolvedTarget?.cid) {
setPublishReplyError(
t('external_quote_publish_missing', {
interpolation: { escapeValue: false },
quote: reference.raw,
}),
);
return;
}
resolvedCids.add(resolvedTarget.cid);
const publishReply = useCallback(
async (options?: Partial<Comment>) => {
if (options) {
setPublishReplyOptions(options);
setPendingSyncedPublishRequestId((requestId) => requestId + 1);
return;
}
setResolvedExternalQuotedCids(resolvedCids.size > 0 ? [...resolvedCids] : undefined);
setPublishReplyStateMessage(null);
setPendingPublishRequestId((requestId) => requestId + 1);
} catch {
setPublishReplyError(t('external_quote_resolution_unavailable'));
} finally {
setIsResolvingExternalQuotes(false);
setPublishReplyError(null);
if (blockedReason) {
setPublishReplyStateMessage(null);
setPublishReplyError(getPublishAuthorDomainErrorMessage(blockedReason));
return;
}
if (publishResolvableQuoteReferences.length === 0) {
setResolvedExternalQuotedCids(undefined);
setPublishReplyStateMessage(null);
setPendingPublishRequestId((requestId) => requestId + 1);
return;
}
if (!account?.id) {
setPublishReplyError(t('external_quote_resolution_unavailable'));
return;
}
setIsResolvingExternalQuotes(true);
try {
const resolvedCids = new Set<string>();
for (const reference of publishResolvableQuoteReferences) {
const resolvedTarget = await resolveExternalQuoteTarget({
account,
directories,
onStatus: (status) => {
setPublishReplyStateMessage(getExternalQuoteStatusMessage(t, status));
},
reference,
});
if (!resolvedTarget?.cid) {
setPublishReplyError(
t('external_quote_publish_missing', {
interpolation: { escapeValue: false },
quote: reference.raw,
}),
);
return;
}
resolvedCids.add(resolvedTarget.cid);
}
setResolvedExternalQuotedCids(resolvedCids.size > 0 ? [...resolvedCids] : undefined);
setPublishReplyStateMessage(null);
setPendingPublishRequestId((requestId) => requestId + 1);
} catch {
setPublishReplyError(t('external_quote_resolution_unavailable'));
} finally {
setIsResolvingExternalQuotes(false);
}
},
[account, blockedReason, directories, publishResolvableQuoteReferences, setPublishReplyOptions, t],
);
useEffect(() => {
if (pendingSyncedPublishRequestId === 0 || pendingSyncedPublishRequestId === startedSyncedPublishRequestIdRef.current) {
return;
}
}, [account, blockedReason, directories, publishResolvableQuoteReferences, t]);
startedSyncedPublishRequestIdRef.current = pendingSyncedPublishRequestId;
publishReply();
}, [pendingSyncedPublishRequestId, publishReply]);
return {
isResolvingExternalQuotes,