diff --git a/CHANGELOG.md b/CHANGELOG.md index 958c0192..0a733642 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). -## [Unreleased] +## [3.3.0] - 2021-09-25 ### Added * [#465](https://github.com/shlinkio/shlink-web-client/issues/465) Added new page to manage domains and their redirects, when consuming Shlink 2.8 or higher. * [#460](https://github.com/shlinkio/shlink-web-client/issues/460) Added dynamic title on hover for tags with a very long title. @@ -31,7 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), * *Nothing* ### Removed -* *Nothing* +* [#491](https://github.com/shlinkio/shlink-web-client/issues/491) Dropped support for Shlink older than v2.4.0. ### Fixed * *Nothing* diff --git a/src/common/MenuLayout.tsx b/src/common/MenuLayout.tsx index 871a0f97..6672583e 100644 --- a/src/common/MenuLayout.tsx +++ b/src/common/MenuLayout.tsx @@ -5,7 +5,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import classNames from 'classnames'; import { withSelectedServer } from '../servers/helpers/withSelectedServer'; import { useSwipeable, useToggle } from '../utils/helpers/hooks'; -import { supportsDomainRedirects, supportsOrphanVisits, supportsTagVisits } from '../utils/helpers/features'; +import { supportsDomainRedirects, supportsOrphanVisits } from '../utils/helpers/features'; import { isReachableServer } from '../servers/data'; import NotFound from './NotFound'; import { AsideMenuProps } from './AsideMenu'; @@ -32,7 +32,6 @@ const MenuLayout = ( return ; } - const addTagsVisitsRoute = supportsTagVisits(selectedServer); const addOrphanVisitsRoute = supportsOrphanVisits(selectedServer); const addManageDomainsRoute = supportsDomainRedirects(selectedServer); const burgerClasses = classNames('menu-layout__burger-icon', { 'menu-layout__burger-icon--active': sidebarVisible }); @@ -54,7 +53,7 @@ const MenuLayout = ( - {addTagsVisitsRoute && } + {addOrphanVisitsRoute && } {addManageDomainsRoute && } diff --git a/src/servers/Overview.tsx b/src/servers/Overview.tsx index 84614b2e..99311133 100644 --- a/src/servers/Overview.tsx +++ b/src/servers/Overview.tsx @@ -55,14 +55,7 @@ export const Overview = (
Visits - - - {loadingVisits ? 'Loading...' : prettify(visitsCount)} - - - Shlink 2.2 is needed - - + {loadingVisits ? 'Loading...' : prettify(visitsCount)}
diff --git a/src/short-urls/ShortUrlForm.tsx b/src/short-urls/ShortUrlForm.tsx index 96fc6eea..b09a70cb 100644 --- a/src/short-urls/ShortUrlForm.tsx +++ b/src/short-urls/ShortUrlForm.tsx @@ -5,13 +5,7 @@ import { isEmpty, pipe, replace, trim } from 'ramda'; import classNames from 'classnames'; import { parseISO } from 'date-fns'; import DateInput, { DateInputProps } from '../utils/DateInput'; -import { - supportsCrawlableVisits, - supportsListingDomains, - supportsSettingShortCodeLength, - supportsShortUrlTitle, - supportsValidateUrl, -} from '../utils/helpers/features'; +import { supportsCrawlableVisits, supportsShortUrlTitle } from '../utils/helpers/features'; import { SimpleCard } from '../utils/SimpleCard'; import { handleEventPreventingDefault, hasValue } from '../utils/utils'; import Checkbox from '../utils/Checkbox'; @@ -102,17 +96,13 @@ export const ShortUrlForm = ( ); - const showDomainSelector = supportsListingDomains(selectedServer); - const disableShortCodeLength = !supportsSettingShortCodeLength(selectedServer); const supportsTitle = supportsShortUrlTitle(selectedServer); const showCustomizeCard = supportsTitle || !isEdit; const limitAccessCardClasses = classNames('mb-3', { 'col-sm-6': showCustomizeCard, 'col-sm-12': !showCustomizeCard, }); - const showValidateUrl = supportsValidateUrl(selectedServer); const showCrawlableControl = supportsCrawlableVisits(selectedServer); - const showExtraValidationsCard = showValidateUrl || showCrawlableControl || !isEdit; return (
@@ -139,22 +129,16 @@ export const ShortUrlForm = (
{renderOptionalInput('shortCodeLength', 'Short code length', 'number', { min: 4, - disabled: disableShortCodeLength || hasValue(shortUrlData.customSlug), - ...disableShortCodeLength && { - title: 'Shlink 2.1.0 or higher is required to be able to provide the short code length', - }, + disabled: hasValue(shortUrlData.customSlug), })}
- {!showDomainSelector && renderOptionalInput('domain', 'Domain', 'text')} - {showDomainSelector && ( - - setShortUrlData({ ...shortUrlData, domain })} - /> - - )} + + setShortUrlData({ ...shortUrlData, domain })} + /> + )} @@ -170,41 +154,37 @@ export const ShortUrlForm = (
- {showExtraValidationsCard && ( - - {showValidateUrl && ( - setShortUrlData({ ...shortUrlData, validateUrl })} + + setShortUrlData({ ...shortUrlData, validateUrl })} + > + Validate URL + + {showCrawlableControl && ( + setShortUrlData({ ...shortUrlData, crawlable })} + > + Make it crawlable + + )} + {!isEdit && ( +

+ setShortUrlData({ ...shortUrlData, findIfExists })} > - Validate URL - - )} - {showCrawlableControl && ( - setShortUrlData({ ...shortUrlData, crawlable })} - > - Make it crawlable - - )} - {!isEdit && ( -

- setShortUrlData({ ...shortUrlData, findIfExists })} - > - Use existing URL if found - - -

- )} -
- )} + Use existing URL if found + + +

