Compare commits

...

10 Commits

Author SHA1 Message Date
Alejandro Celaya
e9fcdcb049 Merge pull request #563 from shlinkio/develop
Release 3.5.1
2022-01-08 11:25:22 +01:00
Alejandro Celaya
5b7f1ef18a Merge pull request #562 from acelaya-forks/feature/autocomplete-new-tags
Fixed new tags added to new short URLs, not appearing on tags autosug…
2022-01-08 11:21:32 +01:00
Alejandro Celaya
715128a653 Fixed new tags added to new short URLs, not appearing on tags autosuggest 2022-01-08 11:14:11 +01:00
Alejandro Celaya
83fbdbb135 Merge pull request #561 from acelaya-forks/feature/overview-list
Fixed short URLs list in overview page
2022-01-08 10:55:02 +01:00
Alejandro Celaya
2e963bdc8e Fixed short URLs list in overview page 2022-01-08 10:51:34 +01:00
Alejandro Celaya
8d6e93ea4f Merge pull request #560 from acelaya-forks/feature/logo-alignment
Feature/logo alignment
2022-01-08 10:30:26 +01:00
Alejandro Celaya
112a8cdf2f Updated changelog 2022-01-08 10:24:07 +01:00
Alejandro Celaya
27476d8b23 Added missing border in welcome screen title 2022-01-08 10:22:51 +01:00
Alejandro Celaya
2ad2d69b2b Fixed Shlink logo not being vertically aligned in welcome screen 2022-01-08 10:19:20 +01:00
Alejandro Celaya
a3d6944fc1 Added Twitter follow badge to readme 2022-01-07 16:16:53 +01:00
9 changed files with 103 additions and 18 deletions

View File

