mirror of
https://github.com/Sonarr/Sonarr
synced 2024-12-21 23:33:00 +00:00
Fixed: Truncating long text in the middle when it shouldn't be truncated
Closes #7413
This commit is contained in:
parent
417af2b915
commit
93c3f6d1d6
11 changed files with 78 additions and 61 deletions
|
@ -52,8 +52,7 @@ module.exports = (env) => {
|
|||
'node_modules'
|
||||
],
|
||||
alias: {
|
||||
jquery: 'jquery/dist/jquery.min',
|
||||
'react-middle-truncate': 'react-middle-truncate/lib/react-middle-truncate'
|
||||
jquery: 'jquery/dist/jquery.min'
|
||||
},
|
||||
fallback: {
|
||||
buffer: false,
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import MiddleTruncate from 'react-middle-truncate';
|
||||
import Label, { LabelProps } from 'Components/Label';
|
||||
import IconButton from 'Components/Link/IconButton';
|
||||
import Link from 'Components/Link/Link';
|
||||
import MiddleTruncate from 'Components/MiddleTruncate';
|
||||
import { icons } from 'Helpers/Props';
|
||||
import { TagBase } from './TagInput';
|
||||
import styles from './TagInputTag.css';
|
||||
|
@ -58,7 +58,7 @@ function TagInputTag<T extends TagBase>({
|
|||
tabIndex={-1}
|
||||
onPress={handleDelete}
|
||||
>
|
||||
<MiddleTruncate text={String(tag.name)} start={10} end={10} />
|
||||
<MiddleTruncate text={String(tag.name)} />
|
||||
</Link>
|
||||
|
||||
{canEdit ? (
|
||||
|
|
63
frontend/src/Components/MiddleTruncate.tsx
Normal file
63
frontend/src/Components/MiddleTruncate.tsx
Normal file
|
@ -0,0 +1,63 @@
|
|||
import React, { useEffect, useRef, useState } from 'react';
|
||||
import useMeasure from 'Helpers/Hooks/useMeasure';
|
||||
|
||||
interface MiddleTruncateProps {
|
||||
text: string;
|
||||
}
|
||||
|
||||
function getTruncatedText(text: string, length: number) {
|
||||
return `${text.slice(0, length)}...${text.slice(text.length - length)}`;
|
||||
}
|
||||
|
||||
function MiddleTruncate({ text }: MiddleTruncateProps) {
|
||||
const [containerRef, { width: containerWidth }] = useMeasure();
|
||||
const [textRef, { width: textWidth }] = useMeasure();
|
||||
const [truncatedText, setTruncatedText] = useState(text);
|
||||
const truncatedTextRef = useRef(text);
|
||||
|
||||
useEffect(() => {
|
||||
setTruncatedText(text);
|
||||
}, [text]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerWidth || !textWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (textWidth <= containerWidth) {
|
||||
return;
|
||||
}
|
||||
|
||||
const characterLength = textWidth / text.length;
|
||||
const charactersToRemove =
|
||||
Math.ceil(text.length - containerWidth / characterLength) + 3;
|
||||
let length = Math.ceil(text.length / 2 - charactersToRemove / 2);
|
||||
|
||||
let updatedText = getTruncatedText(text, length);
|
||||
|
||||
// Make sure if the text is still too long, we keep reducing the length
|
||||
// each time we re-run this.
|
||||
while (
|
||||
updatedText.length >= truncatedTextRef.current.length &&
|
||||
length > 10
|
||||
) {
|
||||
length--;
|
||||
updatedText = getTruncatedText(text, length);
|
||||
}
|
||||
|
||||
// Store the value in the ref so we can compare it in the next render,
|
||||
// without triggering this effect every time we change the text.
|
||||
truncatedTextRef.current = updatedText;
|
||||
setTruncatedText(updatedText);
|
||||
}, [text, truncatedTextRef, containerWidth, textWidth]);
|
||||
|
||||
return (
|
||||
<div ref={containerRef} style={{ whiteSpace: 'nowrap' }}>
|
||||
<div ref={textRef} style={{ display: 'inline-block' }}>
|
||||
{truncatedText}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default MiddleTruncate;
|
|
@ -1,9 +1,9 @@
|
|||
import React, { useCallback } from 'react';
|
||||
import MiddleTruncate from 'react-middle-truncate';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { Tag } from 'App/State/TagsAppState';
|
||||
import Card from 'Components/Card';
|
||||
import Label from 'Components/Label';
|
||||
import MiddleTruncate from 'Components/MiddleTruncate';
|
||||
import ConfirmModal from 'Components/Modal/ConfirmModal';
|
||||
import TagList from 'Components/TagList';
|
||||
import useModalOpenState from 'Helpers/Hooks/useModalOpenState';
|
||||
|
@ -13,14 +13,14 @@ import Indexer from 'typings/Indexer';
|
|||
import ReleaseProfile from 'typings/Settings/ReleaseProfile';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditReleaseProfileModal from './EditReleaseProfileModal';
|
||||
import styles from './ReleaseProfileRow.css';
|
||||
import styles from './ReleaseProfileItem.css';
|
||||
|
||||
interface ReleaseProfileProps extends ReleaseProfile {
|
||||
tagList: Tag[];
|
||||
indexerList: Indexer[];
|
||||
}
|
||||
|
||||
function ReleaseProfileRow(props: ReleaseProfileProps) {
|
||||
function ReleaseProfileItem(props: ReleaseProfileProps) {
|
||||
const {
|
||||
id,
|
||||
name,
|
||||
|
@ -70,7 +70,7 @@ function ReleaseProfileRow(props: ReleaseProfileProps) {
|
|||
|
||||
return (
|
||||
<Label key={item} className={styles.label} kind={kinds.SUCCESS}>
|
||||
<MiddleTruncate text={item} start={10} end={10} />
|
||||
<MiddleTruncate text={item} />
|
||||
</Label>
|
||||
);
|
||||
})}
|
||||
|
@ -84,7 +84,7 @@ function ReleaseProfileRow(props: ReleaseProfileProps) {
|
|||
|
||||
return (
|
||||
<Label key={item} className={styles.label} kind={kinds.DANGER}>
|
||||
<MiddleTruncate text={item} start={10} end={10} />
|
||||
<MiddleTruncate text={item} />
|
||||
</Label>
|
||||
);
|
||||
})}
|
||||
|
@ -128,4 +128,4 @@ function ReleaseProfileRow(props: ReleaseProfileProps) {
|
|||
);
|
||||
}
|
||||
|
||||
export default ReleaseProfileRow;
|
||||
export default ReleaseProfileItem;
|
|
@ -4,7 +4,7 @@
|
|||
}
|
||||
|
||||
.addReleaseProfile {
|
||||
composes: releaseProfile from '~./ReleaseProfileRow.css';
|
||||
composes: releaseProfile from '~./ReleaseProfileItem.css';
|
||||
|
||||
background-color: var(--cardAlternateBackgroundColor);
|
||||
color: var(--gray);
|
||||
|
|
|
@ -14,7 +14,7 @@ import createClientSideCollectionSelector from 'Store/Selectors/createClientSide
|
|||
import createTagsSelector from 'Store/Selectors/createTagsSelector';
|
||||
import translate from 'Utilities/String/translate';
|
||||
import EditReleaseProfileModal from './EditReleaseProfileModal';
|
||||
import ReleaseProfileRow from './ReleaseProfileRow';
|
||||
import ReleaseProfileItem from './ReleaseProfileItem';
|
||||
import styles from './ReleaseProfiles.css';
|
||||
|
||||
function ReleaseProfiles() {
|
||||
|
@ -59,7 +59,7 @@ function ReleaseProfiles() {
|
|||
|
||||
{items.map((item) => {
|
||||
return (
|
||||
<ReleaseProfileRow
|
||||
<ReleaseProfileItem
|
||||
key={item.id}
|
||||
tagList={tagList}
|
||||
indexerList={indexerList}
|
||||
|
|
16
frontend/typings/MiddleTruncate.d.ts
vendored
16
frontend/typings/MiddleTruncate.d.ts
vendored
|
@ -1,16 +0,0 @@
|
|||
declare module 'react-middle-truncate' {
|
||||
import { ComponentPropsWithoutRef } from 'react';
|
||||
|
||||
interface MiddleTruncateProps extends ComponentPropsWithoutRef<'div'> {
|
||||
text: string;
|
||||
ellipsis?: string;
|
||||
start?: number | RegExp | string;
|
||||
end?: number | RegExp | string;
|
||||
smartCopy?: 'all' | 'partial';
|
||||
onResizeDebounceMs?: number;
|
||||
}
|
||||
|
||||
export default function MiddleTruncate(
|
||||
props: MiddleTruncateProps
|
||||
): JSX.Element;
|
||||
}
|
|
@ -64,7 +64,6 @@
|
|||
"react-google-recaptcha": "2.1.0",
|
||||
"react-lazyload": "3.2.0",
|
||||
"react-measure": "1.4.7",
|
||||
"react-middle-truncate": "1.0.3",
|
||||
"react-popper": "1.3.7",
|
||||
"react-redux": "7.2.4",
|
||||
"react-router": "5.2.0",
|
||||
|
|
34
yarn.lock
34
yarn.lock
|
@ -2271,7 +2271,7 @@ chrome-trace-event@^1.0.2:
|
|||
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b"
|
||||
integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==
|
||||
|
||||
classnames@2.5.1, classnames@^2.2.6:
|
||||
classnames@2.5.1:
|
||||
version "2.5.1"
|
||||
resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
|
||||
integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
|
||||
|
@ -4120,11 +4120,6 @@ isexe@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
|
||||
integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
|
||||
|
||||
isnumeric@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/isnumeric/-/isnumeric-0.2.0.tgz#a2347ba360de19e33d0ffd590fddf7755cbf2e64"
|
||||
integrity sha512-uSJoAwnN1eCKDFKi8hL3UCYJSkQv+NwhKzhevUPIn/QZ8ILO21f+wQnlZHU0eh1rsLO1gI4w/HQdeOSTKwlqMg==
|
||||
|
||||
isobject@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df"
|
||||
|
@ -4464,7 +4459,7 @@ lodash.upperfirst@4.3.1:
|
|||
resolved "https://registry.yarnpkg.com/lodash.upperfirst/-/lodash.upperfirst-4.3.1.tgz#1365edf431480481ef0d1c68957a5ed99d49f7ce"
|
||||
integrity sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==
|
||||
|
||||
lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21:
|
||||
lodash@4.17.21, lodash@^4.17.14, lodash@^4.17.20, lodash@^4.17.21:
|
||||
version "4.17.21"
|
||||
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
|
||||
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
|
||||
|
@ -4787,7 +4782,7 @@ normalize-range@^0.1.2:
|
|||
resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
|
||||
integrity sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==
|
||||
|
||||
normalize.css@8.0.1, normalize.css@^8.0.0:
|
||||
normalize.css@8.0.1:
|
||||
version "8.0.1"
|
||||
resolved "https://registry.yarnpkg.com/normalize.css/-/normalize.css-8.0.1.tgz#9b98a208738b9cc2634caacbc42d131c97487bf3"
|
||||
integrity sha512-qizSNPO93t1YUuUhP22btGOo3chcvDFqFaj2TRybP0DMxkHOCTYwp3n34fel4a31ORXy4m1Xq0Gyqpb5m33qIg==
|
||||
|
@ -5491,16 +5486,6 @@ react-measure@1.4.7:
|
|||
prop-types "^15.5.4"
|
||||
resize-observer-polyfill "^1.4.1"
|
||||
|
||||
react-middle-truncate@1.0.3:
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/react-middle-truncate/-/react-middle-truncate-1.0.3.tgz#42d198ad9738bc2d8f7b8e77e11e02107b856fe1"
|
||||
integrity sha512-rBYJjSYgAvNayDk+yZz8QhQqbGLjsSZV2CuGJ4g18o6BUGlMgZ4fIOGKuIEIZj17zCXzSw7mCGAcZ4lw0y8Lgw==
|
||||
dependencies:
|
||||
classnames "^2.2.6"
|
||||
lodash "^4.17.15"
|
||||
normalize.css "^8.0.0"
|
||||
units-css "^0.4.0"
|
||||
|
||||
react-popper@1.3.7:
|
||||
version "1.3.7"
|
||||
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-1.3.7.tgz#f6a3471362ef1f0d10a4963673789de1baca2324"
|
||||
|
@ -6771,14 +6756,6 @@ unicode-property-aliases-ecmascript@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz#43d41e3be698bd493ef911077c9b131f827e8ccd"
|
||||
integrity sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==
|
||||
|
||||
units-css@^0.4.0:
|
||||
version "0.4.0"
|
||||
resolved "https://registry.yarnpkg.com/units-css/-/units-css-0.4.0.tgz#d6228653a51983d7c16ff28f8b9dc3b1ffed3a07"
|
||||
integrity sha512-WijzYC+chwzg2D6HmNGUSzPAgFRJfuxVyG9oiY28Ei5E+g6fHoPkhXUr5GV+5hE/RTHZNd9SuX2KLioYHdttoA==
|
||||
dependencies:
|
||||
isnumeric "^0.2.0"
|
||||
viewport-dimensions "^0.2.0"
|
||||
|
||||
universalify@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
|
||||
|
@ -6864,11 +6841,6 @@ value-equal@^1.0.1:
|
|||
resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-1.0.1.tgz#1e0b794c734c5c0cade179c437d356d931a34d6c"
|
||||
integrity sha512-NOJ6JZCAWr0zlxZt+xqCHNTEKOsrks2HQd4MqhP1qy4z1SkbEP467eNx6TgDKXMvUOb+OENfJCZwM+16n7fRfw==
|
||||
|
||||
viewport-dimensions@^0.2.0:
|
||||
version "0.2.0"
|
||||
resolved "https://registry.yarnpkg.com/viewport-dimensions/-/viewport-dimensions-0.2.0.tgz#de740747db5387fd1725f5175e91bac76afdf36c"
|
||||
integrity sha512-94JqlKxEP4m7WO+N3rm4tFRGXZmXXwSPQCoV+EPxDnn8YAGiLU3T+Ha1imLreAjXsHl0K+ELnIqv64i1XZHLFQ==
|
||||
|
||||
warning@^4.0.2, warning@^4.0.3:
|
||||
version "4.0.3"
|
||||
resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3"
|
||||
|
|
Loading…
Reference in a new issue