+ )} +
)} diff --git a/src/short-urls/helpers/QrCodeModal.tsx b/src/short-urls/helpers/QrCodeModal.tsx index 6521e5e1..053c5e11 100644 --- a/src/short-urls/helpers/QrCodeModal.tsx +++ b/src/short-urls/helpers/QrCodeModal.tsx @@ -10,7 +10,6 @@ import { CopyToClipboardIcon } from '../../utils/CopyToClipboardIcon'; import { buildQrCodeUrl, QrCodeCapabilities, QrCodeFormat, QrErrorCorrection } from '../../utils/helpers/qrCodes'; import { supportsQrCodeSizeInQuery, - supportsQrCodeSvgFormat, supportsQrCodeMargin, supportsQrErrorCorrection, } from '../../utils/helpers/features'; @@ -33,10 +32,10 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC('L'); const capabilities: QrCodeCapabilities = useMemo(() => ({ useSizeInPath: !supportsQrCodeSizeInQuery(selectedServer), - svgIsSupported: supportsQrCodeSvgFormat(selectedServer), marginIsSupported: supportsQrCodeMargin(selectedServer), errorCorrectionIsSupported: supportsQrErrorCorrection(selectedServer), }), [ selectedServer ]); + const willRenderThreeControls = capabilities.marginIsSupported !== capabilities.errorCorrectionIsSupported; const qrCodeUrl = useMemo( () => buildQrCodeUrl(shortUrl, { size, format, margin, errorCorrection }, capabilities), [ shortUrl, size, format, margin, errorCorrection, capabilities ], @@ -58,11 +57,7 @@ const QrCodeModal = (imageDownloader: ImageDownloader, ForServerVersion: FC {capabilities.marginIsSupported && ( - + )} - {capabilities.svgIsSupported && ( - - - - )} + + + {capabilities.errorCorrectionIsSupported && ( diff --git a/src/tags/TagCard.tsx b/src/tags/TagCard.tsx index 6bad3281..e3cd220e 100644 --- a/src/tags/TagCard.tsx +++ b/src/tags/TagCard.tsx @@ -5,7 +5,6 @@ import { FC, useEffect, useRef } from 'react'; import { Link } from 'react-router-dom'; import { prettify } from '../utils/helpers/numbers'; import { useToggle } from '../utils/helpers/hooks'; -import { Versions } from '../utils/helpers/version'; import ColorGenerator from '../utils/services/ColorGenerator'; import { isServerWithId, SelectedServer } from '../servers/data'; import TagBullet from './helpers/TagBullet'; @@ -25,16 +24,13 @@ const isTruncated = (el: HTMLElement | undefined): boolean => !!el && el.scrollW const TagCard = ( DeleteTagConfirmModal: FC, EditTagModal: FC, - ForServerVersion: FC, colorGenerator: ColorGenerator, ) => ({ tag, tagStats, selectedServer, displayed, toggle }: TagCardProps) => { const [ isDeleteModalOpen, toggleDelete ] = useToggle(); const [ isEditModalOpen, toggleEdit ] = useToggle(); const [ hasTitle,, displayTitle ] = useToggle(); const titleRef = useRef(); - const serverId = isServerWithId(selectedServer) ? selectedServer.id : ''; - const shortUrlsLink = `/server/${serverId}/list-short-urls/1?tag=${encodeURIComponent(tag)}`; useEffect(() => { if (isTruncated(titleRef.current)) { @@ -59,12 +55,7 @@ const TagCard = ( }} > - - {tag} - - - {tag} - + {tag} @@ -72,7 +63,7 @@ const TagCard = ( Short URLs diff --git a/src/tags/services/provideServices.ts b/src/tags/services/provideServices.ts index ac1753dc..1e3e8d39 100644 --- a/src/tags/services/provideServices.ts +++ b/src/tags/services/provideServices.ts @@ -18,14 +18,7 @@ const provideServices = (bottle: Bottle, connect: ConnectDecorator) => { bottle.serviceFactory('TagsSelector', TagsSelector, 'ColorGenerator'); bottle.decorator('TagsSelector', connect([ 'tagsList', 'settings' ], [ 'listTags' ])); - bottle.serviceFactory( - 'TagCard', - TagCard, - 'DeleteTagConfirmModal', - 'EditTagModal', - 'ForServerVersion', - 'ColorGenerator', - ); + bottle.serviceFactory('TagCard', TagCard, 'DeleteTagConfirmModal', 'EditTagModal', 'ColorGenerator'); bottle.serviceFactory('DeleteTagConfirmModal', () => DeleteTagConfirmModal); bottle.decorator('DeleteTagConfirmModal', connect([ 'tagDelete' ], [ 'deleteTag', 'tagDeleted' ])); diff --git a/src/utils/helpers/features.ts b/src/utils/helpers/features.ts index 68651fcc..120926f8 100644 --- a/src/utils/helpers/features.ts +++ b/src/utils/helpers/features.ts @@ -4,16 +4,6 @@ import { versionMatch, Versions } from './version'; const serverMatchesVersions = (versions: Versions) => (selectedServer: SelectedServer): boolean => isReachableServer(selectedServer) && versionMatch(selectedServer.version, versions); -export const supportsSettingShortCodeLength = serverMatchesVersions({ minVersion: '2.1.0' }); - -export const supportsTagVisits = serverMatchesVersions({ minVersion: '2.2.0' }); - -export const supportsListingDomains = serverMatchesVersions({ minVersion: '2.4.0' }); - -export const supportsQrCodeSvgFormat = supportsListingDomains; - -export const supportsValidateUrl = supportsListingDomains; - export const supportsQrCodeSizeInQuery = serverMatchesVersions({ minVersion: '2.5.0' }); export const supportsShortUrlTitle = serverMatchesVersions({ minVersion: '2.6.0' }); diff --git a/src/utils/helpers/qrCodes.ts b/src/utils/helpers/qrCodes.ts index fb94350a..c096f148 100644 --- a/src/utils/helpers/qrCodes.ts +++ b/src/utils/helpers/qrCodes.ts @@ -3,7 +3,6 @@ import { stringifyQuery } from './query'; export interface QrCodeCapabilities { useSizeInPath: boolean; - svgIsSupported: boolean; marginIsSupported: boolean; errorCorrectionIsSupported: boolean; } @@ -22,12 +21,12 @@ export interface QrCodeOptions { export const buildQrCodeUrl = ( shortUrl: string, { size, format, margin, errorCorrection }: QrCodeOptions, - { useSizeInPath, svgIsSupported, marginIsSupported, errorCorrectionIsSupported }: QrCodeCapabilities, + { useSizeInPath, marginIsSupported, errorCorrectionIsSupported }: QrCodeCapabilities, ): string => { const baseUrl = `${shortUrl}/qr-code${useSizeInPath ? `/${size}` : ''}`; const query = stringifyQuery({ size: useSizeInPath ? undefined : size, - format: svgIsSupported ? format : undefined, + format, margin: marginIsSupported && margin > 0 ? margin : undefined, errorCorrection: errorCorrectionIsSupported ? errorCorrection : undefined, }); diff --git a/test/common/MenuLayout.test.tsx b/test/common/MenuLayout.test.tsx index 439bb81f..e45dfb3d 100644 --- a/test/common/MenuLayout.test.tsx +++ b/test/common/MenuLayout.test.tsx @@ -49,8 +49,6 @@ describe('', () => { }); it.each([ - [ '2.1.0' as SemVer, 7 ], - [ '2.2.0' as SemVer, 8 ], [ '2.5.0' as SemVer, 8 ], [ '2.6.0' as SemVer, 9 ], [ '2.7.0' as SemVer, 9 ], diff --git a/test/servers/Overview.test.tsx b/test/servers/Overview.test.tsx index 3b29b3e4..14cc85b1 100644 --- a/test/servers/Overview.test.tsx +++ b/test/servers/Overview.test.tsx @@ -64,13 +64,6 @@ describe('', () => { expect(cards.at(3).html()).toContain(prettify(3)); }); - it('displays warning in first card for old shlink versions', () => { - const wrapper = createWrapper(); - const firstCard = wrapper.find(CardText).first(); - - expect(firstCard.html()).toContain('Shlink 2.2 is needed'); - }); - it('nests complex components', () => { const wrapper = createWrapper(); diff --git a/test/short-urls/ShortUrlForm.test.tsx b/test/short-urls/ShortUrlForm.test.tsx index 380d1309..66ae1fc1 100644 --- a/test/short-urls/ShortUrlForm.test.tsx +++ b/test/short-urls/ShortUrlForm.test.tsx @@ -13,9 +13,10 @@ import { parseDate } from '../../src/utils/helpers/date'; describe('', () => { let wrapper: ShallowWrapper; const TagsSelector = () => null; + const DomainSelector = () => null; const createShortUrl = jest.fn(async () => Promise.resolve()); const createWrapper = (selectedServer: SelectedServer = null, mode: Mode = 'create') => { - const ShortUrlForm = createShortUrlForm(TagsSelector, () => null); + const ShortUrlForm = createShortUrlForm(TagsSelector, DomainSelector); wrapper = shallow( ', () => { wrapper.find(Input).first().simulate('change', { target: { value: 'https://long-domain.com/foo/bar' } }); wrapper.find('TagsSelector').simulate('change', [ 'tag_foo', 'tag_bar' ]); wrapper.find('#customSlug').simulate('change', { target: { value: 'my-slug' } }); - wrapper.find('#domain').simulate('change', { target: { value: 'example.com' } }); + wrapper.find(DomainSelector).simulate('change', 'example.com'); wrapper.find('#maxVisits').simulate('change', { target: { value: '20' } }); wrapper.find('#shortCodeLength').simulate('change', { target: { value: 15 } }); wrapper.find(DateInput).at(0).simulate('change', validSince); @@ -68,12 +69,8 @@ describe('', () => { [ null, 'create-basic' as Mode, 0 ], [ Mock.of({ version: '2.6.0' }), 'create' as Mode, 4 ], [ Mock.of({ version: '2.5.0' }), 'create' as Mode, 4 ], - [ Mock.of({ version: '2.4.0' }), 'create' as Mode, 4 ], - [ Mock.of({ version: '2.3.0' }), 'create' as Mode, 4 ], [ Mock.of({ version: '2.6.0' }), 'edit' as Mode, 4 ], [ Mock.of({ version: '2.5.0' }), 'edit' as Mode, 3 ], - [ Mock.of({ version: '2.4.0' }), 'edit' as Mode, 3 ], - [ Mock.of({ version: '2.3.0' }), 'edit' as Mode, 2 ], ])( 'renders expected amount of cards based on server capabilities and mode', (selectedServer, mode, expectedAmountOfCards) => { diff --git a/test/short-urls/helpers/QrCodeModal.test.tsx b/test/short-urls/helpers/QrCodeModal.test.tsx index aa8b5160..900e90e2 100644 --- a/test/short-urls/helpers/QrCodeModal.test.tsx +++ b/test/short-urls/helpers/QrCodeModal.test.tsx @@ -43,9 +43,6 @@ describe('', () => { }); it.each([ - [ '2.3.0' as SemVer, 0, '/qr-code/300' ], - [ '2.4.0' as SemVer, 0, '/qr-code/300?format=png' ], - [ '2.4.0' as SemVer, 10, '/qr-code/300?format=png' ], [ '2.5.0' as SemVer, 0, '/qr-code?size=300&format=png' ], [ '2.6.0' as SemVer, 0, '/qr-code?size=300&format=png' ], [ '2.6.0' as SemVer, 10, '/qr-code?size=300&format=png&margin=10' ], @@ -90,8 +87,6 @@ describe('', () => { }); it.each([ - [ '2.3.0' as SemVer, 0, 'col-12' ], - [ '2.4.0' as SemVer, 1, 'col-md-6' ], [ '2.6.0' as SemVer, 1, 'col-md-4' ], [ '2.8.0' as SemVer, 2, 'col-md-6' ], ])('shows expected components based on server version', (version, expectedAmountOfDropdowns, expectedRangeClass) => { diff --git a/test/tags/TagCard.test.tsx b/test/tags/TagCard.test.tsx index db1dce18..8bdd6c6c 100644 --- a/test/tags/TagCard.test.tsx +++ b/test/tags/TagCard.test.tsx @@ -14,7 +14,7 @@ describe('', () => { }; const DeleteTagConfirmModal = jest.fn(); const EditTagModal = jest.fn(); - const TagCard = createTagCard(DeleteTagConfirmModal, EditTagModal, () => null, Mock.all()); + const TagCard = createTagCard(DeleteTagConfirmModal, EditTagModal, Mock.all()); const createWrapper = (tag = 'ssr') => { wrapper = shallow( ', () => { it('shows expected tag stats', () => { const links = wrapper.find(Link); - expect(links.at(1).prop('to')).toEqual('/server/1/list-short-urls/1?tag=ssr'); - expect(links.at(1).text()).toContain('48'); - expect(links.at(2).prop('to')).toEqual('/server/1/tag/ssr/visits'); - expect(links.at(2).text()).toContain('23,257'); + expect(links).toHaveLength(2); + expect(links.at(0).prop('to')).toEqual('/server/1/list-short-urls/1?tag=ssr'); + expect(links.at(0).text()).toContain('48'); + expect(links.at(1).prop('to')).toEqual('/server/1/tag/ssr/visits'); + expect(links.at(1).text()).toContain('23,257'); }); }); diff --git a/test/utils/helpers/qrCodes.test.ts b/test/utils/helpers/qrCodes.test.ts index c9d7bcbe..5a2edf22 100644 --- a/test/utils/helpers/qrCodes.test.ts +++ b/test/utils/helpers/qrCodes.test.ts @@ -6,67 +6,67 @@ describe('qrCodes', () => { [ 'foo.com', { size: 530, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: true, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, + { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false }, 'foo.com/qr-code/530?format=svg', ], [ 'foo.com', { size: 530, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: true, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, + { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false }, 'foo.com/qr-code/530?format=png', ], [ 'bar.io', { size: 870, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: false, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, - 'bar.io/qr-code?size=870', + { useSizeInPath: false, marginIsSupported: false, errorCorrectionIsSupported: false }, + 'bar.io/qr-code?size=870&format=svg', ], [ 'bar.io', { size: 200, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: false, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, + { useSizeInPath: false, marginIsSupported: false, errorCorrectionIsSupported: false }, 'bar.io/qr-code?size=200&format=png', ], [ 'bar.io', { size: 200, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: false, svgIsSupported: true, marginIsSupported: false, errorCorrectionIsSupported: false }, + { useSizeInPath: false, marginIsSupported: false, errorCorrectionIsSupported: false }, 'bar.io/qr-code?size=200&format=svg', ], [ 'foo.net', { size: 480, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: true, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, - 'foo.net/qr-code/480', + { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false }, + 'foo.net/qr-code/480?format=png', ], [ 'foo.net', { size: 480, format: 'svg' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: true, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, - 'foo.net/qr-code/480', + { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false }, + 'foo.net/qr-code/480?format=svg', ], [ 'shlink.io', { size: 123, format: 'svg' as QrCodeFormat, margin: 10, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: true, svgIsSupported: false, marginIsSupported: false, errorCorrectionIsSupported: false }, - 'shlink.io/qr-code/123', + { useSizeInPath: true, marginIsSupported: false, errorCorrectionIsSupported: false }, + 'shlink.io/qr-code/123?format=svg', ], [ 'shlink.io', { size: 456, format: 'png' as QrCodeFormat, margin: 10, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: true, svgIsSupported: true, marginIsSupported: true, errorCorrectionIsSupported: false }, + { useSizeInPath: true, marginIsSupported: true, errorCorrectionIsSupported: false }, 'shlink.io/qr-code/456?format=png&margin=10', ], [ 'shlink.io', { size: 456, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'L' as QrErrorCorrection }, - { useSizeInPath: true, svgIsSupported: true, marginIsSupported: true, errorCorrectionIsSupported: false }, + { useSizeInPath: true, marginIsSupported: true, errorCorrectionIsSupported: false }, 'shlink.io/qr-code/456?format=png', ], [ 'shlink.io', { size: 456, format: 'png' as QrCodeFormat, margin: 0, errorCorrection: 'H' as QrErrorCorrection }, - { useSizeInPath: true, svgIsSupported: true, marginIsSupported: true, errorCorrectionIsSupported: true }, + { useSizeInPath: true, marginIsSupported: true, errorCorrectionIsSupported: true }, 'shlink.io/qr-code/456?format=png&errorCorrection=H', ], ])('builds expected URL based in params', (shortUrl, options, capabilities, expectedUrl) => {