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:
commit
3a414a2da3
8 changed files with 87 additions and 151 deletions
|
@ -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'>
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
);
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue