Merge pull request #751 from acelaya-forks/feature/fix-max-length

Feature/fix max length
This commit is contained in:
Alejandro Celaya
2022-11-25 20:14:53 +01:00
committed by GitHub
8 changed files with 50 additions and 22 deletions

View File

@@ -25,6 +25,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
* [#729](https://github.com/shlinkio/shlink-web-client/issues/729) Fixed wrong stats displayed in tags after renaming. * [#729](https://github.com/shlinkio/shlink-web-client/issues/729) Fixed wrong stats displayed in tags after renaming.
* [#737](https://github.com/shlinkio/shlink-web-client/issues/737) Fixed incorrect contrast in warning messages when using dark theme. * [#737](https://github.com/shlinkio/shlink-web-client/issues/737) Fixed incorrect contrast in warning messages when using dark theme.
* [#726](https://github.com/shlinkio/shlink-web-client/issues/726) Fixed delete server and delete short URL modals getting removed from the DOM before finishing close transition. * [#726](https://github.com/shlinkio/shlink-web-client/issues/726) Fixed delete server and delete short URL modals getting removed from the DOM before finishing close transition.
* [#749](https://github.com/shlinkio/shlink-web-client/issues/749) Fixed broken short URLs table when some short URL has a too long custom slug.
## [3.7.3] - 2022-09-13 ## [3.7.3] - 2022-09-13

View File

@@ -3,7 +3,8 @@
@import './utils/base'; @import './utils/base';
@import 'node_modules/bootstrap/scss/bootstrap.scss'; @import 'node_modules/bootstrap/scss/bootstrap.scss';
@import './common/react-tag-autocomplete.scss'; @import './common/react-tag-autocomplete.scss';
@import 'utils/theme/theme'; @import './utils/theme/theme';
@import './utils/mixins/text-ellipsis';
@import './utils/table/ResponsiveTable'; @import './utils/table/ResponsiveTable';
@import './utils/StickyCardPaginator'; @import './utils/StickyCardPaginator';
@@ -222,9 +223,7 @@ hr {
} }
.text-ellipsis { .text-ellipsis {
text-overflow: ellipsis; @include text-ellipsis();
overflow: hidden;
white-space: nowrap;
} }
.progress-bar { .progress-bar {

View File

@@ -15,6 +15,8 @@ interface DeleteShortUrlModalConnectProps extends ShortUrlModalProps {
resetDeleteShortUrl: () => void; resetDeleteShortUrl: () => void;
} }
const DELETION_PATTERN = 'delete';
export const DeleteShortUrlModal = ({ export const DeleteShortUrlModal = ({
shortUrl, shortUrl,
toggle, toggle,
@@ -41,12 +43,12 @@ export const DeleteShortUrlModal = ({
<ModalBody> <ModalBody>
<p><b className="text-danger">Caution!</b> You are about to delete a short URL.</p> <p><b className="text-danger">Caution!</b> You are about to delete a short URL.</p>
<p>This action cannot be undone. Once you have deleted it, all the visits stats will be lost.</p> <p>This action cannot be undone. Once you have deleted it, all the visits stats will be lost.</p>
<p>Write <b>{shortUrl.shortCode}</b> to confirm deletion.</p> <p>Write <b>{DELETION_PATTERN}</b> to confirm deletion.</p>
<input <input
type="text" type="text"
className="form-control" className="form-control"
placeholder={`Insert the short code (${shortUrl.shortCode})`} placeholder={`Insert ${DELETION_PATTERN}`}
value={inputValue} value={inputValue}
onChange={(e) => setInputValue(e.target.value)} onChange={(e) => setInputValue(e.target.value)}
/> />
@@ -62,7 +64,7 @@ export const DeleteShortUrlModal = ({
<button <button
type="submit" type="submit"
className="btn btn-danger" className="btn btn-danger"
disabled={inputValue !== shortUrl.shortCode || loading} disabled={inputValue !== DELETION_PATTERN || loading}
> >
{loading ? 'Deleting...' : 'Delete'} {loading ? 'Deleting...' : 'Delete'}
</button> </button>

View File

@@ -1,4 +1,5 @@
@import '../../utils/base'; @import '../../utils/base';
@import '../../utils/mixins/text-ellipsis';
@import '../../utils/mixins/vertical-align'; @import '../../utils/mixins/vertical-align';
.short-urls-row__cell.short-urls-row__cell { .short-urls-row__cell.short-urls-row__cell {
@@ -13,6 +14,26 @@
position: relative; position: relative;
} }
.short-urls-row__cell--indivisible {
@media (min-width: $lgMin) {
white-space: nowrap;
}
}
.short-urls-row__short-url-wrapper {
@media (max-width: $mdMax) {
word-break: break-all;
}
@media (min-width: $lgMin) {
@include text-ellipsis();
vertical-align: bottom;
display: inline-block;
max-width: 18rem;
}
}
.short-urls-row__copy-hint { .short-urls-row__copy-hint {
@include vertical-align(translateX(10px)); @include vertical-align(translateX(10px));

View File

@@ -43,11 +43,8 @@ export const ShortUrlsRow = (
}; };
useEffect(() => { useEffect(() => {
if (isFirstRun.current) { !isFirstRun.current && setActive();
isFirstRun.current = false; isFirstRun.current = false;
} else {
setActive();
}
}, [shortUrl.visitsCount]); }, [shortUrl.visitsCount]);
return ( return (
@@ -56,15 +53,20 @@ export const ShortUrlsRow = (
<Time date={shortUrl.dateCreated} /> <Time date={shortUrl.dateCreated} />
</td> </td>
<td className="responsive-table__cell short-urls-row__cell" data-th="Short URL"> <td className="responsive-table__cell short-urls-row__cell" data-th="Short URL">
<span className="indivisible short-urls-row__cell--relative"> <span className="short-urls-row__cell--relative short-urls-row__cell--indivisible">
<ExternalLink href={shortUrl.shortUrl} /> <span className="short-urls-row__short-url-wrapper">
<ExternalLink href={shortUrl.shortUrl} />
</span>
<CopyToClipboardIcon text={shortUrl.shortUrl} onCopy={setCopiedToClipboard} /> <CopyToClipboardIcon text={shortUrl.shortUrl} onCopy={setCopiedToClipboard} />
<span className="badge bg-warning text-black short-urls-row__copy-hint" hidden={!copiedToClipboard}> <span className="badge bg-warning text-black short-urls-row__copy-hint" hidden={!copiedToClipboard}>
Copied short URL! Copied short URL!
</span> </span>
</span> </span>
</td> </td>
<td className="responsive-table__cell short-urls-row__cell short-urls-row__cell--break" data-th={`${shortUrl.title ? 'Title' : 'Long URL'}`}> <td
className="responsive-table__cell short-urls-row__cell short-urls-row__cell--break"
data-th={`${shortUrl.title ? 'Title' : 'Long URL'}`}
>
<ExternalLink href={shortUrl.longUrl}>{shortUrl.title ?? shortUrl.longUrl}</ExternalLink> <ExternalLink href={shortUrl.longUrl}>{shortUrl.title ?? shortUrl.longUrl}</ExternalLink>
</td> </td>
{shortUrl.title && ( {shortUrl.title && (

View File

@@ -27,7 +27,7 @@ export const useTimeoutToggle = (
return [flag, callback]; return [flag, callback];
}; };
type ToggleResult = [ boolean, () => void, () => void, () => void ]; type ToggleResult = [boolean, () => void, () => void, () => void];
export const useToggle = (initialValue = false): ToggleResult => { export const useToggle = (initialValue = false): ToggleResult => {
const [flag, setFlag] = useState<boolean>(initialValue); const [flag, setFlag] = useState<boolean>(initialValue);

View File

@@ -0,0 +1,5 @@
@mixin text-ellipsis() {
text-overflow: ellipsis;
overflow: hidden;
white-space: nowrap;
}

View File

@@ -62,30 +62,28 @@ describe('<DeleteShortUrlModal />', () => {
}); });
it('enables submit button when proper short code is provided', async () => { it('enables submit button when proper short code is provided', async () => {
const shortCode = 'abc123';
const { user } = setUp({ const { user } = setUp({
loading: false, loading: false,
error: false, error: false,
shortCode, shortCode: 'abc123',
}); });
const getDeleteBtn = () => screen.getByRole('button', { name: 'Delete' }); const getDeleteBtn = () => screen.getByRole('button', { name: 'Delete' });
expect(getDeleteBtn()).toHaveAttribute('disabled'); expect(getDeleteBtn()).toHaveAttribute('disabled');
await user.type(screen.getByPlaceholderText(/^Insert the short code/), shortCode); await user.type(screen.getByPlaceholderText('Insert delete'), 'delete');
expect(getDeleteBtn()).not.toHaveAttribute('disabled'); expect(getDeleteBtn()).not.toHaveAttribute('disabled');
}); });
it('tries to delete short URL when form is submit', async () => { it('tries to delete short URL when form is submit', async () => {
const shortCode = 'abc123';
const { user } = setUp({ const { user } = setUp({
loading: false, loading: false,
error: false, error: false,
deleted: true, deleted: true,
shortCode, shortCode: 'abc123',
}); });
expect(deleteShortUrl).not.toHaveBeenCalled(); expect(deleteShortUrl).not.toHaveBeenCalled();
await user.type(screen.getByPlaceholderText(/^Insert the short code/), shortCode); await user.type(screen.getByPlaceholderText('Insert delete'), 'delete');
await user.click(screen.getByRole('button', { name: 'Delete' })); await user.click(screen.getByRole('button', { name: 'Delete' }));
expect(deleteShortUrl).toHaveBeenCalledTimes(1); expect(deleteShortUrl).toHaveBeenCalledTimes(1);
await waitFor(() => expect(shortUrlDeleted).toHaveBeenCalledTimes(1)); await waitFor(() => expect(shortUrlDeleted).toHaveBeenCalledTimes(1));