@@ -4,6 +4,25 @@ 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).
## [3.5.1] - 2022-01-08
### Added
* *Nothing*
### Changed
* *Nothing*
### Deprecated
* *Nothing*
### Removed
* *Nothing*
### Fixed
* [#555](https://github.com/shlinkio/shlink-web-client/issues/555) Fixed vertical alignment in welcome screen logo.
* [#554](https://github.com/shlinkio/shlink-web-client/issues/554) Fixed behavior in overview page, where items in the list of short URLs were stripped out when creating new ones, even if the amount of short URLs was still not yet big enough.
* [#557](https://github.com/shlinkio/shlink-web-client/issues/557) Fixed new tags added to new short URLs, not appearing on tags autosuggest.
## [3.5.0] - 2022-01-01
### Added
* [#407](https://github.com/shlinkio/shlink-web-client/pull/407) Improved how visits (short URLs, tags and orphan) are loaded, to avoid ending up in a page with "There are no visits matching current filter".

View File

@@ -5,6 +5,7 @@
[![GitHub release](https://img.shields.io/github/release/shlinkio/shlink-web-client.svg?style=flat-square)](https://github.com/shlinkio/shlink-web-client/releases/latest)
[![Docker pulls](https://img.shields.io/docker/pulls/shlinkio/shlink-web-client.svg?logo=docker&style=flat-square)](https://hub.docker.com/r/shlinkio/shlink-web-client/)
[![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)
[![Twitter](https://img.shields.io/twitter/follow/shlinkio?color=blue&label=follow&logo=twitter&style=flat-square)](https://twitter.com/shlinkio)
[![Paypal Donate](https://img.shields.io/badge/Donate-paypal-blue.svg?style=flat-square&logo=paypal&colorA=cccccc)](https://slnk.to/donate)
A ReactJS-based progressive web application for [Shlink](https://shlink.io).

View File

@@ -12,8 +12,16 @@
}
}
.home__logo-wrapper {
padding: 1.5rem !important;
height: 100% !important;
min-height: 300px;
}
.home__logo {
@include vertical-align();
width: calc(100% - 3rem);
}
.home__main-card {
@@ -25,6 +33,11 @@
}
}
.home__title-wrapper {
padding: 1.5rem !important;
border-bottom: 1px solid var(--border-color);
}
.home__title {
text-align: center;
font-size: 1.75rem;

View File

@@ -30,12 +30,14 @@ const Home = ({ servers, history }: HomeProps) => {
<Card className="home__main-card">
<Row noGutters>
<div className="col-md-5 d-none d-md-block">
<div className="p-4">
<ShlinkLogo />
<div className="home__logo-wrapper">
<div className="home__logo">
<ShlinkLogo />
</div>
</div>
</div>
<div className="col-md-7 home__servers-container">
<div className="p-4">
<div className="home__title-wrapper">
<h1 className="home__title">Welcome!</h1>
</div>
<ServersListGroup embedded servers={serversList}>

View File

@@ -1,7 +1,7 @@
import { FC, useEffect } from 'react';
import { Card, CardBody, CardHeader, CardText, CardTitle, Row } from 'reactstrap';
import { Link, useHistory } from 'react-router-dom';
import { ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList';
import { ITEMS_IN_OVERVIEW_PAGE, ShortUrlsList as ShortUrlsListState } from '../short-urls/reducers/shortUrlsList';
import { prettify } from '../utils/helpers/numbers';
import { TagsList } from '../tags/reducers/tagsList';
import { ShortUrlsTableProps } from '../short-urls/ShortUrlsTable';
@@ -44,7 +44,7 @@ export const Overview = (
const history = useHistory();
useEffect(() => {
listShortUrls({ itemsPerPage: 5, orderBy: { field: 'dateCreated', dir: 'DESC' } });
listShortUrls({ itemsPerPage: ITEMS_IN_OVERVIEW_PAGE, orderBy: { field: 'dateCreated', dir: 'DESC' } });
listTags();
loadVisitsOverview();
}, []);

View File

@@ -1,4 +1,4 @@
import { assoc, assocPath, init, last, pipe, reject } from 'ramda';
import { assoc, assocPath, last, pipe, reject } from 'ramda';
import { Action, Dispatch } from 'redux';
import { shortUrlMatches } from '../helpers';
import { CREATE_VISITS, CreateVisitsAction } from '../../visits/reducers/visitCreation';
@@ -16,6 +16,8 @@ export const LIST_SHORT_URLS_ERROR = 'shlink/shortUrlsList/LIST_SHORT_URLS_ERROR
export const LIST_SHORT_URLS = 'shlink/shortUrlsList/LIST_SHORT_URLS';
/* eslint-enable padding-line-between-statements */
export const ITEMS_IN_OVERVIEW_PAGE = 5;
export interface ShortUrlsList {
shortUrls?: ShlinkShortUrlsResponse;
loading: boolean;
@@ -75,10 +77,11 @@ export default buildReducer<ShortUrlsList, ListShortUrlsCombinedAction>({
),
[CREATE_SHORT_URL]: pipe(
// The only place where the list and the creation form coexist is the overview page.
// There we can assume we are displaying page 1, and therefore, we can safely prepend the new short URL and remove the last one.
// There we can assume we are displaying page 1, and therefore, we can safely prepend the new short URL.
// We can also remove the items above the amount that is displayed there.
(state: ShortUrlsList, { result }: CreateShortUrlAction) => !state.shortUrls ? state : assocPath(
[ 'shortUrls', 'data' ],
[ result, ...init(state.shortUrls.data) ],
[ result, ...state.shortUrls.data.slice(0, ITEMS_IN_OVERVIEW_PAGE - 1) ],
state,
),
(state: ShortUrlsList) => !state.shortUrls ? state : assocPath(

View File

@@ -9,6 +9,7 @@ import { CreateVisit, Stats } from '../../visits/types';
import { parseApiError } from '../../api/utils';
import { TagStats } from '../data';
import { ApiErrorAction } from '../../api/types/actions';
import { CREATE_SHORT_URL, CreateShortUrlAction } from '../../short-urls/reducers/shortUrlCreation';
import { DeleteTagAction, TAG_DELETED } from './tagDelete';
import { EditTagAction, TAG_EDITED } from './tagEdit';
@@ -42,6 +43,7 @@ interface FilterTagsAction extends Action<string> {
type TagsCombinedAction = ListTagsAction
& DeleteTagAction
& CreateVisitsAction
& CreateShortUrlAction
& EditTagAction
& FilterTagsAction
& ApiErrorAction;
@@ -102,6 +104,10 @@ export default buildReducer<TagsList, TagsCombinedAction>({
...state,
stats: increaseVisitsForTags(calculateVisitsPerTag(createdVisits), state.stats),
}),
[CREATE_SHORT_URL]: ({ tags: stateTags, ...rest }, { result }) => ({
...rest,
tags: stateTags.concat(result.tags.filter((tag) => !stateTags.includes(tag))), // More performant than [ ...new Set(...) ]
}),
}, initialState);
export const listTags = (buildShlinkApiClient: ShlinkApiClientBuilder, force = true) => () => async (

View File

@@ -14,6 +14,8 @@ import { CREATE_SHORT_URL } from '../../../src/short-urls/reducers/shortUrlCreat
import { SHORT_URL_EDITED } from '../../../src/short-urls/reducers/shortUrlEdition';
describe('shortUrlsListReducer', () => {
const shortCode = 'abc123';
describe('reducer', () => {
it('returns loading on LIST_SHORT_URLS_START', () =>
expect(reducer(undefined, { type: LIST_SHORT_URLS_START } as any)).toEqual({
@@ -35,7 +37,6 @@ describe('shortUrlsListReducer', () => {
}));
it('removes matching URL and reduces total on SHORT_URL_DELETED', () => {
const shortCode = 'abc123';
const state = {
shortUrls: Mock.of<ShlinkShortUrlsResponse>({
data: [
@@ -72,7 +73,6 @@ describe('shortUrlsListReducer', () => {
[[{}], 10 ],
[[], 10 ],
])('updates visits count on CREATE_VISITS', (createdVisits, expectedCount) => {
const shortCode = 'abc123';
const state = {
shortUrls: Mock.of<ShlinkShortUrlsResponse>({
data: [
@@ -98,16 +98,42 @@ describe('shortUrlsListReducer', () => {
});
});
it('prepends new short URL and increases total on CREATE_SHORT_URL', () => {
it.each([
[
[
Mock.of<ShortUrl>({ shortCode }),
Mock.of<ShortUrl>({ shortCode, domain: 'example.com' }),
Mock.of<ShortUrl>({ shortCode: 'foo' }),
],
[{ shortCode: 'newOne' }, { shortCode }, { shortCode, domain: 'example.com' }, { shortCode: 'foo' }],
],
[
[
Mock.of<ShortUrl>({ shortCode }),
Mock.of<ShortUrl>({ shortCode: 'code' }),
Mock.of<ShortUrl>({ shortCode: 'foo' }),
Mock.of<ShortUrl>({ shortCode: 'bar' }),
Mock.of<ShortUrl>({ shortCode: 'baz' }),
],
[{ shortCode: 'newOne' }, { shortCode }, { shortCode: 'code' }, { shortCode: 'foo' }, { shortCode: 'bar' }],
],
[
[
Mock.of<ShortUrl>({ shortCode }),
Mock.of<ShortUrl>({ shortCode: 'code' }),
Mock.of<ShortUrl>({ shortCode: 'foo' }),
Mock.of<ShortUrl>({ shortCode: 'bar' }),
Mock.of<ShortUrl>({ shortCode: 'baz1' }),
Mock.of<ShortUrl>({ shortCode: 'baz2' }),
Mock.of<ShortUrl>({ shortCode: 'baz3' }),
],
[{ shortCode: 'newOne' }, { shortCode }, { shortCode: 'code' }, { shortCode: 'foo' }, { shortCode: 'bar' }],
],
])('prepends new short URL and increases total on CREATE_SHORT_URL', (data, expectedData) => {
const newShortUrl = Mock.of<ShortUrl>({ shortCode: 'newOne' });
const shortCode = 'abc123';
const state = {
shortUrls: Mock.of<ShlinkShortUrlsResponse>({
data: [
Mock.of<ShortUrl>({ shortCode }),
Mock.of<ShortUrl>({ shortCode, domain: 'example.com' }),
Mock.of<ShortUrl>({ shortCode: 'foo' }),
],
data,
pagination: Mock.of<ShlinkPaginator>({
totalItems: 15,
}),
@@ -118,7 +144,7 @@ describe('shortUrlsListReducer', () => {
expect(reducer(state, { type: CREATE_SHORT_URL, result: newShortUrl } as any)).toEqual({
shortUrls: {
data: [{ shortCode: 'newOne' }, { shortCode }, { shortCode, domain: 'example.com' }],
data: expectedData,
pagination: { totalItems: 16 },
},
loading: false,

View File

@@ -11,6 +11,8 @@ import reducer, {
import { TAG_DELETED } from '../../../src/tags/reducers/tagDelete';
import { TAG_EDITED } from '../../../src/tags/reducers/tagEdit';
import { ShlinkState } from '../../../src/container/types';
import { ShortUrl } from '../../../src/short-urls/data';
import { CREATE_SHORT_URL } from '../../../src/short-urls/reducers/shortUrlCreation';
describe('tagsListReducer', () => {
const state = (props: Partial<TagsList>) => Mock.of<TagsList>(props);
@@ -74,6 +76,19 @@ describe('tagsListReducer', () => {
filteredTags,
});
});
it.each([
[[ 'foo', 'foo3', 'bar3', 'fo' ], [ 'foo', 'bar', 'baz', 'foo2', 'fo', 'foo3', 'bar3' ]],
[[ 'foo', 'bar' ], [ 'foo', 'bar', 'baz', 'foo2', 'fo' ]],
[[ 'new', 'tag' ], [ 'foo', 'bar', 'baz', 'foo2', 'fo', 'new', 'tag' ]],
])('appends new short URL\'s tags to the list of tags on CREATE_SHORT_URL', (shortUrlTags, expectedTags) => {
const tags = [ 'foo', 'bar', 'baz', 'foo2', 'fo' ];
const result = Mock.of<ShortUrl>({ tags: shortUrlTags });
expect(reducer(state({ tags }), { type: CREATE_SHORT_URL, result } as any)).toEqual({
tags: expectedTags,
});
});
});
describe('filterTags', () => {