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 ButtonElement from '../../../../elements/ButtonElement';
|
||||||
import CheckBoxElement from '../../../../elements/CheckBoxElement';
|
import CheckBoxElement from '../../../../elements/CheckBoxElement';
|
||||||
import InputElement from '../../../../elements/InputElement';
|
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 SectionTitleContainer from '../../../../elements/SectionTitleContainer';
|
||||||
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
|
import SectionTabs from '../../../../components/dashboard/users/SectionTabs';
|
||||||
import loading from '../../../../components/loading/loading';
|
import loading from '../../../../components/loading/loading';
|
||||||
|
@ -38,7 +38,7 @@ function onSaveComplete() {
|
||||||
const UserEdit = () => {
|
const UserEdit = () => {
|
||||||
const [ searchParams ] = useSearchParams();
|
const [ searchParams ] = useSearchParams();
|
||||||
const userId = searchParams.get('userId');
|
const userId = searchParams.get('userId');
|
||||||
const [ userName, setUserName ] = useState('');
|
const [ userDto, setUserDto ] = useState<UserDto>();
|
||||||
const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState<ResetProvider[]>([]);
|
const [ deleteFoldersAccess, setDeleteFoldersAccess ] = useState<ResetProvider[]>([]);
|
||||||
const [ authProviders, setAuthProviders ] = useState<NameIdPair[]>([]);
|
const [ authProviders, setAuthProviders ] = useState<NameIdPair[]>([]);
|
||||||
const [ passwordResetProviders, setPasswordResetProviders ] = useState<NameIdPair[]>([]);
|
const [ passwordResetProviders, setPasswordResetProviders ] = useState<NameIdPair[]>([]);
|
||||||
|
@ -147,10 +147,8 @@ const UserEdit = () => {
|
||||||
txtUserName.disabled = false;
|
txtUserName.disabled = false;
|
||||||
txtUserName.removeAttribute('disabled');
|
txtUserName.removeAttribute('disabled');
|
||||||
|
|
||||||
const lnkEditUserPreferences = page.querySelector('.lnkEditUserPreferences') as HTMLDivElement;
|
|
||||||
lnkEditUserPreferences.setAttribute('href', 'mypreferencesmenu.html?userId=' + user.Id);
|
|
||||||
LibraryMenu.setTitle(user.Name);
|
LibraryMenu.setTitle(user.Name);
|
||||||
setUserName(user.Name || '');
|
setUserDto(user);
|
||||||
(page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name || '';
|
(page.querySelector('#txtUserName') as HTMLInputElement).value = user.Name || '';
|
||||||
(page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = !!user.Policy?.IsAdministrator;
|
(page.querySelector('.chkIsAdmin') as HTMLInputElement).checked = !!user.Policy?.IsAdministrator;
|
||||||
(page.querySelector('.chkDisabled') as HTMLInputElement).checked = !!user.Policy?.IsDisabled;
|
(page.querySelector('.chkDisabled') as HTMLInputElement).checked = !!user.Policy?.IsDisabled;
|
||||||
|
@ -292,7 +290,7 @@ const UserEdit = () => {
|
||||||
<div ref={element} className='content-primary'>
|
<div ref={element} className='content-primary'>
|
||||||
<div className='verticalSection'>
|
<div className='verticalSection'>
|
||||||
<SectionTitleContainer
|
<SectionTitleContainer
|
||||||
title={userName}
|
title={userDto?.Name || ''}
|
||||||
url='https://jellyfin.org/docs/general/server/users/'
|
url='https://jellyfin.org/docs/general/server/users/'
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -302,10 +300,9 @@ const UserEdit = () => {
|
||||||
className='lnkEditUserPreferencesContainer'
|
className='lnkEditUserPreferencesContainer'
|
||||||
style={{ paddingBottom: '1em' }}
|
style={{ paddingBottom: '1em' }}
|
||||||
>
|
>
|
||||||
<LinkEditUserPreferences
|
<LinkButton className='lnkEditUserPreferences button-link' href={userDto?.Id ? `mypreferencesmenu.html?userId=${userDto.Id}` : undefined}>
|
||||||
className= 'lnkEditUserPreferences button-link'
|
{globalize.translate('ButtonEditOtherUserPreferences')}
|
||||||
title= 'ButtonEditOtherUserPreferences'
|
</LinkButton>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<form className='editUserProfileForm'>
|
<form className='editUserProfileForm'>
|
||||||
<div className='disabledUserBanner hide'>
|
<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 React, { FunctionComponent } from 'react';
|
||||||
|
|
||||||
import globalize from 'lib/globalize';
|
import globalize from 'lib/globalize';
|
||||||
|
import { navigate } from '../../../utils/dashboard';
|
||||||
|
import LinkButton from '../../../elements/emby-button/LinkButton';
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
activeTab: string;
|
activeTab: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createLinkElement = (activeTab: string) => ({
|
function useNavigate(url: string): () => void {
|
||||||
__html: `<a href="#"
|
return React.useCallback(() => {
|
||||||
is="emby-linkbutton"
|
navigate(url, true).catch(err => {
|
||||||
data-role="button"
|
console.warn('Error navigating to dashboard url', err);
|
||||||
class="${activeTab === 'useredit' ? 'ui-btn-active' : ''}"
|
});
|
||||||
onclick="Dashboard.navigate('/dashboard/users/profile', true);">
|
}, [url]);
|
||||||
${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>`
|
|
||||||
});
|
|
||||||
|
|
||||||
const SectionTabs: FunctionComponent<IProps> = ({ activeTab }: IProps) => {
|
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 (
|
return (
|
||||||
<div
|
<div
|
||||||
data-role='controlgroup'
|
data-role='controlgroup'
|
||||||
data-type='horizontal'
|
data-type='horizontal'
|
||||||
className='localnav'
|
className='localnav'>
|
||||||
dangerouslySetInnerHTML={createLinkElement(activeTab)}
|
<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 { getLocaleWithSuffix } from '../../../utils/dateFnsLocale';
|
||||||
import globalize from '../../../lib/globalize';
|
import globalize from '../../../lib/globalize';
|
||||||
import IconButtonElement from '../../../elements/IconButtonElement';
|
import IconButtonElement from '../../../elements/IconButtonElement';
|
||||||
import escapeHTML from 'escape-html';
|
import LinkButton from '../../../elements/emby-button/LinkButton';
|
||||||
import { getDefaultBackgroundClass } from '../../cardbuilder/cardBuilderUtils';
|
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 = {
|
type IProps = {
|
||||||
user?: UserDto;
|
user?: UserDto;
|
||||||
};
|
};
|
||||||
|
@ -55,22 +45,21 @@ const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
|
||||||
const lastSeen = getLastSeenText(user.LastActivityDate);
|
const lastSeen = getLastSeenText(user.LastActivityDate);
|
||||||
|
|
||||||
const renderImgUrl = imgUrl ?
|
const renderImgUrl = imgUrl ?
|
||||||
`<div class='${imageClass}' style='background-image:url(${imgUrl})'></div>` :
|
<div className={imageClass} style={{ backgroundImage: `url(${imgUrl})` }} /> :
|
||||||
`<div class='${imageClass} ${getDefaultBackgroundClass(user.Name)} flex align-items-center justify-content-center'>
|
<div className={`${imageClass} ${getDefaultBackgroundClass(user.Name)} flex align-items-center justify-content-center`}>
|
||||||
<span class='material-icons cardImageIcon person' aria-hidden='true'></span>
|
<span className='material-icons cardImageIcon person' aria-hidden='true'></span>
|
||||||
</div>`;
|
</div>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div data-userid={user.Id} data-username={user.Name} className={cssClass}>
|
<div data-userid={user.Id} data-username={user.Name} className={cssClass}>
|
||||||
<div className='cardBox visualCardBox'>
|
<div className='cardBox visualCardBox'>
|
||||||
<div className='cardScalable visualCardBox-cardScalable'>
|
<div className='cardScalable visualCardBox-cardScalable'>
|
||||||
<div className='cardPadder cardPadder-square'></div>
|
<div className='cardPadder cardPadder-square'></div>
|
||||||
<div
|
<LinkButton
|
||||||
dangerouslySetInnerHTML={createLinkElement({
|
className='cardContent'
|
||||||
user: user,
|
href={`#/dashboard/users/profile?userId=${user.Id}`}>
|
||||||
renderImgUrl: renderImgUrl
|
{renderImgUrl}
|
||||||
})}
|
</LinkButton>
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className='cardFooter visualCardBox-cardFooter'>
|
<div className='cardFooter visualCardBox-cardFooter'>
|
||||||
<div
|
<div
|
||||||
|
@ -83,7 +72,7 @@ const UserCardBox: FunctionComponent<IProps> = ({ user = {} }: IProps) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='cardText'>
|
<div className='cardText'>
|
||||||
<span>{escapeHTML(user.Name)}</span>
|
<span>{user.Name}</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='cardText cardText-secondary'>
|
<div className='cardText cardText-secondary'>
|
||||||
<span>{lastSeen != '' ? lastSeen : ''}</span>
|
<span>{lastSeen != '' ? lastSeen : ''}</span>
|
||||||
|
|
|
@ -1,17 +1,19 @@
|
||||||
import React, { type FC } from 'react';
|
import React, { FunctionComponent } from 'react';
|
||||||
import { useSearchSuggestions } from 'hooks/searchHook';
|
|
||||||
import Loading from 'components/loading/LoadingComponent';
|
import Loading from 'components/loading/LoadingComponent';
|
||||||
import { appRouter } from '../router/appRouter';
|
import { appRouter } from '../router/appRouter';
|
||||||
import globalize from '../../lib/globalize';
|
import { useSearchSuggestions } from 'hooks/searchHook/useSearchSuggestions';
|
||||||
import LinkButton from 'elements/emby-button/LinkButton';
|
import globalize from 'lib/globalize';
|
||||||
|
import LinkButton from '../../elements/emby-button/LinkButton';
|
||||||
|
|
||||||
import '../../elements/emby-button/emby-button';
|
import '../../elements/emby-button/emby-button';
|
||||||
|
|
||||||
interface SearchSuggestionsProps {
|
type SearchSuggestionsProps = {
|
||||||
parentId?: string;
|
parentId?: string | null;
|
||||||
}
|
};
|
||||||
|
|
||||||
const SearchSuggestions: FC<SearchSuggestionsProps> = ({ parentId }) => {
|
const SearchSuggestions: FunctionComponent<SearchSuggestionsProps> = ({ parentId }) => {
|
||||||
const { isLoading, data: suggestions } = useSearchSuggestions(parentId);
|
const { isLoading, data: suggestions } = useSearchSuggestions(parentId || undefined);
|
||||||
|
|
||||||
if (isLoading) return <Loading />;
|
if (isLoading) return <Loading />;
|
||||||
|
|
||||||
|
@ -27,15 +29,12 @@ const SearchSuggestions: FC<SearchSuggestionsProps> = ({ parentId }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className='searchSuggestionsList padded-left padded-right'>
|
<div className='searchSuggestionsList padded-left padded-right'>
|
||||||
{suggestions?.map((item) => (
|
{suggestions?.map(item => (
|
||||||
<div key={`suggestion-${item.Id}`}>
|
<div key={item.Id}>
|
||||||
<LinkButton
|
<LinkButton
|
||||||
className='button-link'
|
className='button-link'
|
||||||
|
style={{ display: 'inline-block', padding: '0.5em 1em' }}
|
||||||
href={appRouter.getRouteUrl(item)}
|
href={appRouter.getRouteUrl(item)}
|
||||||
style={{
|
|
||||||
display: 'inline-block',
|
|
||||||
padding: '0.5em 1em'
|
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
{item.Name}
|
{item.Name}
|
||||||
</LinkButton>
|
</LinkButton>
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import React, { FunctionComponent } from 'react';
|
import React, { FunctionComponent } from 'react';
|
||||||
import IconButtonElement from './IconButtonElement';
|
import IconButtonElement from './IconButtonElement';
|
||||||
import SectionTitleLinkElement from './SectionTitleLinkElement';
|
import LinkButton from './emby-button/LinkButton';
|
||||||
|
import globalize from 'lib/globalize';
|
||||||
|
|
||||||
type IProps = {
|
type IProps = {
|
||||||
SectionClassName?: string;
|
SectionClassName?: string;
|
||||||
|
@ -28,11 +29,13 @@ const SectionTitleContainer: FunctionComponent<IProps> = ({ SectionClassName, ti
|
||||||
icon={btnIcon}
|
icon={btnIcon}
|
||||||
/>}
|
/>}
|
||||||
|
|
||||||
{isLinkVisible && <SectionTitleLinkElement
|
{isLinkVisible && <LinkButton
|
||||||
className='raised button-alt headerHelpButton'
|
className='raised button-alt headerHelpButton'
|
||||||
title='Help'
|
target='_blank'
|
||||||
url={url}
|
rel='noopener noreferrer'
|
||||||
/>}
|
href={url}>
|
||||||
|
{globalize.translate('Help')}
|
||||||
|
</LinkButton>}
|
||||||
|
|
||||||
</div>
|
</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,
|
isAutoHideEnabled,
|
||||||
href,
|
href,
|
||||||
target,
|
target,
|
||||||
|
onClick,
|
||||||
children,
|
children,
|
||||||
...rest
|
...rest
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -41,7 +42,8 @@ const LinkButton: React.FC<LinkButtonProps> = ({
|
||||||
} else {
|
} else {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
}
|
}
|
||||||
}, [ href, target ]);
|
onClick?.(e);
|
||||||
|
}, [ href, target, onClick ]);
|
||||||
|
|
||||||
if (isAutoHideEnabled === true && !appHost.supports('externallinks')) {
|
if (isAutoHideEnabled === true && !appHost.supports('externallinks')) {
|
||||||
return null;
|
return null;
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue