1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

Merge pull request #5226 from terite/terite-emby-linkbutton

use LinkButton instead of dangerouslySetInnerHTML
This commit is contained in:
Bill Thornton 2024-09-23 12:27:37 -04:00 committed by GitHub
commit 3a414a2da3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 87 additions and 151 deletions

View file

@ -9,7 +9,7 @@ import LibraryMenu from '../../../../scripts/libraryMenu';
import ButtonElement from '../../../../elements/ButtonElement';
import CheckBoxElement from '../../../../elements/CheckBoxElement';
import InputElement from '../../../../elements/InputElement';
import LinkEditUserPreferences from '../../../../components/dashboard/users/LinkEditUserPreferences';
import LinkButton from '../../../../elements/emby-button/LinkButton';
import SectionTitleContainer from '../../../../elements/SectionTitleContainer';
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
import loading from '../../../../components/loading/loading';
@ -38,7 +38,7 @@ function onSaveComplete() {
const UserEdit = () => {
const [ searchParams ] = useSearchParams();
const userId = searchParams.get('userId');
const [ userName, setUserName ] = useState('');
const [ userDto, setUserDto ] = useState<UserDto>();
const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState<ResetProvider[]>([]);
const [ authProviders, setAuthProviders ] = useState<NameIdPair[]>([]);
const [ passwordResetProviders, setPasswordResetProviders ] = useState<NameIdPair[]>([]);
@ -147,10 +147,8 @@ const UserEdit = () => {
txtUserName.disabled = false;
txtUserName.removeAttribute('disabled');
const lnkEditUserPreferences = page.querySelector('.lnkEditUserPreferences') as HTMLDivElement;
lnkEditUserPreferences.setAttribute('href', 'mypreferencesmenu.html?userId=' + user.Id);
LibraryMenu.setTitle(user.Name);
setUserName(user.Name || '');
setUserDto(user);
(page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name || '';
(page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = !!user.Policy?.IsAdministrator;
(page.querySelector('.chkDisabled') as HTMLInputElement).checked = !!user.Policy?.IsDisabled;
@ -292,7 +290,7 @@ const UserEdit = () => {
<div ref={element} className='content-primary'>
<div className='verticalSection'>
<SectionTitleContainer
title={userName}
title={userDto?.Name || ''}
url='https://jellyfin.org/docs/general/server/users/'
/>
</div>
@ -302,10 +300,9 @@ const UserEdit = () => {
className='lnkEditUserPreferencesContainer'
style={{ paddingBottom: '1em' }}
>
<LinkEditUserPreferences
className= 'lnkEditUserPreferences button-link'
title= 'ButtonEditOtherUserPreferences'
/>
<LinkButton className='lnkEditUserPreferences button-link' href={userDto?.Id ? `mypreferencesmenu.html?userId=${userDto.Id}` : undefined}>
{globalize.translate('ButtonEditOtherUserPreferences')}
</LinkButton>
</div>
<form className='editUserProfileForm'>
<div className='disabledUserBanner hide'>

View file

@ -1,30 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from 'lib/globalize';
type IProps = {
title?: string;
className?: string;
};
const createLinkElement = ({ className, title }: IProps) => ({
__html: `<a
is="emby-linkbutton"
class="${className}"
href='#'
>
${title}
</a>`
});
const LinkEditUserPreferences: FunctionComponent<IProps> = ({ className, title }: IProps) => {
return (
<div
dangerouslySetInnerHTML={createLinkElement({
className: className,
title: globalize.translate(title)
})}
/>
);
};
export default LinkEditUserPreferences;

View file

@ -1,49 +1,60 @@
import React, { FunctionComponent } from 'react';
import globalize from 'lib/globalize';
import { navigate } from '../../../utils/dashboard';
import LinkButton from '../../../elements/emby-button/LinkButton';
type IProps = {
activeTab: string;
};
const createLinkElement = (activeTab: string) => ({
__html: `<a href="#"
is="emby-linkbutton"
data-role="button"
class="${activeTab === 'useredit' ? 'ui-btn-active' : ''}"
onclick="Dashboard.navigate('/dashboard/users/profile', true);">
${globalize.translate('Profile')}
</a>
<a href="#"
is="emby-linkbutton"
data-role="button"
class="${activeTab === 'userlibraryaccess' ? 'ui-btn-active' : ''}"
onclick="Dashboard.navigate('/dashboard/users/access', true);">
${globalize.translate('TabAccess')}
</a>
<a href="#"
is="emby-linkbutton"
data-role="button"
class="${activeTab === 'userparentalcontrol' ? 'ui-btn-active' : ''}"
onclick="Dashboard.navigate('/dashboard/users/parentalcontrol', true);">
${globalize.translate('TabParentalControl')}
</a>
<a href="#"
is="emby-linkbutton"
data-role="button"
class="${activeTab === 'userpassword' ? 'ui-btn-active' : ''}"
onclick="Dashboard.navigate('/dashboard/users/password', true);">
${globalize.translate('HeaderPassword')}
</a>`
function useNavigate(url: string): () => void {
return React.useCallback(() => {
navigate(url, true).catch(err => {
console.warn('Error navigating to dashboard url', err);
});
}, [url]);
}
const SectionTabs: FunctionComponent<IProps> = ({ activeTab }: IProps) => {
const onClickProfile = useNavigate('/dashboard/users/profile');
const onClickAccess = useNavigate('/dashboard/users/access');
const onClickParentalControl = useNavigate('/dashboard/users/parentalcontrol');
const clickPassword = useNavigate('/dashboard/users/password');
return (
<div
data-role='controlgroup'
data-type='horizontal'
className='localnav'
dangerouslySetInnerHTML={createLinkElement(activeTab)}
/>
className='localnav'>
<LinkButton
href='#'
data-role='button'
className={activeTab === 'useredit' ? 'ui-btn-active' : ''}
onClick={onClickProfile}>
{globalize.translate('Profile')}
</LinkButton>
<LinkButton
href='#'
data-role='button'
className={activeTab === 'userlibraryaccess' ? 'ui-btn-active' : ''}
onClick={onClickAccess}>
{globalize.translate('TabAccess')}
</LinkButton>
<LinkButton
href='#'
data-role='button'
className={activeTab === 'userparentalcontrol' ? 'ui-btn-active' : ''}
onClick={onClickParentalControl}>
{globalize.translate('TabParentalControl')}
</LinkButton>
<LinkButton
href='#'
data-role='button'
className={activeTab === 'userpassword' ? 'ui-btn-active' : ''}
onClick={clickPassword}>
{globalize.translate('HeaderPassword')}
</LinkButton>
</div>
);
};

View file

@ -4,19 +4,9 @@ import { formatDistanceToNow } from 'date-fns';
import { getLocaleWithSuffix } from '../../../utils/dateFnsLocale';
import globalize from '../../../lib/globalize';
import IconButtonElement from '../../../elements/IconButtonElement';
import escapeHTML from 'escape-html';
import LinkButton from '../../../elements/emby-button/LinkButton';
import { getDefaultBackgroundClass } from '../../cardbuilder/cardBuilderUtils';
const createLinkElement = ({ user, renderImgUrl }: { user: UserDto, renderImgUrl: string }) => ({
__html: `<a
is="emby-linkbutton"
class="cardContent"
href="#/dashboard/users/profile?userId=${user.Id}"
>
${renderImgUrl}
</a>`
});
type IProps = {
user?: UserDto;
};
@ -55,22 +45,21 @@ const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
const lastSeen = getLastSeenText(user.LastActivityDate);
const renderImgUrl = imgUrl ?
`<div class='${imageClass}' style='background-image:url(${imgUrl})'></div>` :
`<div class='${imageClass} ${getDefaultBackgroundClass(user.Name)} flex align-items-center justify-content-center'>
<span class='material-icons cardImageIcon person' aria-hidden='true'></span>
</div>`;
<div className={imageClass} style={{ backgroundImage: `url(${imgUrl})` }} /> :
<div className={`${imageClass} ${getDefaultBackgroundClass(user.Name)} flex align-items-center justify-content-center`}>
<span className='material-icons cardImageIcon person' aria-hidden='true'></span>
</div>;
return (
<div data-userid={user.Id} data-username={user.Name} className={cssClass}>
<div className='cardBox visualCardBox'>
<div className='cardScalable visualCardBox-cardScalable'>
<div className='cardPadder cardPadder-square'></div>
<div
dangerouslySetInnerHTML={createLinkElement({
user: user,
renderImgUrl: renderImgUrl
})}
/>
<LinkButton
className='cardContent'
href={`#/dashboard/users/profile?userId=${user.Id}`}>
{renderImgUrl}
</LinkButton>
</div>
<div className='cardFooter visualCardBox-cardFooter'>
<div
@ -83,7 +72,7 @@ const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
/>
</div>
<div className='cardText'>
<span>{escapeHTML(user.Name)}</span>
<span>{user.Name}</span>
</div>
<div className='cardText cardText-secondary'>
<span>{lastSeen != '' ? lastSeen : ''}</span>

View file

@ -1,17 +1,19 @@
import React, { type FC } from 'react';
import { useSearchSuggestions } from 'hooks/searchHook';
import React, { FunctionComponent } from 'react';
import Loading from 'components/loading/LoadingComponent';
import { appRouter } from '../router/appRouter';
import globalize from '../../lib/globalize';
import LinkButton from 'elements/emby-button/LinkButton';
import { useSearchSuggestions } from 'hooks/searchHook/useSearchSuggestions';
import globalize from 'lib/globalize';
import LinkButton from '../../elements/emby-button/LinkButton';
import '../../elements/emby-button/emby-button';
interface SearchSuggestionsProps {
parentId?: string;
}
type SearchSuggestionsProps = {
parentId?: string | null;
};
const SearchSuggestions: FC<SearchSuggestionsProps> = ({ parentId }) => {
const { isLoading, data: suggestions } = useSearchSuggestions(parentId);
const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ parentId }) => {
const { isLoading, data: suggestions } = useSearchSuggestions(parentId || undefined);
if (isLoading) return <Loading />;
@ -27,15 +29,12 @@ const SearchSuggestions: FC<SearchSuggestionsProps> = ({ parentId }) => {
</div>
<div className='searchSuggestionsList padded-left padded-right'>
{suggestions?.map((item) => (
<div key={`suggestion-${item.Id}`}>
{suggestions?.map(item => (
<div key={item.Id}>
<LinkButton
className='button-link'
style={{ display: 'inline-block', padding: '0.5em 1em' }}
href={appRouter.getRouteUrl(item)}
style={{
display: 'inline-block',
padding: '0.5em 1em'
}}
>
{item.Name}
</LinkButton>

View file

@ -1,6 +1,7 @@
import React, { FunctionComponent } from 'react';
import IconButtonElement from './IconButtonElement';
import SectionTitleLinkElement from './SectionTitleLinkElement';
import LinkButton from './emby-button/LinkButton';
import globalize from 'lib/globalize';
type IProps = {
SectionClassName?: string;
@ -28,11 +29,13 @@ const SectionTitleContainer: FunctionComponent<IProps> = ({ SectionClassName, ti
icon={btnIcon}
/>}
{isLinkVisible && <SectionTitleLinkElement
{isLinkVisible && <LinkButton
className='raised button-alt headerHelpButton'
title='Help'
url={url}
/>}
target='_blank'
rel='noopener noreferrer'
href={url}>
{globalize.translate('Help')}
</LinkButton>}
</div>
);

View file

@ -1,35 +0,0 @@
import React, { FunctionComponent } from 'react';
import globalize from 'lib/globalize';
const createLinkElement = ({ className, title, href }: { className?: string, title?: string, href?: string }) => ({
__html: `<a
is="emby-linkbutton"
rel="noopener noreferrer"
class="${className}"
target="_blank"
href="${href}"
>
${title}
</a>`
});
type IProps = {
title?: string;
className?: string;
url?: string
};
const SectionTitleLinkElement: FunctionComponent<IProps> = ({ className, title, url }: IProps) => {
return (
<div
dangerouslySetInnerHTML={createLinkElement({
className: className,
title: globalize.translate(title),
href: url
})}
/>
);
};
export default SectionTitleLinkElement;

View file

@ -20,6 +20,7 @@ const LinkButton: React.FC<LinkButtonProps> = ({
isAutoHideEnabled,
href,
target,
onClick,
children,
...rest
}) => {
@ -41,7 +42,8 @@ const LinkButton: React.FC<LinkButtonProps> = ({
} else {
e.preventDefault();
}
}, [ href, target ]);
onClick?.(e);
}, [ href, target, onClick ]);
if (isAutoHideEnabled === true && !appHost.supports('externallinks')) {
return null;