mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 09:30:31 +03:00
Added support for short URLs with multi-segment slugs
This commit is contained in:
parent
b1749ee2ef
commit
0c17818a24
5 changed files with 35 additions and 7 deletions
|
@ -16,7 +16,7 @@ import { ShortUrlFormProps } from './ShortUrlForm';
|
|||
import { ShortUrlDetail } from './reducers/shortUrlDetail';
|
||||
import { EditShortUrlData } from './data';
|
||||
import { ShortUrlEdition } from './reducers/shortUrlEdition';
|
||||
import { shortUrlDataFromShortUrl } from './helpers';
|
||||
import { shortUrlDataFromShortUrl, urlDecodeShortCode } from './helpers';
|
||||
|
||||
interface EditShortUrlConnectProps {
|
||||
settings: Settings;
|
||||
|
@ -48,7 +48,7 @@ export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>) => ({
|
|||
const [savingSucceeded,, isSuccessful, isNotSuccessful] = useToggle();
|
||||
|
||||
useEffect(() => {
|
||||
params.shortCode && getShortUrlDetail(params.shortCode, domain);
|
||||
params.shortCode && getShortUrlDetail(urlDecodeShortCode(params.shortCode), domain);
|
||||
}, []);
|
||||
|
||||
if (loading) {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { FC } from 'react';
|
|||
import { Link } from 'react-router-dom';
|
||||
import { isServerWithId, SelectedServer, ServerWithId } from '../../servers/data';
|
||||
import { ShortUrl } from '../data';
|
||||
import { urlEncodeShortCode } from './index';
|
||||
|
||||
export type LinkSuffix = 'visits' | 'edit';
|
||||
|
||||
|
@ -13,7 +14,7 @@ export interface ShortUrlDetailLinkProps {
|
|||
|
||||
const buildUrl = ({ id }: ServerWithId, { shortCode, domain }: ShortUrl, suffix: LinkSuffix) => {
|
||||
const query = domain ? `?domain=${domain}` : '';
|
||||
return `/server/${id}/short-code/${shortCode}/${suffix}${query}`;
|
||||
return `/server/${id}/short-code/${urlEncodeShortCode(shortCode)}/${suffix}${query}`;
|
||||
};
|
||||
|
||||
export const ShortUrlDetailLink: FC<ShortUrlDetailLinkProps & Record<string | number, any>> = (
|
||||
|
|
|
@ -40,3 +40,9 @@ export const shortUrlDataFromShortUrl = (shortUrl?: ShortUrl, settings?: ShortUr
|
|||
validateUrl,
|
||||
};
|
||||
};
|
||||
|
||||
const MULTI_SEGMENT_SEPARATOR = '__';
|
||||
|
||||
export const urlEncodeShortCode = (shortCode: string): string => shortCode.replaceAll('/', MULTI_SEGMENT_SEPARATOR);
|
||||
|
||||
export const urlDecodeShortCode = (shortCode: string): string => shortCode.replaceAll(MULTI_SEGMENT_SEPARATOR, '/');
|
||||
|
|
|
@ -13,6 +13,7 @@ import { VisitsStats } from './VisitsStats';
|
|||
import { NormalizedVisit, VisitsParams } from './types';
|
||||
import { CommonVisitsProps } from './types/CommonVisitsProps';
|
||||
import { toApiParams } from './types/helpers';
|
||||
import { urlDecodeShortCode } from '../short-urls/helpers';
|
||||
|
||||
export interface ShortUrlVisitsProps extends CommonVisitsProps {
|
||||
getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void;
|
||||
|
@ -36,14 +37,14 @@ export const ShortUrlVisits = ({ exportVisits }: ReportExporter) => boundToMercu
|
|||
const goBack = useGoBack();
|
||||
const { domain } = parseQuery<{ domain?: string }>(search);
|
||||
const loadVisits = (params: VisitsParams, doIntervalFallback?: boolean) =>
|
||||
getShortUrlVisits(shortCode, { ...toApiParams(params), domain }, doIntervalFallback);
|
||||
getShortUrlVisits(urlDecodeShortCode(shortCode), { ...toApiParams(params), domain }, doIntervalFallback);
|
||||
const exportCsv = (visits: NormalizedVisit[]) => exportVisits(
|
||||
`short-url_${shortUrlDetail.shortUrl?.shortUrl.replace(/https?:\/\//g, '')}_visits.csv`,
|
||||
visits,
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
getShortUrlDetail(shortCode, domain);
|
||||
getShortUrlDetail(urlDecodeShortCode(shortCode), domain);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
|
@ -59,4 +60,4 @@ export const ShortUrlVisits = ({ exportVisits }: ReportExporter) => boundToMercu
|
|||
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
||||
</VisitsStats>
|
||||
);
|
||||
}, (_, params) => [Topics.shortUrlVisits(params.shortCode)]);
|
||||
}, (_, params) => (params.shortCode ? [Topics.shortUrlVisits(urlDecodeShortCode(params.shortCode))] : []));
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Mock } from 'ts-mockery';
|
||||
import { ShortUrl } from '../../../src/short-urls/data';
|
||||
import { shortUrlDataFromShortUrl } from '../../../src/short-urls/helpers';
|
||||
import { shortUrlDataFromShortUrl, urlDecodeShortCode, urlEncodeShortCode } from '../../../src/short-urls/helpers';
|
||||
|
||||
describe('helpers', () => {
|
||||
describe('shortUrlDataFromShortUrl', () => {
|
||||
|
@ -25,4 +25,24 @@ describe('helpers', () => {
|
|||
expect(shortUrlDataFromShortUrl(shortUrl, settings)).toEqual(expectedInitialState);
|
||||
});
|
||||
});
|
||||
|
||||
describe('urlEncodeShortCode', () => {
|
||||
it.each([
|
||||
['foo', 'foo'],
|
||||
['foo/bar', 'foo__bar'],
|
||||
['foo/bar/baz', 'foo__bar__baz'],
|
||||
])('parses shortCode as expected', (shortCode, result) => {
|
||||
expect(urlEncodeShortCode(shortCode)).toEqual(result);
|
||||
});
|
||||
});
|
||||
|
||||
describe('urlDecodeShortCode', () => {
|
||||
it.each([
|
||||
['foo', 'foo'],
|
||||
['foo__bar', 'foo/bar'],
|
||||
['foo__bar__baz', 'foo/bar/baz'],
|
||||
])('parses shortCode as expected', (shortCode, result) => {
|
||||
expect(urlDecodeShortCode(shortCode)).toEqual(result);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue