mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
refactor(routing): migrate to directory-based board routing
This commit is contained in:
@@ -42,17 +42,30 @@ export const is5chanLink = (url: string): boolean => {
|
||||
routePath = parsedUrl.hash.substring(1); // Remove the # to get the path
|
||||
}
|
||||
|
||||
// For pleb.bz, only support the exact sharelink format
|
||||
// For pleb.bz, only support the exact sharelink format (legacy /p/... format)
|
||||
if (hostname === 'pleb.bz') {
|
||||
// Must match exactly: /p/{subplebbitAddress}/c/{cid}
|
||||
// Allow redirect parameter since these are still valid internal links
|
||||
return /^\/p\/[^/]+\/c\/[^/]+$/.test(routePath);
|
||||
}
|
||||
|
||||
// For other 5chan hostnames, support:
|
||||
// For other 5chan hostnames, support both old and new formats:
|
||||
// Old format (for backward compatibility):
|
||||
// - /p/{subplebbitAddress}
|
||||
// - /p/{subplebbitAddress}/c/{commentCid}
|
||||
return /^\/p\/[^/]+(\/c\/[^/]+)?$/.test(routePath);
|
||||
// New format:
|
||||
// - /{boardIdentifier} (directory code or address)
|
||||
// - /{boardIdentifier}/thread/{commentCid}
|
||||
// - /{boardIdentifier}/catalog
|
||||
// - /{boardIdentifier}/description
|
||||
// - /{boardIdentifier}/rules
|
||||
// - /all, /subscriptions, /mod, /pending/{index}
|
||||
return (
|
||||
/^\/p\/[^/]+(\/c\/[^/]+)?$/.test(routePath) ||
|
||||
/^\/[^/]+(\/thread\/[^/]+|\/catalog|\/description|\/rules)?$/.test(routePath) ||
|
||||
/^\/(all|subscriptions|mod)(\/catalog|\/thread\/[^/]+)?(\/[^/]+)?$/.test(routePath) ||
|
||||
/^\/pending\/[^/]+$/.test(routePath)
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -71,7 +84,8 @@ export const transform5chanLinkToInternal = (url: string): string | null => {
|
||||
if (parsedUrl.hash && parsedUrl.hash.startsWith('#/')) {
|
||||
// Extract the route from the hash, preserving any query params within the hash
|
||||
const hashPath = parsedUrl.hash.substring(1); // Remove the #
|
||||
return hashPath;
|
||||
// Transform old /p/... format to new format if needed
|
||||
return transformOldPathToNew(hashPath);
|
||||
}
|
||||
|
||||
// For regular pathname-based routes, remove redirect parameter from query string
|
||||
@@ -81,12 +95,37 @@ export const transform5chanLinkToInternal = (url: string): string | null => {
|
||||
const cleanSearch = searchParams.toString();
|
||||
const searchString = cleanSearch ? `?${cleanSearch}` : '';
|
||||
|
||||
return parsedUrl.pathname + searchString + parsedUrl.hash;
|
||||
// Transform old /p/... format to new format if needed
|
||||
const transformedPath = transformOldPathToNew(parsedUrl.pathname);
|
||||
return transformedPath + searchString + parsedUrl.hash;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
// Transform old URL format (/p/{address}/c/{cid}) to new format (/{boardIdentifier}/thread/{cid})
|
||||
// Note: This function doesn't resolve directory codes - that's handled by the routing system
|
||||
const transformOldPathToNew = (path: string): string => {
|
||||
// Transform /p/{address}/c/{cid} to /{address}/thread/{cid}
|
||||
const oldPostPattern = /^\/p\/([^/]+)\/c\/([^/]+)$/;
|
||||
const postMatch = path.match(oldPostPattern);
|
||||
if (postMatch) {
|
||||
const [, address, cid] = postMatch;
|
||||
return `/${address}/thread/${cid}`;
|
||||
}
|
||||
|
||||
// Transform /p/{address} to /{address}
|
||||
const oldBoardPattern = /^\/p\/([^/]+)$/;
|
||||
const boardMatch = path.match(oldBoardPattern);
|
||||
if (boardMatch) {
|
||||
const [, address] = boardMatch;
|
||||
return `/${address}`;
|
||||
}
|
||||
|
||||
// Return path as-is if it doesn't match old patterns
|
||||
return path;
|
||||
};
|
||||
|
||||
// Check if a string is a valid IPNS public key (52 chars starting with 12D3KooW)
|
||||
const isValidIPNSKey = (str: string): boolean => {
|
||||
return str.length === 52 && str.startsWith('12D3KooW');
|
||||
|
||||
+54
-32
@@ -1,41 +1,60 @@
|
||||
export type ParamsType = {
|
||||
accountCommentIndex?: string;
|
||||
boardIdentifier?: string;
|
||||
commentCid?: string;
|
||||
subplebbitAddress?: string;
|
||||
subplebbitAddress?: string; // deprecated, kept for backward compatibility
|
||||
timeFilterName?: string;
|
||||
};
|
||||
|
||||
export const isAllView = (pathname: string): boolean => {
|
||||
return pathname.startsWith('/p/all');
|
||||
return pathname.startsWith('/all');
|
||||
};
|
||||
|
||||
export const isBoardView = (pathname: string, params: ParamsType): boolean => {
|
||||
// some subs might use emojis in their address, so we need to decode the pathname
|
||||
const decodedPathname = decodeURIComponent(pathname);
|
||||
return params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}`) : false;
|
||||
// Check if it's a board view (not all, subscriptions, mod, pending, or special routes)
|
||||
if (
|
||||
pathname.startsWith('/all') ||
|
||||
pathname.startsWith('/subscriptions') ||
|
||||
pathname.startsWith('/mod') ||
|
||||
pathname.startsWith('/pending') ||
|
||||
pathname === '/' ||
|
||||
pathname.startsWith('/faq') ||
|
||||
pathname.startsWith('/not-found')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const identifier = params.boardIdentifier || params.subplebbitAddress;
|
||||
return identifier ? decodedPathname.startsWith(`/${identifier}`) : false;
|
||||
};
|
||||
|
||||
export const isCatalogView = (pathname: string, params: ParamsType): boolean => {
|
||||
const { subplebbitAddress, timeFilterName } = params;
|
||||
const { boardIdentifier, subplebbitAddress, timeFilterName } = params;
|
||||
const identifier = boardIdentifier || subplebbitAddress;
|
||||
const decodedPathname = decodeURIComponent(pathname);
|
||||
|
||||
return (
|
||||
decodedPathname === `/p/${subplebbitAddress}/catalog` ||
|
||||
decodedPathname === `/p/${subplebbitAddress}/catalog/settings` ||
|
||||
decodedPathname === `/p/all/catalog` ||
|
||||
decodedPathname === `/p/all/catalog/settings` ||
|
||||
decodedPathname === `/p/all/catalog/${timeFilterName}` ||
|
||||
decodedPathname === `/p/all/catalog/${timeFilterName}/settings` ||
|
||||
decodedPathname === `/p/subscriptions/catalog` ||
|
||||
decodedPathname === `/p/subscriptions/catalog/settings` ||
|
||||
decodedPathname === `/p/subscriptions/catalog/${timeFilterName}` ||
|
||||
decodedPathname === `/p/subscriptions/catalog/${timeFilterName}/settings`
|
||||
(identifier && (decodedPathname === `/${identifier}/catalog` || decodedPathname === `/${identifier}/catalog/settings`)) ||
|
||||
decodedPathname === `/all/catalog` ||
|
||||
decodedPathname === `/all/catalog/settings` ||
|
||||
decodedPathname === `/all/catalog/${timeFilterName}` ||
|
||||
decodedPathname === `/all/catalog/${timeFilterName}/settings` ||
|
||||
decodedPathname === `/subscriptions/catalog` ||
|
||||
decodedPathname === `/subscriptions/catalog/settings` ||
|
||||
decodedPathname === `/subscriptions/catalog/${timeFilterName}` ||
|
||||
decodedPathname === `/subscriptions/catalog/${timeFilterName}/settings` ||
|
||||
decodedPathname === `/mod/catalog` ||
|
||||
decodedPathname === `/mod/catalog/settings` ||
|
||||
decodedPathname === `/mod/catalog/${timeFilterName}` ||
|
||||
decodedPathname === `/mod/catalog/${timeFilterName}/settings`
|
||||
);
|
||||
};
|
||||
|
||||
export const isDescriptionView = (pathname: string, params: ParamsType): boolean => {
|
||||
const decodedPathname = decodeURIComponent(pathname);
|
||||
return pathname === '/p/all/description' ? true : params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/description`) : false;
|
||||
const identifier = params.boardIdentifier || params.subplebbitAddress;
|
||||
return pathname === '/all/description' ? true : identifier ? decodedPathname.startsWith(`/${identifier}/description`) : false;
|
||||
};
|
||||
|
||||
export const isHomeView = (pathname: string): boolean => {
|
||||
@@ -43,11 +62,11 @@ export const isHomeView = (pathname: string): boolean => {
|
||||
};
|
||||
|
||||
export const isModView = (pathname: string): boolean => {
|
||||
return pathname === `/p/mod` || pathname.startsWith(`/p/mod/`);
|
||||
return pathname === `/mod` || pathname.startsWith(`/mod/`);
|
||||
};
|
||||
|
||||
export const isPendingPostView = (pathname: string, params: ParamsType): boolean => {
|
||||
return pathname === `/profile/${params.accountCommentIndex}` || pathname === `/profile/${params.accountCommentIndex}/settings`;
|
||||
return pathname === `/pending/${params.accountCommentIndex}` || pathname === `/pending/${params.accountCommentIndex}/settings`;
|
||||
};
|
||||
|
||||
export const isPostPageView = (pathname: string, params: ParamsType): boolean => {
|
||||
@@ -55,36 +74,39 @@ export const isPostPageView = (pathname: string, params: ParamsType): boolean =>
|
||||
return true;
|
||||
}
|
||||
const decodedPathname = decodeURIComponent(pathname);
|
||||
return params.subplebbitAddress && params.commentCid ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/c/${params.commentCid}`) : false;
|
||||
const identifier = params.boardIdentifier || params.subplebbitAddress;
|
||||
return identifier && params.commentCid ? decodedPathname.startsWith(`/${identifier}/thread/${params.commentCid}`) : false;
|
||||
};
|
||||
|
||||
export const isRulesView = (pathname: string, params: ParamsType): boolean => {
|
||||
const decodedPathname = decodeURIComponent(pathname);
|
||||
return params.subplebbitAddress ? decodedPathname.startsWith(`/p/${params.subplebbitAddress}/rules`) : false;
|
||||
const identifier = params.boardIdentifier || params.subplebbitAddress;
|
||||
return identifier ? decodedPathname.startsWith(`/${identifier}/rules`) : false;
|
||||
};
|
||||
|
||||
export const isSettingsView = (pathname: string, params: ParamsType): boolean => {
|
||||
const { accountCommentIndex, commentCid, subplebbitAddress } = params;
|
||||
const { accountCommentIndex, boardIdentifier, commentCid, subplebbitAddress } = params;
|
||||
const identifier = boardIdentifier || subplebbitAddress;
|
||||
const decodedPathname = decodeURIComponent(pathname);
|
||||
return (
|
||||
decodedPathname === `/p/${subplebbitAddress}/c/${commentCid}/settings` ||
|
||||
decodedPathname === `/p/${subplebbitAddress}/description/settings` ||
|
||||
decodedPathname === `/p/${subplebbitAddress}/rules/settings` ||
|
||||
decodedPathname === `/profile/${accountCommentIndex}/settings`
|
||||
(identifier && commentCid && decodedPathname === `/${identifier}/thread/${commentCid}/settings`) ||
|
||||
(identifier && decodedPathname === `/${identifier}/description/settings`) ||
|
||||
(identifier && decodedPathname === `/${identifier}/rules/settings`) ||
|
||||
decodedPathname === `/pending/${accountCommentIndex}/settings`
|
||||
);
|
||||
};
|
||||
|
||||
export const isSubscriptionsView = (pathname: string, params: ParamsType): boolean => {
|
||||
const { timeFilterName } = params;
|
||||
return (
|
||||
pathname === '/p/subscriptions' ||
|
||||
pathname === '/p/subscriptions/settings' ||
|
||||
pathname === `/p/subscriptions/${timeFilterName}` ||
|
||||
pathname === `/p/subscriptions/${timeFilterName}/settings` ||
|
||||
pathname === '/p/subscriptions/catalog' ||
|
||||
pathname === '/p/subscriptions/catalog/settings' ||
|
||||
pathname === `/p/subscriptions/catalog/${timeFilterName}` ||
|
||||
pathname === `/p/subscriptions/catalog/${timeFilterName}/settings`
|
||||
pathname === '/subscriptions' ||
|
||||
pathname === '/subscriptions/settings' ||
|
||||
pathname === `/subscriptions/${timeFilterName}` ||
|
||||
pathname === `/subscriptions/${timeFilterName}/settings` ||
|
||||
pathname === '/subscriptions/catalog' ||
|
||||
pathname === '/subscriptions/catalog/settings' ||
|
||||
pathname === `/subscriptions/catalog/${timeFilterName}` ||
|
||||
pathname === `/subscriptions/catalog/${timeFilterName}/settings`
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user