mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
148 lines
4.7 KiB
TypeScript
148 lines
4.7 KiB
TypeScript
import type { DirectoryCommunity } from '../../hooks/use-directories';
|
|
import { findDirectoryByAddress } from '../../hooks/use-directories';
|
|
import { getBoardPath, getCommunityAddress } from './route-utils';
|
|
import { QUOTE_NUMBER_REGEX } from './url-utils';
|
|
|
|
const CROSSBOARD_NUMBER_BOARD_PART = '(?:[a-zA-Z0-9]{1,10}|12D3KooW[a-zA-Z0-9]{44}|[a-zA-Z0-9\\-.]+)';
|
|
|
|
const CROSSBOARD_NUMBER_QUOTE_REGEX = new RegExp(`>>>\\/(${CROSSBOARD_NUMBER_BOARD_PART})\\/(\\d+)(?=[^\\d]|$)`, 'g');
|
|
export const CROSSBOARD_NUMBER_QUOTE_TOKEN_REGEX = new RegExp(`>>>\\/(${CROSSBOARD_NUMBER_BOARD_PART})\\/(\\d+)[.,:;!?]*`);
|
|
|
|
export type SameBoardExternalQuoteReference = {
|
|
kind: 'same-board';
|
|
number: number;
|
|
raw: string;
|
|
communityAddress?: string;
|
|
};
|
|
|
|
export type CrossBoardExternalQuoteReference = {
|
|
kind: 'cross-board';
|
|
boardIdentifier: string;
|
|
number: number;
|
|
raw: string;
|
|
};
|
|
|
|
export type ExternalQuoteReference = SameBoardExternalQuoteReference | CrossBoardExternalQuoteReference;
|
|
|
|
const getAddressForCanonicalReference = (reference: ExternalQuoteReference, directories: DirectoryCommunity[]): string => {
|
|
if (reference.kind === 'cross-board') {
|
|
return resolveLegacyCommunityAddress(reference.boardIdentifier, directories);
|
|
}
|
|
|
|
return resolveLegacyCommunityAddress(reference.communityAddress || '', directories);
|
|
};
|
|
|
|
const getExternalQuoteKey = (reference: ExternalQuoteReference) =>
|
|
reference.kind === 'cross-board'
|
|
? `${reference.kind}:${reference.boardIdentifier}:${reference.number}`
|
|
: `${reference.kind}:${reference.communityAddress}:${reference.number}`;
|
|
|
|
const resolveLegacyCommunityAddress = (boardIdentifier: string, communities: DirectoryCommunity[]) => {
|
|
const matchingDirectory = findDirectoryByAddress(communities, boardIdentifier);
|
|
if (matchingDirectory?.address) {
|
|
return matchingDirectory.address;
|
|
}
|
|
|
|
// Canonical resolver in route utils handles directory or address mapping.
|
|
const address = getCommunityAddress(boardIdentifier, communities);
|
|
if (address) {
|
|
return address;
|
|
}
|
|
|
|
return boardIdentifier;
|
|
};
|
|
|
|
export const getExternalQuoteBoardAddress = (reference: ExternalQuoteReference, directories: DirectoryCommunity[]) =>
|
|
getAddressForCanonicalReference(reference, directories);
|
|
|
|
export const getExternalQuoteBoardLabel = (reference: ExternalQuoteReference, directories: DirectoryCommunity[]) => {
|
|
const address = getAddressForCanonicalReference(reference, directories);
|
|
return getBoardPath(address, directories);
|
|
};
|
|
|
|
export const extractUnresolvedExternalQuoteReferences = ({
|
|
content,
|
|
scopedNumberToCid,
|
|
communityAddress,
|
|
}: {
|
|
content?: string;
|
|
scopedNumberToCid?: Record<number, string>;
|
|
communityAddress?: string;
|
|
}) => {
|
|
if (!content) {
|
|
return [] as ExternalQuoteReference[];
|
|
}
|
|
|
|
const references = new Map<string, ExternalQuoteReference>();
|
|
|
|
if (communityAddress) {
|
|
for (const match of content.matchAll(new RegExp(QUOTE_NUMBER_REGEX.source, 'g'))) {
|
|
const number = Number.parseInt(match[1], 10);
|
|
if (Number.isNaN(number) || scopedNumberToCid?.[number]) {
|
|
continue;
|
|
}
|
|
|
|
const reference: SameBoardExternalQuoteReference = {
|
|
kind: 'same-board',
|
|
number,
|
|
raw: `>>${number}`,
|
|
communityAddress,
|
|
};
|
|
references.set(getExternalQuoteKey(reference), reference);
|
|
}
|
|
}
|
|
|
|
for (const match of content.matchAll(new RegExp(CROSSBOARD_NUMBER_QUOTE_REGEX.source, 'g'))) {
|
|
const boardIdentifier = match[1];
|
|
const number = Number.parseInt(match[2], 10);
|
|
|
|
if (!boardIdentifier || Number.isNaN(number)) {
|
|
continue;
|
|
}
|
|
|
|
const reference: CrossBoardExternalQuoteReference = {
|
|
kind: 'cross-board',
|
|
boardIdentifier,
|
|
number,
|
|
raw: `>>>/${boardIdentifier}/${number}`,
|
|
};
|
|
references.set(getExternalQuoteKey(reference), reference);
|
|
}
|
|
|
|
return [...references.values()];
|
|
};
|
|
|
|
export type ExternalQuoteSearchStatus =
|
|
| {
|
|
phase: 'search-board';
|
|
boardLabel: string;
|
|
quoteDisplay: string;
|
|
}
|
|
| {
|
|
phase: 'search-thread';
|
|
boardLabel: string;
|
|
currentThread: number;
|
|
totalThreads: number;
|
|
quoteDisplay: string;
|
|
}
|
|
| {
|
|
phase: 'redirecting';
|
|
boardLabel: string;
|
|
quoteDisplay: string;
|
|
};
|
|
|
|
export const getExternalQuoteStatusMessage = (t: (key: string, options?: Record<string, unknown>) => string, status: ExternalQuoteSearchStatus) => {
|
|
switch (status.phase) {
|
|
case 'search-board':
|
|
return t('external_quote_search_board_feed', { board: status.boardLabel });
|
|
case 'search-thread':
|
|
return t('external_quote_search_thread', {
|
|
board: status.boardLabel,
|
|
current: status.currentThread,
|
|
total: status.totalThreads,
|
|
});
|
|
case 'redirecting':
|
|
return t('external_quote_redirecting_to_post', { board: status.boardLabel });
|
|
}
|
|
};
|