mirror of
https://github.com/shlinkio/shlink-web-client.git
synced 2024-12-23 17:40:23 +03:00
Merge pull request #698 from acelaya-forks/feature/multi-segment-slugs
Feature/multi segment slugs
This commit is contained in:
commit
8fd419dc72
6 changed files with 37 additions and 8 deletions
|
@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org).
|
||||||
|
|
||||||
## [Unreleased]
|
## [3.7.2] - 2022-08-07
|
||||||
### Added
|
### Added
|
||||||
* [#671](https://github.com/shlinkio/shlink-web-client/issues/671) Added proper color-scheme in root element based on selected theme.
|
* [#671](https://github.com/shlinkio/shlink-web-client/issues/671) Added proper color-scheme in root element based on selected theme.
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
||||||
### Fixed
|
### Fixed
|
||||||
* [#695](https://github.com/shlinkio/shlink-web-client/issues/695) Fixed some warnings in tests.
|
* [#695](https://github.com/shlinkio/shlink-web-client/issues/695) Fixed some warnings in tests.
|
||||||
* [#693](https://github.com/shlinkio/shlink-web-client/issues/693) Fixed tags, servers and domains search to make it case-insensitive.
|
* [#693](https://github.com/shlinkio/shlink-web-client/issues/693) Fixed tags, servers and domains search to make it case-insensitive.
|
||||||
|
* [#694](https://github.com/shlinkio/shlink-web-client/issues/694) Fixed editing and loading visits on short URLs with multi-segment slugs.
|
||||||
|
|
||||||
|
|
||||||
## [3.7.1] - 2022-05-25
|
## [3.7.1] - 2022-05-25
|
||||||
|
|
|
@ -16,7 +16,7 @@ import { ShortUrlFormProps } from './ShortUrlForm';
|
||||||
import { ShortUrlDetail } from './reducers/shortUrlDetail';
|
import { ShortUrlDetail } from './reducers/shortUrlDetail';
|
||||||
import { EditShortUrlData } from './data';
|
import { EditShortUrlData } from './data';
|
||||||
import { ShortUrlEdition } from './reducers/shortUrlEdition';
|
import { ShortUrlEdition } from './reducers/shortUrlEdition';
|
||||||
import { shortUrlDataFromShortUrl } from './helpers';
|
import { shortUrlDataFromShortUrl, urlDecodeShortCode } from './helpers';
|
||||||
|
|
||||||
interface EditShortUrlConnectProps {
|
interface EditShortUrlConnectProps {
|
||||||
settings: Settings;
|
settings: Settings;
|
||||||
|
@ -48,7 +48,7 @@ export const EditShortUrl = (ShortUrlForm: FC<ShortUrlFormProps>) => ({
|
||||||
const [savingSucceeded,, isSuccessful, isNotSuccessful] = useToggle();
|
const [savingSucceeded,, isSuccessful, isNotSuccessful] = useToggle();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
params.shortCode && getShortUrlDetail(params.shortCode, domain);
|
params.shortCode && getShortUrlDetail(urlDecodeShortCode(params.shortCode), domain);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
if (loading) {
|
if (loading) {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { FC } from 'react';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { isServerWithId, SelectedServer, ServerWithId } from '../../servers/data';
|
import { isServerWithId, SelectedServer, ServerWithId } from '../../servers/data';
|
||||||
import { ShortUrl } from '../data';
|
import { ShortUrl } from '../data';
|
||||||
|
import { urlEncodeShortCode } from './index';
|
||||||
|
|
||||||
export type LinkSuffix = 'visits' | 'edit';
|
export type LinkSuffix = 'visits' | 'edit';
|
||||||
|
|
||||||
|
@ -13,7 +14,7 @@ export interface ShortUrlDetailLinkProps {
|
||||||
|
|
||||||
const buildUrl = ({ id }: ServerWithId, { shortCode, domain }: ShortUrl, suffix: LinkSuffix) => {
|
const buildUrl = ({ id }: ServerWithId, { shortCode, domain }: ShortUrl, suffix: LinkSuffix) => {
|
||||||
const query = domain ? `?domain=${domain}` : '';
|
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>> = (
|
export const ShortUrlDetailLink: FC<ShortUrlDetailLinkProps & Record<string | number, any>> = (
|
||||||
|
|
|
@ -40,3 +40,9 @@ export const shortUrlDataFromShortUrl = (shortUrl?: ShortUrl, settings?: ShortUr
|
||||||
validateUrl,
|
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 { NormalizedVisit, VisitsParams } from './types';
|
||||||
import { CommonVisitsProps } from './types/CommonVisitsProps';
|
import { CommonVisitsProps } from './types/CommonVisitsProps';
|
||||||
import { toApiParams } from './types/helpers';
|
import { toApiParams } from './types/helpers';
|
||||||
|
import { urlDecodeShortCode } from '../short-urls/helpers';
|
||||||
|
|
||||||
export interface ShortUrlVisitsProps extends CommonVisitsProps {
|
export interface ShortUrlVisitsProps extends CommonVisitsProps {
|
||||||
getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void;
|
getShortUrlVisits: (shortCode: string, query?: ShlinkVisitsParams, doIntervalFallback?: boolean) => void;
|
||||||
|
@ -36,14 +37,14 @@ export const ShortUrlVisits = ({ exportVisits }: ReportExporter) => boundToMercu
|
||||||
const goBack = useGoBack();
|
const goBack = useGoBack();
|
||||||
const { domain } = parseQuery<{ domain?: string }>(search);
|
const { domain } = parseQuery<{ domain?: string }>(search);
|
||||||
const loadVisits = (params: VisitsParams, doIntervalFallback?: boolean) =>
|
const loadVisits = (params: VisitsParams, doIntervalFallback?: boolean) =>
|
||||||
getShortUrlVisits(shortCode, { ...toApiParams(params), domain }, doIntervalFallback);
|
getShortUrlVisits(urlDecodeShortCode(shortCode), { ...toApiParams(params), domain }, doIntervalFallback);
|
||||||
const exportCsv = (visits: NormalizedVisit[]) => exportVisits(
|
const exportCsv = (visits: NormalizedVisit[]) => exportVisits(
|
||||||
`short-url_${shortUrlDetail.shortUrl?.shortUrl.replace(/https?:\/\//g, '')}_visits.csv`,
|
`short-url_${shortUrlDetail.shortUrl?.shortUrl.replace(/https?:\/\//g, '')}_visits.csv`,
|
||||||
visits,
|
visits,
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getShortUrlDetail(shortCode, domain);
|
getShortUrlDetail(urlDecodeShortCode(shortCode), domain);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -59,4 +60,4 @@ export const ShortUrlVisits = ({ exportVisits }: ReportExporter) => boundToMercu
|
||||||
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
<ShortUrlVisitsHeader shortUrlDetail={shortUrlDetail} shortUrlVisits={shortUrlVisits} goBack={goBack} />
|
||||||
</VisitsStats>
|
</VisitsStats>
|
||||||
);
|
);
|
||||||
}, (_, params) => [Topics.shortUrlVisits(params.shortCode)]);
|
}, (_, params) => (params.shortCode ? [Topics.shortUrlVisits(urlDecodeShortCode(params.shortCode))] : []));
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { Mock } from 'ts-mockery';
|
import { Mock } from 'ts-mockery';
|
||||||
import { ShortUrl } from '../../../src/short-urls/data';
|
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('helpers', () => {
|
||||||
describe('shortUrlDataFromShortUrl', () => {
|
describe('shortUrlDataFromShortUrl', () => {
|
||||||
|
@ -25,4 +25,24 @@ describe('helpers', () => {
|
||||||
expect(shortUrlDataFromShortUrl(shortUrl, settings)).toEqual(expectedInitialState);
|
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