Merge pull request #1351 from shlinkio/develop

Release 4.2.2
This commit is contained in:
Alejandro Celaya 2024-10-19 12:15:18 +02:00 committed by GitHub
commit 2e438f9814
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 2674 additions and 1740 deletions

View file

@ -12,11 +12,12 @@ updates:
fontawesome:
patterns:
- '@fortawesome/*'
eslint-plugins: # TODO Add eslint back once updated to v9
eslint:
patterns:
- '@shlinkio/eslint-config-js-coding-standard'
- 'typescript-eslint'
- '*eslint-plugin*'
- 'eslint'
shlink:
patterns:
- '@shlinkio/*'

View file

@ -4,6 +4,24 @@ 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).
## [4.2.2] - 2024-10-19
### Added
* *Nothing*
### Changed
* Update to `@shlinkio/shlink-frontend-kit` 0.6.0
* Update to `@shlinkio/shlink-web-component` 0.10.1
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [shlink-web-component#475](https://github.com/shlinkio/shlink-web-component/issues/475) Fix incorrect amount of dots being displayed in line charts when the difference in days/weeks/months is rounded up.
## [4.2.1] - 2024-10-09
### Added
* *Nothing*

View file

@ -1,4 +1,4 @@
FROM node:22.9-alpine as node
FROM node:23.0-alpine as node
COPY . /shlink-web-client
ARG VERSION="latest"
ENV VERSION ${VERSION}

View file

@ -7,7 +7,6 @@
[![GitHub license](https://img.shields.io/github/license/shlinkio/shlink-web-client.svg?style=flat-square)](https://github.com/shlinkio/shlink-web-client/blob/main/LICENSE)
[![Mastodon](https://img.shields.io/mastodon/follow/109329425426175098?color=%236364ff&domain=https%3A%2F%2Ffosstodon.org&label=follow&logo=mastodon&logoColor=white&style=flat-square)](https://fosstodon.org/@shlinkio)
[![Twitter](https://img.shields.io/badge/follow-shlinkio-blue.svg?style=flat-square&logo=x&color=black)](https://twitter.com/shlinkio)
[![Bluesky](https://img.shields.io/badge/follow-shlinkio-0285FF.svg?style=flat-square&logo=bluesky&logoColor=white)](https://bsky.app/profile/shlinkio.bsky.social)
[![Paypal Donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=cccccc)](https://slnk.to/donate)

4292
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -31,11 +31,11 @@
"@fortawesome/free-solid-svg-icons": "^6.6.0",
"@fortawesome/react-fontawesome": "^0.2.2",
"@json2csv/plainjs": "^7.0.6",
"@reduxjs/toolkit": "^2.2.7",
"@reduxjs/toolkit": "^2.3.0",
"@shlinkio/data-manipulation": "^1.0.3",
"@shlinkio/shlink-frontend-kit": "^0.5.2",
"@shlinkio/shlink-frontend-kit": "^0.6.0",
"@shlinkio/shlink-js-sdk": "^1.2.0",
"@shlinkio/shlink-web-component": "^0.8.1",
"@shlinkio/shlink-web-component": "^0.10.1",
"bootstrap": "5.2.3",
"bottlejs": "^2.0.1",
"clsx": "^2.1.1",
@ -46,7 +46,7 @@
"react-dom": "^18.3.1",
"react-external-link": "^2.3.1",
"react-redux": "^9.1.2",
"react-router-dom": "^6.26.2",
"react-router-dom": "^6.27.0",
"reactstrap": "^9.2.3",
"redux-localstorage-simple": "^2.5.1",
"uuid": "^10.0.0",
@ -57,7 +57,7 @@
"workbox-strategies": "^7.1.0"
},
"devDependencies": {
"@shlinkio/eslint-config-js-coding-standard": "~3.1.0",
"@shlinkio/eslint-config-js-coding-standard": "~3.2.0",
"@shlinkio/stylelint-config-css-coding-standard": "~1.1.1",
"@stylistic/eslint-plugin": "^2.9.0",
"@testing-library/jest-dom": "^6.5.0",
@ -65,25 +65,25 @@
"@testing-library/user-event": "^14.5.2",
"@total-typescript/shoehorn": "^0.1.2",
"@types/react": "^18.3.11",
"@types/react-dom": "^18.3.0",
"@types/react-dom": "^18.3.1",
"@types/uuid": "^10.0.0",
"@vitejs/plugin-react": "^4.3.2",
"@vitest/coverage-v8": "^2.1.2",
"@vitest/coverage-v8": "^2.1.3",
"adm-zip": "^0.5.16",
"axe-core": "^4.10.0",
"axe-core": "^4.10.1",
"chalk": "^5.3.0",
"eslint": "^8.57.0",
"eslint": "^9.13.0",
"eslint-plugin-jsx-a11y": "^6.10.0",
"eslint-plugin-react": "^7.37.1",
"eslint-plugin-react-hooks": "^4.6.2",
"eslint-plugin-react-hooks": "^5.0.0",
"eslint-plugin-simple-import-sort": "^12.1.1",
"history": "^5.3.0",
"jsdom": "^25.0.1",
"sass": "^1.79.4",
"sass": "^1.80.3",
"stylelint": "^15.11.0",
"typescript": "^5.6.2",
"typescript-eslint": "^8.8.0",
"vite": "^5.4.8",
"typescript": "^5.6.3",
"typescript-eslint": "^8.10.0",
"vite": "^5.4.9",
"vite-plugin-pwa": "^0.20.5",
"vitest": "^2.0.2"
},

View file

@ -20,7 +20,6 @@ export function componentFactory<Deps, CompType = Omit<Partial<Deps>, keyof FC>>
console.error(`[Debug] Could not find "${dep as string}" dependency in container`);
}
// eslint-disable-next-line no-param-reassign
Component[dep] = resolvedDependency;
});

View file

@ -10,7 +10,7 @@ import './index.scss';
const store = setUpStore(container);
const { App, ScrollToTop, ErrorHandler, appUpdateAvailable } = container;
createRoot(document.getElementById('root')!).render( // eslint-disable-line @typescript-eslint/no-non-null-assertion
createRoot(document.getElementById('root')!).render(
<Provider store={store}>
<BrowserRouter basename={pack.homepage}>
<ErrorHandler>

View file

@ -1,7 +1,7 @@
import type { TimeoutToggle } from '@shlinkio/shlink-frontend-kit';
import { Result, useToggle } from '@shlinkio/shlink-frontend-kit';
import type { FC } from 'react';
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { Button } from 'reactstrap';
import { NoMenuLayout } from '../common/NoMenuLayout';
@ -50,26 +50,23 @@ const CreateServer: FCWithDeps<CreateServerProps, CreateServerDeps> = ({ servers
createServers([{ ...theServerData, id }]);
navigate(`/server/${id}`);
}, [createServers, navigate]);
useEffect(() => {
if (!serverData) {
return;
}
const onSubmit = useCallback((newServerData: ServerData) => {
setServerData(newServerData);
const serverExists = Object.values(servers).some(
({ url, apiKey }) => serverData?.url === url && serverData?.apiKey === apiKey,
({ url, apiKey }) => newServerData.url === url && newServerData.apiKey === apiKey,
);
if (serverExists) {
toggleConfirmModal();
} else {
saveNewServer(serverData);
saveNewServer(newServerData);
}
}, [saveNewServer, serverData, servers, toggleConfirmModal]);
}, [saveNewServer, servers, toggleConfirmModal]);
return (
<NoMenuLayout>
<ServerForm title={<h5 className="mb-0">Add new server</h5>} onSubmit={setServerData}>
<ServerForm title={<h5 className="mb-0">Add new server</h5>} onSubmit={onSubmit}>
{!hasServers && (
<ImportServersBtn tooltipPlacement="top" onImport={setServersImported} onImportError={setErrorImporting} />
)}

View file

@ -3,7 +3,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import type { TimeoutToggle } from '@shlinkio/shlink-frontend-kit';
import { Result, SearchField, SimpleCard } from '@shlinkio/shlink-frontend-kit';
import type { FC } from 'react';
import { useEffect, useState } from 'react';
import { useMemo, useState } from 'react';
import { Link } from 'react-router-dom';
import { Button, Row } from 'reactstrap';
import { NoMenuLayout } from '../common/NoMenuLayout';
@ -34,26 +34,23 @@ const ManageServers: FCWithDeps<ManageServersProps, ManageServersDeps> = ({ serv
useTimeoutToggle,
ManageServersRow,
} = useDependencies(ManageServers);
const allServers = Object.values(servers);
const [serversList, setServersList] = useState(allServers);
const filterServers = (searchTerm: string) => setServersList(
allServers.filter(({ name, url }) => `${name} ${url}`.toLowerCase().match(searchTerm.toLowerCase())),
const [searchTerm, setSearchTerm] = useState('');
const allServers = useMemo(() => Object.values(servers), [servers]);
const filteredServers = useMemo(
() => allServers.filter(({ name, url }) => `${name} ${url}`.toLowerCase().match(searchTerm.toLowerCase())),
[allServers, searchTerm],
);
const hasAutoConnect = serversList.some(({ autoConnect }) => !!autoConnect);
const hasAutoConnect = allServers.some(({ autoConnect }) => !!autoConnect);
const [errorImporting, setErrorImporting] = useTimeoutToggle(false, SHOW_IMPORT_MSG_TIME);
useEffect(() => {
setServersList(Object.values(servers));
}, [servers]);
return (
<NoMenuLayout>
<SearchField className="mb-3" onChange={filterServers} />
<SearchField className="mb-3" onChange={setSearchTerm} />
<Row className="mb-3">
<div className="col-md-6 d-flex d-md-block mb-2 mb-md-0">
<ImportServersBtn className="flex-fill" onImportError={setErrorImporting}>Import servers</ImportServersBtn>
{allServers.length > 0 && (
{filteredServers.length > 0 && (
<Button outline className="ms-2 flex-fill" onClick={async () => serversExporter.exportServers()}>
<FontAwesomeIcon icon={exportIcon} fixedWidth /> Export servers
</Button>
@ -77,8 +74,8 @@ const ManageServers: FCWithDeps<ManageServersProps, ManageServersDeps> = ({ serv
</tr>
</thead>
<tbody>
{!serversList.length && <tr className="text-center"><td colSpan={4}>No servers found.</td></tr>}
{serversList.map((server) => (
{!filteredServers.length && <tr className="text-center"><td colSpan={4}>No servers found.</td></tr>}
{filteredServers.map((server) => (
<ManageServersRow key={server.id} server={server} hasAutoConnect={hasAutoConnect} />
))}
</tbody>

View file

@ -67,7 +67,7 @@ const ImportServersBtn: FCWithDeps<ImportServersBtnConnectProps, ImportServersBt
})
.then(() => {
// Reset input after processing file
(target as { value: string | null }).value = null; // eslint-disable-line no-param-reassign
(target as { value: string | null }).value = null;
})
.catch(onImportError),
[create, onImportError, servers, serversImporter, showModal],

View file

@ -1,6 +1,6 @@
import { InputFormGroup, SimpleCard } from '@shlinkio/shlink-frontend-kit';
import type { FC, PropsWithChildren, ReactNode } from 'react';
import { useEffect, useState } from 'react';
import { useState } from 'react';
import { handleEventPreventingDefault } from '../../utils/utils';
import type { ServerData } from '../data';
@ -11,19 +11,11 @@ type ServerFormProps = PropsWithChildren<{
}>;
export const ServerForm: FC<ServerFormProps> = ({ onSubmit, initialValues, children, title }) => {
const [name, setName] = useState('');
const [url, setUrl] = useState('');
const [apiKey, setApiKey] = useState('');
const [name, setName] = useState(initialValues?.name ?? '');
const [url, setUrl] = useState(initialValues?.url ?? '');
const [apiKey, setApiKey] = useState(initialValues?.apiKey ?? '');
const handleSubmit = handleEventPreventingDefault(() => onSubmit({ name, url, apiKey }));
useEffect(() => {
if (initialValues) {
setName(initialValues.name);
setUrl(initialValues.url);
setApiKey(initialValues.apiKey);
}
}, [initialValues]);
return (
<form className="server-form" name="serverForm" onSubmit={handleSubmit}>
<SimpleCard className="mb-3" title={title}>

View file

@ -22,7 +22,7 @@ export class ServersExporter {
saveCsv(this.window, csv, SERVERS_FILENAME);
} catch (e) {
// FIXME Handle error
console.error(e); // eslint-disable-line no-console
console.error(e);
}
};
}

View file

@ -1,6 +1,5 @@
import type { ShlinkState } from '../../container/types';
/* eslint-disable no-param-reassign */
export const migrateDeprecatedSettings = (state: Partial<ShlinkState>): Partial<ShlinkState> => {
if (!state.settings) {
return state;

View file

@ -39,7 +39,7 @@ describe('ShlinkApiClientBuilder', () => {
const apiKey = 'apiKey';
const apiClient = buildShlinkApiClient(fromPartial({}))(server({ url, apiKey }));
expect(apiClient['serverInfo'].baseUrl).toEqual(url); // eslint-disable-line @typescript-eslint/dot-notation
expect(apiClient['serverInfo'].apiKey).toEqual(apiKey); // eslint-disable-line @typescript-eslint/dot-notation
expect(apiClient['serverInfo'].baseUrl).toEqual(url);
expect(apiClient['serverInfo'].apiKey).toEqual(apiKey);
});
});