feat: use s.5chan.app share links and remove report menu items

Copy share links as path-based s.5chan.app URLs so the share server can render social previews, rename the menu label, remove the stub report action, and update the FAQ.
This commit is contained in:
Tommaso Casaburi
2026-06-06 19:06:04 +07:00
parent aa5a985201
commit 404a464310
42 changed files with 97 additions and 166 deletions
+5 -3
View File
@@ -72,12 +72,12 @@ describe('url-utils', () => {
expect(getExpiringMediaLinkHostname('not-a-url')).toBeNull();
});
it('copies share links for threads and catalog pages using the production fallback base url', async () => {
it('copies path-based share links for threads and catalog pages using the share subdomain', async () => {
await copyShareLinkToClipboard('music.eth', 'thread', 'cid-123');
expect(testState.copyToClipboardMock).toHaveBeenCalledWith('https://5chan.app/#/music.eth/thread/cid-123');
expect(testState.copyToClipboardMock).toHaveBeenCalledWith('https://s.5chan.app/music.eth/thread/cid-123');
await copyShareLinkToClipboard('music.eth', 'catalog');
expect(testState.copyToClipboardMock).toHaveBeenCalledWith('https://5chan.app/#/music.eth/catalog');
expect(testState.copyToClipboardMock).toHaveBeenCalledWith('https://s.5chan.app/music.eth/catalog');
const copyThreadWithoutCid = copyShareLinkToClipboard as (boardIdentifier: string, linkType: 'thread', cid?: string) => Promise<void>;
await expect(copyThreadWithoutCid('music.eth', 'thread')).rejects.toThrow('copyShareLinkToClipboard: thread links require a cid');
@@ -89,6 +89,8 @@ describe('url-utils', () => {
expect(is5chanLink('https://5chan.app/#/music.eth/catalog')).toBe(true);
expect(is5chanLink('https://5chan.app/p/music.eth/c/cid-123')).toBe(true);
expect(is5chanLink('https://5chan.app/all/catalog')).toBe(true);
expect(is5chanLink('https://s.5chan.app/music.eth/thread/cid-123')).toBe(true);
expect(is5chanLink('https://s.5chan.app/music.eth/catalog')).toBe(true);
expect(is5chanLink('https://example.com/music.eth')).toBe(false);
});
+10 -4
View File
@@ -128,10 +128,14 @@ export const getPublishURLFilename = (url: string): string | null => {
}
};
const CHAN_5_HOSTNAMES = ['5chan.app', '5chan.eth.limo', '5chan.eth.link', '5chan.eth.sucks', '5chan.netlify.app'];
// Dedicated subdomain that serves shared posts. Its server renders social-media link
// previews (OpenGraph/Twitter cards) for the shared post before handing off to the app.
const SHARE_HOSTNAME = 's.5chan.app';
const CHAN_5_HOSTNAMES = ['5chan.app', SHARE_HOSTNAME, '5chan.eth.limo', '5chan.eth.link', '5chan.eth.sucks', '5chan.netlify.app'];
function getShareBaseUrl(): string {
return `https://${CHAN_5_HOSTNAMES[0]}`;
return `https://${SHARE_HOSTNAME}`;
}
export type ShareLinkType = 'thread' | 'catalog';
@@ -140,16 +144,18 @@ export type ShareLinkType = 'thread' | 'catalog';
export function copyShareLinkToClipboard(boardIdentifier: string, linkType: 'thread', cid: string): Promise<void>;
export function copyShareLinkToClipboard(boardIdentifier: string, linkType: Exclude<ShareLinkType, 'thread'>): Promise<void>;
export async function copyShareLinkToClipboard(boardIdentifier: string, linkType: ShareLinkType, cid?: string): Promise<void> {
// Share links are path-based (no HashRouter `#/`) so the share server can read the post
// from the request path and render its preview; URL fragments never reach the server.
if (linkType === 'thread') {
if (!cid) {
throw new Error('copyShareLinkToClipboard: thread links require a cid');
}
const shareLink = `${getShareBaseUrl()}/#/${boardIdentifier}/thread/${cid}`;
const shareLink = `${getShareBaseUrl()}/${boardIdentifier}/thread/${cid}`;
await copyToClipboard(shareLink);
return;
}
const shareLink = `${getShareBaseUrl()}/#/${boardIdentifier}/${linkType}`;
const shareLink = `${getShareBaseUrl()}/${boardIdentifier}/${linkType}`;
await copyToClipboard(shareLink);
}