WIP: Accessibility of web app
This commit is contained in:
		
							parent
							
								
									4a8678bf39
								
							
						
					
					
						commit
						bb5e0e3fed
					
				
					 9 changed files with 170 additions and 77 deletions
				
			
		| 
						 | 
				
			
			@ -44,16 +44,22 @@ const ActionBar = (props) => {
 | 
			
		|||
                <IconButton
 | 
			
		||||
                    color="inherit"
 | 
			
		||||
                    edge="start"
 | 
			
		||||
                    aria-label={t("action_bar_show_menu")}
 | 
			
		||||
                    onClick={props.onMobileDrawerToggle}
 | 
			
		||||
                    sx={{ mr: 2, display: { sm: 'none' } }}
 | 
			
		||||
                >
 | 
			
		||||
                    <MenuIcon />
 | 
			
		||||
                </IconButton>
 | 
			
		||||
                <Box component="img" src={logo} sx={{
 | 
			
		||||
                    display: { xs: 'none', sm: 'block' },
 | 
			
		||||
                    marginRight: '10px',
 | 
			
		||||
                    height: '28px'
 | 
			
		||||
                }}/>
 | 
			
		||||
                <Box
 | 
			
		||||
                    component="img"
 | 
			
		||||
                    src={logo}
 | 
			
		||||
                    alt={t("action_bar_logo_alt")}
 | 
			
		||||
                    sx={{
 | 
			
		||||
                        display: { xs: 'none', sm: 'block' },
 | 
			
		||||
                        marginRight: '10px',
 | 
			
		||||
                        height: '28px'
 | 
			
		||||
                    }}
 | 
			
		||||
                />
 | 
			
		||||
                <Typography variant="h6" noWrap component="div" sx={{ flexGrow: 1 }}>
 | 
			
		||||
                    {title}
 | 
			
		||||
                </Typography>
 | 
			
		||||
| 
						 | 
				
			
			@ -173,10 +179,10 @@ const SettingsIcons = (props) => {
 | 
			
		|||
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
            <IconButton color="inherit" size="large" edge="end" onClick={handleToggleMute} sx={{marginRight: 0}}>
 | 
			
		||||
            <IconButton color="inherit" size="large" edge="end" onClick={handleToggleMute} sx={{marginRight: 0}} aria-label={t("action_bar_toggle_mute")}>
 | 
			
		||||
                {subscription.mutedUntil ? <NotificationsOffIcon/> : <NotificationsIcon/>}
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <IconButton color="inherit" size="large" edge="end" ref={anchorRef} onClick={handleToggleOpen}>
 | 
			
		||||
            <IconButton color="inherit" size="large" edge="end" ref={anchorRef} onClick={handleToggleOpen} aria-label={t("action_bar_toggle_action_menu")}>
 | 
			
		||||
                <MoreVertIcon/>
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Popper
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -5,27 +5,36 @@ import fileImage from "../img/file-image.svg";
 | 
			
		|||
import fileVideo from "../img/file-video.svg";
 | 
			
		||||
import fileAudio from "../img/file-audio.svg";
 | 
			
		||||
import fileApp from "../img/file-app.svg";
 | 
			
		||||
import {useTranslation} from "react-i18next";
 | 
			
		||||
 | 
			
		||||
const AttachmentIcon = (props) => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const type = props.type;
 | 
			
		||||
    let imageFile;
 | 
			
		||||
    let imageFile, imageLabel;
 | 
			
		||||
    if (!type) {
 | 
			
		||||
        imageFile = fileDocument;
 | 
			
		||||
        imageLabel = t("notifications_attachment_file_image");
 | 
			
		||||
    } else if (type.startsWith('image/')) {
 | 
			
		||||
        imageFile = fileImage;
 | 
			
		||||
        imageLabel = t("notifications_attachment_file_video");
 | 
			
		||||
    } else if (type.startsWith('video/')) {
 | 
			
		||||
        imageFile = fileVideo;
 | 
			
		||||
        imageLabel = t("notifications_attachment_file_video");
 | 
			
		||||
    } else if (type.startsWith('audio/')) {
 | 
			
		||||
        imageFile = fileAudio;
 | 
			
		||||
        imageLabel = t("notifications_attachment_file_audio");
 | 
			
		||||
    } else if (type === "application/vnd.android.package-archive") {
 | 
			
		||||
        imageFile = fileApp;
 | 
			
		||||
        imageLabel = t("notifications_attachment_file_app");
 | 
			
		||||
    } else {
 | 
			
		||||
        imageFile = fileDocument;
 | 
			
		||||
        imageLabel = t("notifications_attachment_file_document");
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <Box
 | 
			
		||||
            component="img"
 | 
			
		||||
            src={imageFile}
 | 
			
		||||
            alt={imageLabel}
 | 
			
		||||
            loading="lazy"
 | 
			
		||||
            sx={{
 | 
			
		||||
                width: '28px',
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -73,6 +73,8 @@ const EmojiPicker = (props) => {
 | 
			
		|||
                                inputRef={searchRef}
 | 
			
		||||
                                margin="dense"
 | 
			
		||||
                                size="small"
 | 
			
		||||
                                role="searchbox" 
 | 
			
		||||
                                aria-label={t("emoji_picker_search_placeholder")}
 | 
			
		||||
                                placeholder={t("emoji_picker_search_placeholder")}
 | 
			
		||||
                                value={search}
 | 
			
		||||
                                onChange={ev => setSearch(ev.target.value)}
 | 
			
		||||
| 
						 | 
				
			
			@ -83,7 +85,9 @@ const EmojiPicker = (props) => {
 | 
			
		|||
                                InputProps={{
 | 
			
		||||
                                    endAdornment:
 | 
			
		||||
                                        <InputAdornment position="end" sx={{ display: (search) ? '' : 'none' }}>
 | 
			
		||||
                                            <IconButton size="small" onClick={handleSearchClear} edge="end"><Close/></IconButton>
 | 
			
		||||
                                            <IconButton size="small" onClick={handleSearchClear} edge="end" aria-label={t("emoji_picker_search_clear")}>
 | 
			
		||||
                                                <Close/>
 | 
			
		||||
                                            </IconButton>
 | 
			
		||||
                                        </InputAdornment>
 | 
			
		||||
                                }}
 | 
			
		||||
                            />
 | 
			
		||||
| 
						 | 
				
			
			@ -130,10 +134,12 @@ const Category = (props) => {
 | 
			
		|||
const Emoji = (props) => {
 | 
			
		||||
    const emoji = props.emoji;
 | 
			
		||||
    const matches = emojiMatches(emoji, props.search);
 | 
			
		||||
    const title = `${emoji.description} (${emoji.aliases[0]})`;
 | 
			
		||||
    return (
 | 
			
		||||
        <EmojiDiv
 | 
			
		||||
            onClick={props.onClick}
 | 
			
		||||
            title={`${emoji.description} (${emoji.aliases[0]})`}
 | 
			
		||||
            title={title}
 | 
			
		||||
            aria-label={title}
 | 
			
		||||
            style={{ display: (matches) ? '' : 'none' }}
 | 
			
		||||
        >
 | 
			
		||||
            {props.emoji.emoji}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -75,13 +75,15 @@ const MessageBar = (props) => {
 | 
			
		|||
                backgroundColor: (theme) => theme.palette.mode === 'light' ? theme.palette.grey[100] : theme.palette.grey[900]
 | 
			
		||||
            }}
 | 
			
		||||
        >
 | 
			
		||||
            <IconButton color="inherit" size="large" edge="start" onClick={props.onOpenDialogClick}>
 | 
			
		||||
            <IconButton color="inherit" size="large" edge="start" onClick={props.onOpenDialogClick} aria-label={t("message_bar_show_dialog")}>
 | 
			
		||||
                <KeyboardArrowUpIcon/>
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <TextField
 | 
			
		||||
                autoFocus
 | 
			
		||||
                margin="dense"
 | 
			
		||||
                placeholder={t("message_bar_type_message")}
 | 
			
		||||
                aria-label={t("message_bar_type_message")}
 | 
			
		||||
                role="textbox" 
 | 
			
		||||
                type="text"
 | 
			
		||||
                fullWidth
 | 
			
		||||
                variant="standard"
 | 
			
		||||
| 
						 | 
				
			
			@ -94,7 +96,7 @@ const MessageBar = (props) => {
 | 
			
		|||
                    }
 | 
			
		||||
                }}
 | 
			
		||||
            />
 | 
			
		||||
            <IconButton color="inherit" size="large" edge="end" onClick={handleSendClick}>
 | 
			
		||||
            <IconButton color="inherit" size="large" edge="end" onClick={handleSendClick} aria-label={t("message_bar_publish")}>
 | 
			
		||||
                <SendIcon/>
 | 
			
		||||
            </IconButton>
 | 
			
		||||
            <Portal>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -31,10 +31,15 @@ const navWidth = 280;
 | 
			
		|||
const Navigation = (props) => {
 | 
			
		||||
    const navigationList = <NavList {...props}/>;
 | 
			
		||||
    return (
 | 
			
		||||
        <Box component="nav" sx={{width: {sm: Navigation.width}, flexShrink: {sm: 0}}}>
 | 
			
		||||
        <Box
 | 
			
		||||
            component="nav"
 | 
			
		||||
            role="navigation" 
 | 
			
		||||
            sx={{width: {sm: Navigation.width}, flexShrink: {sm: 0}}}
 | 
			
		||||
        >
 | 
			
		||||
            {/* Mobile drawer; only shown if menu icon clicked (mobile open) and display is small */}
 | 
			
		||||
            <Drawer
 | 
			
		||||
                variant="temporary"
 | 
			
		||||
                role="menubar" 
 | 
			
		||||
                open={props.mobileDrawerOpen}
 | 
			
		||||
                onClose={props.onMobileDrawerToggle}
 | 
			
		||||
                ModalProps={{ keepMounted: true }} // Better open performance on mobile.
 | 
			
		||||
| 
						 | 
				
			
			@ -49,6 +54,7 @@ const Navigation = (props) => {
 | 
			
		|||
            <Drawer
 | 
			
		||||
                open
 | 
			
		||||
                variant="permanent"
 | 
			
		||||
                role="menubar" 
 | 
			
		||||
                sx={{
 | 
			
		||||
                    display: { xs: 'none', sm: 'block' },
 | 
			
		||||
                    '& .MuiDrawer-paper': { boxSizing: 'border-box', width: navWidth },
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -98,6 +98,8 @@ const NotificationList = (props) => {
 | 
			
		|||
        >
 | 
			
		||||
            <Container
 | 
			
		||||
                maxWidth="md"
 | 
			
		||||
                role="list" 
 | 
			
		||||
                aria-label={t("notifications_list")}
 | 
			
		||||
                sx={{
 | 
			
		||||
                    marginTop: 3,
 | 
			
		||||
                    marginBottom: (props.messageBar) ? "100px" : 3 // Hack to avoid hiding notifications behind the message bar
 | 
			
		||||
| 
						 | 
				
			
			@ -143,9 +145,9 @@ const NotificationItem = (props) => {
 | 
			
		|||
    const hasUserActions = notification.actions && notification.actions.length > 0;
 | 
			
		||||
    const showActions = hasAttachmentActions || hasClickAction || hasUserActions;
 | 
			
		||||
    return (
 | 
			
		||||
        <Card sx={{ minWidth: 275, padding: 1 }}>
 | 
			
		||||
        <Card sx={{ minWidth: 275, padding: 1 }} role="listitem" aria-label={t("notifications_list_item")}>
 | 
			
		||||
            <CardContent>
 | 
			
		||||
                <IconButton onClick={handleDelete} sx={{ float: 'right', marginRight: -1, marginTop: -1 }}>
 | 
			
		||||
                <IconButton onClick={handleDelete} sx={{ float: 'right', marginRight: -1, marginTop: -1 }} aria-label={t("notifications_delete")}>
 | 
			
		||||
                    <CloseIcon />
 | 
			
		||||
                </IconButton>
 | 
			
		||||
                <Typography sx={{ fontSize: 14 }} color="text.secondary">
 | 
			
		||||
| 
						 | 
				
			
			@ -153,15 +155,15 @@ const NotificationItem = (props) => {
 | 
			
		|||
                    {[1,2,4,5].includes(notification.priority) &&
 | 
			
		||||
                        <img
 | 
			
		||||
                            src={priorityFiles[notification.priority]}
 | 
			
		||||
                            alt={`Priority ${notification.priority}`}
 | 
			
		||||
                            alt={t("notifications_priority_x", { priority: notification.priority})}
 | 
			
		||||
                            style={{ verticalAlign: 'bottom' }}
 | 
			
		||||
                        />}
 | 
			
		||||
                    {notification.new === 1 &&
 | 
			
		||||
                        <svg style={{ width: '8px', height: '8px', marginLeft: '4px' }} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
 | 
			
		||||
                        <svg style={{ width: '8px', height: '8px', marginLeft: '4px' }} viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg" aria-label={t("notifications_new_indicator")}>
 | 
			
		||||
                            <circle cx="50" cy="50" r="50" fill="#338574"/>
 | 
			
		||||
                        </svg>}
 | 
			
		||||
                </Typography>
 | 
			
		||||
                {notification.title && <Typography variant="h5" component="div">{formatTitle(notification)}</Typography>}
 | 
			
		||||
                {notification.title && <Typography variant="h5" component="div" role="rowheader">{formatTitle(notification)}</Typography>}
 | 
			
		||||
                <Typography variant="body1" sx={{ whiteSpace: 'pre-line' }}>
 | 
			
		||||
                    {autolink(maybeAppendActionErrors(formatMessage(notification), notification))}
 | 
			
		||||
                </Typography>
 | 
			
		||||
| 
						 | 
				
			
			@ -289,6 +291,7 @@ const Attachment = (props) => {
 | 
			
		|||
};
 | 
			
		||||
 | 
			
		||||
const Image = (props) => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const [open, setOpen] = useState(false);
 | 
			
		||||
    return (
 | 
			
		||||
        <>
 | 
			
		||||
| 
						 | 
				
			
			@ -296,6 +299,7 @@ const Image = (props) => {
 | 
			
		|||
                component="img"
 | 
			
		||||
                src={props.attachment.url}
 | 
			
		||||
                loading="lazy"
 | 
			
		||||
                alt={t("notifications_attachment_image")}
 | 
			
		||||
                onClick={() => setOpen(true)}
 | 
			
		||||
                sx={{
 | 
			
		||||
                    marginTop: 2,
 | 
			
		||||
| 
						 | 
				
			
			@ -316,6 +320,7 @@ const Image = (props) => {
 | 
			
		|||
                    <Box
 | 
			
		||||
                        component="img"
 | 
			
		||||
                        src={props.attachment.url}
 | 
			
		||||
                        alt={t("notifications_attachment_image")}
 | 
			
		||||
                        loading="lazy"
 | 
			
		||||
                        sx={{
 | 
			
		||||
                            maxWidth: 1,
 | 
			
		||||
| 
						 | 
				
			
			@ -347,13 +352,16 @@ const UserAction = (props) => {
 | 
			
		|||
    if (action.action === "broadcast") {
 | 
			
		||||
        return (
 | 
			
		||||
            <Tooltip title={t("notifications_actions_not_supported")}>
 | 
			
		||||
                <span><Button disabled>{action.label}</Button></span>
 | 
			
		||||
                <span><Button disabled aria-label={t("notifications_actions_not_supported")}>{action.label}</Button></span>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
        );
 | 
			
		||||
    } else if (action.action === "view") {
 | 
			
		||||
        return (
 | 
			
		||||
            <Tooltip title={t("notifications_actions_open_url_title", { url: action.url })}>
 | 
			
		||||
                <Button onClick={() => openUrl(action.url)}>{action.label}</Button>
 | 
			
		||||
                <Button
 | 
			
		||||
                    onClick={() => openUrl(action.url)}
 | 
			
		||||
                    aria-label={t("notifications_actions_open_url_title", { url: action.url })}
 | 
			
		||||
                >{action.label}</Button>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
        );
 | 
			
		||||
    } else if (action.action === "http") {
 | 
			
		||||
| 
						 | 
				
			
			@ -361,7 +369,10 @@ const UserAction = (props) => {
 | 
			
		|||
        const label = action.label + (ACTION_LABEL_SUFFIX[action.progress ?? 0] ?? "");
 | 
			
		||||
        return (
 | 
			
		||||
            <Tooltip title={t("notifications_actions_http_request_title", { method: method, url: action.url })}>
 | 
			
		||||
                <Button onClick={() => performHttpAction(notification, action)}>{label}</Button>
 | 
			
		||||
                <Button
 | 
			
		||||
                    onClick={() => performHttpAction(notification, action)}
 | 
			
		||||
                    aria-label={t("notifications_actions_http_request_title", { method: method, url: action.url })}
 | 
			
		||||
                >{label}</Button>
 | 
			
		||||
            </Tooltip>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -416,7 +427,7 @@ const NoNotifications = (props) => {
 | 
			
		|||
    return (
 | 
			
		||||
        <VerticallyCenteredContainer maxWidth="xs">
 | 
			
		||||
            <Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
 | 
			
		||||
                <img src={logoOutline} height="64" width="64"/><br />
 | 
			
		||||
                <img src={logoOutline} height="64" width="64" alt={t("action_bar_logo_alt")}/><br />
 | 
			
		||||
                {t("notifications_none_for_topic_title")}
 | 
			
		||||
            </Typography>
 | 
			
		||||
            <Paragraph>
 | 
			
		||||
| 
						 | 
				
			
			@ -442,7 +453,7 @@ const NoNotificationsWithoutSubscription = (props) => {
 | 
			
		|||
    return (
 | 
			
		||||
        <VerticallyCenteredContainer maxWidth="xs">
 | 
			
		||||
            <Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
 | 
			
		||||
                <img src={logoOutline} height="64" width="64"/><br />
 | 
			
		||||
                <img src={logoOutline} height="64" width="64" alt={t("action_bar_logo_alt")}/><br />
 | 
			
		||||
                {t("notifications_none_for_any_title")}
 | 
			
		||||
            </Typography>
 | 
			
		||||
            <Paragraph>
 | 
			
		||||
| 
						 | 
				
			
			@ -466,7 +477,7 @@ const NoSubscriptions = () => {
 | 
			
		|||
    return (
 | 
			
		||||
        <VerticallyCenteredContainer maxWidth="xs">
 | 
			
		||||
            <Typography variant="h5" align="center" sx={{ paddingBottom: 1 }}>
 | 
			
		||||
                <img src={logoOutline} height="64" width="64"/><br />
 | 
			
		||||
                <img src={logoOutline} height="64" width="64" alt={t("action_bar_logo_alt")}/><br />
 | 
			
		||||
                {t("notifications_no_subscriptions_title")}
 | 
			
		||||
            </Typography>
 | 
			
		||||
            <Paragraph>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -34,11 +34,6 @@ import DialogActions from "@mui/material/DialogActions";
 | 
			
		|||
import userManager from "../app/UserManager";
 | 
			
		||||
import {playSound, shuffle, sounds, validUrl} from "../app/utils";
 | 
			
		||||
import {useTranslation} from "react-i18next";
 | 
			
		||||
import priority1 from "../img/priority-1.svg";
 | 
			
		||||
import priority2 from "../img/priority-2.svg";
 | 
			
		||||
import priority3 from "../img/priority-3.svg";
 | 
			
		||||
import priority4 from "../img/priority-4.svg";
 | 
			
		||||
import priority5 from "../img/priority-5.svg";
 | 
			
		||||
 | 
			
		||||
const Preferences = () => {
 | 
			
		||||
    return (
 | 
			
		||||
| 
						 | 
				
			
			@ -55,7 +50,7 @@ const Preferences = () => {
 | 
			
		|||
const Notifications = () => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    return (
 | 
			
		||||
        <Card sx={{p: 3}}>
 | 
			
		||||
        <Card sx={{p: 3}} aria-label={t("prefs_notifications_title")}>
 | 
			
		||||
            <Typography variant="h5" sx={{marginBottom: 2}}>
 | 
			
		||||
                {t("prefs_notifications_title")}
 | 
			
		||||
            </Typography>
 | 
			
		||||
| 
						 | 
				
			
			@ -70,6 +65,7 @@ const Notifications = () => {
 | 
			
		|||
 | 
			
		||||
const Sound = () => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const labelId = "prefSound";
 | 
			
		||||
    const sound = useLiveQuery(async () => prefs.sound());
 | 
			
		||||
    const handleChange = async (ev) => {
 | 
			
		||||
        await prefs.setSound(ev.target.value);
 | 
			
		||||
| 
						 | 
				
			
			@ -84,15 +80,15 @@ const Sound = () => {
 | 
			
		|||
        description = t("prefs_notifications_sound_description_some", { sound: sounds[sound].label });
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <Pref title={t("prefs_notifications_sound_title")} description={description}>
 | 
			
		||||
        <Pref labelId={labelId} title={t("prefs_notifications_sound_title")} description={description}>
 | 
			
		||||
            <div style={{ display: 'flex', width: '100%' }}>
 | 
			
		||||
                <FormControl fullWidth variant="standard" sx={{ margin: 1 }}>
 | 
			
		||||
                    <Select value={sound} onChange={handleChange}>
 | 
			
		||||
                    <Select value={sound} onChange={handleChange} aria-labelledby={labelId}>
 | 
			
		||||
                        <MenuItem value={"none"}>{t("prefs_notifications_sound_no_sound")}</MenuItem>
 | 
			
		||||
                        {Object.entries(sounds).map(s => <MenuItem key={s[0]} value={s[0]}>{s[1].label}</MenuItem>)}
 | 
			
		||||
                    </Select>
 | 
			
		||||
                </FormControl>
 | 
			
		||||
                <IconButton onClick={() => playSound(sound)} disabled={sound === "none"}>
 | 
			
		||||
                <IconButton onClick={() => playSound(sound)} disabled={sound === "none"} aria-label={t("prefs_notifications_sound_play")}>
 | 
			
		||||
                    <PlayArrowIcon />
 | 
			
		||||
                </IconButton>
 | 
			
		||||
            </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -102,6 +98,7 @@ const Sound = () => {
 | 
			
		|||
 | 
			
		||||
const MinPriority = () => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const labelId = "prefMinPriority";
 | 
			
		||||
    const minPriority = useLiveQuery(async () => prefs.minPriority());
 | 
			
		||||
    const handleChange = async (ev) => {
 | 
			
		||||
        await prefs.setMinPriority(ev.target.value);
 | 
			
		||||
| 
						 | 
				
			
			@ -128,9 +125,9 @@ const MinPriority = () => {
 | 
			
		|||
        });
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <Pref title={t("prefs_notifications_min_priority_title")} description={description}>
 | 
			
		||||
        <Pref labelId={labelId} title={t("prefs_notifications_min_priority_title")} description={description}>
 | 
			
		||||
            <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
 | 
			
		||||
                <Select value={minPriority} onChange={handleChange}>
 | 
			
		||||
                <Select value={minPriority} onChange={handleChange} aria-labelledby={labelId}>
 | 
			
		||||
                    <MenuItem value={1}>{t("prefs_notifications_min_priority_any")}</MenuItem>
 | 
			
		||||
                    <MenuItem value={2}>{t("prefs_notifications_min_priority_low_and_higher")}</MenuItem>
 | 
			
		||||
                    <MenuItem value={3}>{t("prefs_notifications_min_priority_default_and_higher")}</MenuItem>
 | 
			
		||||
| 
						 | 
				
			
			@ -144,6 +141,7 @@ const MinPriority = () => {
 | 
			
		|||
 | 
			
		||||
const DeleteAfter = () => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    const labelId = "prefDeleteAfter";
 | 
			
		||||
    const deleteAfter = useLiveQuery(async () => prefs.deleteAfter());
 | 
			
		||||
    const handleChange = async (ev) => {
 | 
			
		||||
        await prefs.setDeleteAfter(ev.target.value);
 | 
			
		||||
| 
						 | 
				
			
			@ -161,9 +159,9 @@ const DeleteAfter = () => {
 | 
			
		|||
        }
 | 
			
		||||
    })();
 | 
			
		||||
    return (
 | 
			
		||||
        <Pref title={t("prefs_notifications_delete_after_title")} description={description}>
 | 
			
		||||
        <Pref labelId={labelId} title={t("prefs_notifications_delete_after_title")} description={description}>
 | 
			
		||||
            <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
 | 
			
		||||
                <Select value={deleteAfter} onChange={handleChange}>
 | 
			
		||||
                <Select value={deleteAfter} onChange={handleChange} aria-labelledby={labelId}>
 | 
			
		||||
                    <MenuItem value={0}>{t("prefs_notifications_delete_after_never")}</MenuItem>
 | 
			
		||||
                    <MenuItem value={10800}>{t("prefs_notifications_delete_after_three_hours")}</MenuItem>
 | 
			
		||||
                    <MenuItem value={86400}>{t("prefs_notifications_delete_after_one_day")}</MenuItem>
 | 
			
		||||
| 
						 | 
				
			
			@ -177,7 +175,7 @@ const DeleteAfter = () => {
 | 
			
		|||
 | 
			
		||||
const PrefGroup = (props) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <div>
 | 
			
		||||
        <div role="table">
 | 
			
		||||
            {props.children}
 | 
			
		||||
        </div>
 | 
			
		||||
    )
 | 
			
		||||
| 
						 | 
				
			
			@ -185,28 +183,39 @@ const PrefGroup = (props) => {
 | 
			
		|||
 | 
			
		||||
const Pref = (props) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <div style={{
 | 
			
		||||
            display: "flex",
 | 
			
		||||
            flexDirection: "row",
 | 
			
		||||
            marginTop: "10px",
 | 
			
		||||
            marginBottom: "20px",
 | 
			
		||||
        }}>
 | 
			
		||||
            <div style={{
 | 
			
		||||
                flex: '1 0 40%',
 | 
			
		||||
                display: 'flex',
 | 
			
		||||
                flexDirection: 'column',
 | 
			
		||||
                justifyContent: 'center',
 | 
			
		||||
                paddingRight: '30px'
 | 
			
		||||
            }}>
 | 
			
		||||
        <div
 | 
			
		||||
            role="row"
 | 
			
		||||
            style={{
 | 
			
		||||
                display: "flex",
 | 
			
		||||
                flexDirection: "row",
 | 
			
		||||
                marginTop: "10px",
 | 
			
		||||
                marginBottom: "20px",
 | 
			
		||||
            }}
 | 
			
		||||
        >
 | 
			
		||||
            <div
 | 
			
		||||
                role="cell"
 | 
			
		||||
                id={props.labelId}
 | 
			
		||||
                aria-label={props.title}
 | 
			
		||||
                style={{
 | 
			
		||||
                    flex: '1 0 40%',
 | 
			
		||||
                    display: 'flex',
 | 
			
		||||
                    flexDirection: 'column',
 | 
			
		||||
                    justifyContent: 'center',
 | 
			
		||||
                    paddingRight: '30px'
 | 
			
		||||
                }}
 | 
			
		||||
            >
 | 
			
		||||
                <div><b>{props.title}</b></div>
 | 
			
		||||
                {props.description && <div><em>{props.description}</em></div>}
 | 
			
		||||
            </div>
 | 
			
		||||
            <div style={{
 | 
			
		||||
                flex: '1 0 calc(60% - 50px)',
 | 
			
		||||
                display: 'flex',
 | 
			
		||||
                flexDirection: 'column',
 | 
			
		||||
                justifyContent: 'center'
 | 
			
		||||
            }}>
 | 
			
		||||
            <div
 | 
			
		||||
                role="cell"
 | 
			
		||||
                style={{
 | 
			
		||||
                    flex: '1 0 calc(60% - 50px)',
 | 
			
		||||
                    display: 'flex',
 | 
			
		||||
                    flexDirection: 'column',
 | 
			
		||||
                    justifyContent: 'center'
 | 
			
		||||
                }}
 | 
			
		||||
            >
 | 
			
		||||
                {props.children}
 | 
			
		||||
            </div>
 | 
			
		||||
        </div>
 | 
			
		||||
| 
						 | 
				
			
			@ -235,7 +244,7 @@ const Users = () => {
 | 
			
		|||
        }
 | 
			
		||||
    };
 | 
			
		||||
    return (
 | 
			
		||||
        <Card sx={{ padding: 1 }}>
 | 
			
		||||
        <Card sx={{ padding: 1 }} aria-label={t("prefs_users_title")}>
 | 
			
		||||
            <CardContent sx={{ paddingBottom: 1 }}>
 | 
			
		||||
                <Typography variant="h5" sx={{marginBottom: 2}}>
 | 
			
		||||
                    {t("prefs_users_title")}
 | 
			
		||||
| 
						 | 
				
			
			@ -291,7 +300,7 @@ const UserTable = (props) => {
 | 
			
		|||
        }
 | 
			
		||||
    };
 | 
			
		||||
    return (
 | 
			
		||||
        <Table size="small">
 | 
			
		||||
        <Table size="small" aria-label={t("prefs_users_table")}>
 | 
			
		||||
            <TableHead>
 | 
			
		||||
                <TableRow>
 | 
			
		||||
                    <TableCell sx={{paddingLeft: 0}}>{t("prefs_users_table_user_header")}</TableCell>
 | 
			
		||||
| 
						 | 
				
			
			@ -305,13 +314,13 @@ const UserTable = (props) => {
 | 
			
		|||
                        key={user.baseUrl}
 | 
			
		||||
                        sx={{ '&:last-child td, &:last-child th': { border: 0 } }}
 | 
			
		||||
                    >
 | 
			
		||||
                        <TableCell component="th" scope="row" sx={{paddingLeft: 0}}>{user.username}</TableCell>
 | 
			
		||||
                        <TableCell>{user.baseUrl}</TableCell>
 | 
			
		||||
                        <TableCell component="th" scope="row" sx={{paddingLeft: 0}} aria-label={t("prefs_users_table_user_header")}>{user.username}</TableCell>
 | 
			
		||||
                        <TableCell aria-label={t("prefs_users_table_base_url_header")}>{user.baseUrl}</TableCell>
 | 
			
		||||
                        <TableCell align="right">
 | 
			
		||||
                            <IconButton onClick={() => handleEditClick(user)}>
 | 
			
		||||
                            <IconButton onClick={() => handleEditClick(user)} aria-label={t("prefs_users_edit_button")}>
 | 
			
		||||
                                <EditIcon/>
 | 
			
		||||
                            </IconButton>
 | 
			
		||||
                            <IconButton onClick={() => handleDeleteClick(user)}>
 | 
			
		||||
                            <IconButton onClick={() => handleDeleteClick(user)} aria-label={t("prefs_users_delete_button")}>
 | 
			
		||||
                                <CloseIcon />
 | 
			
		||||
                            </IconButton>
 | 
			
		||||
                        </TableCell>
 | 
			
		||||
| 
						 | 
				
			
			@ -371,6 +380,7 @@ const UserDialog = (props) => {
 | 
			
		|||
                    margin="dense"
 | 
			
		||||
                    id="baseUrl"
 | 
			
		||||
                    label={t("prefs_users_dialog_base_url_label")}
 | 
			
		||||
                    aria-label={t("prefs_users_dialog_base_url_label")}
 | 
			
		||||
                    value={baseUrl}
 | 
			
		||||
                    onChange={ev => setBaseUrl(ev.target.value)}
 | 
			
		||||
                    type="url"
 | 
			
		||||
| 
						 | 
				
			
			@ -382,6 +392,7 @@ const UserDialog = (props) => {
 | 
			
		|||
                    margin="dense"
 | 
			
		||||
                    id="username"
 | 
			
		||||
                    label={t("prefs_users_dialog_username_label")}
 | 
			
		||||
                    aria-label={t("prefs_users_dialog_username_label")}
 | 
			
		||||
                    value={username}
 | 
			
		||||
                    onChange={ev => setUsername(ev.target.value)}
 | 
			
		||||
                    type="text"
 | 
			
		||||
| 
						 | 
				
			
			@ -392,6 +403,7 @@ const UserDialog = (props) => {
 | 
			
		|||
                    margin="dense"
 | 
			
		||||
                    id="password"
 | 
			
		||||
                    label={t("prefs_users_dialog_password_label")}
 | 
			
		||||
                    aria-label={t("prefs_users_dialog_password_label")}
 | 
			
		||||
                    type="password"
 | 
			
		||||
                    value={password}
 | 
			
		||||
                    onChange={ev => setPassword(ev.target.value)}
 | 
			
		||||
| 
						 | 
				
			
			@ -410,7 +422,7 @@ const UserDialog = (props) => {
 | 
			
		|||
const Appearance = () => {
 | 
			
		||||
    const { t } = useTranslation();
 | 
			
		||||
    return (
 | 
			
		||||
        <Card sx={{p: 3}}>
 | 
			
		||||
        <Card sx={{p: 3}} aria-label={t("prefs_appearance_title")}>
 | 
			
		||||
            <Typography variant="h5" sx={{marginBottom: 2}}>
 | 
			
		||||
                {t("prefs_appearance_title")}
 | 
			
		||||
            </Typography>
 | 
			
		||||
| 
						 | 
				
			
			@ -423,6 +435,7 @@ const Appearance = () => {
 | 
			
		|||
 | 
			
		||||
const Language = () => {
 | 
			
		||||
    const { t, i18n } = useTranslation();
 | 
			
		||||
    const labelId = "prefLanguage";
 | 
			
		||||
    const randomFlags = shuffle(["🇬🇧", "🇺🇸", "🇪🇸", "🇫🇷", "🇧🇬", "🇨🇿", "🇩🇪", "🇮🇩", "🇯🇵", "🇷🇺", "🇹🇷"]).slice(0, 3);
 | 
			
		||||
    const title = t("prefs_appearance_language_title") + " " + randomFlags.join(" ");
 | 
			
		||||
    const lang = i18n.language ?? "en";
 | 
			
		||||
| 
						 | 
				
			
			@ -432,9 +445,9 @@ const Language = () => {
 | 
			
		|||
    // Better: Sidebar in Wikipedia: https://en.wikipedia.org/wiki/Bokm%C3%A5l
 | 
			
		||||
 | 
			
		||||
    return (
 | 
			
		||||
        <Pref title={title}>
 | 
			
		||||
        <Pref labelId={labelId} title={title}>
 | 
			
		||||
            <FormControl fullWidth variant="standard" sx={{ m: 1 }}>
 | 
			
		||||
                <Select value={lang} onChange={(ev) => i18n.changeLanguage(ev.target.value)}>
 | 
			
		||||
                <Select value={lang} onChange={(ev) => i18n.changeLanguage(ev.target.value)} aria-labelledby={labelId}>
 | 
			
		||||
                    <MenuItem value="en">English</MenuItem>
 | 
			
		||||
                    <MenuItem value="bg">Български</MenuItem>
 | 
			
		||||
                    <MenuItem value="cs">Čeština</MenuItem>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -240,6 +240,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
                            <TextField
 | 
			
		||||
                                margin="dense"
 | 
			
		||||
                                label={t("publish_dialog_base_url_label")}
 | 
			
		||||
                                aria-label={t("publish_dialog_base_url_label")}
 | 
			
		||||
                                placeholder={t("publish_dialog_base_url_placeholder")}
 | 
			
		||||
                                value={baseUrl}
 | 
			
		||||
                                onChange={ev => setBaseUrl(ev.target.value)}
 | 
			
		||||
| 
						 | 
				
			
			@ -251,6 +252,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
                            <TextField
 | 
			
		||||
                                margin="dense"
 | 
			
		||||
                                label={t("publish_dialog_topic_label")}
 | 
			
		||||
                                aria-label={t("publish_dialog_topic_label")}
 | 
			
		||||
                                placeholder={t("publish_dialog_topic_placeholder")}
 | 
			
		||||
                                value={topic}
 | 
			
		||||
                                onChange={ev => setTopic(ev.target.value)}
 | 
			
		||||
| 
						 | 
				
			
			@ -265,6 +267,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
                    <TextField
 | 
			
		||||
                        margin="dense"
 | 
			
		||||
                        label={t("publish_dialog_title_label")}
 | 
			
		||||
                        aria-label={t("publish_dialog_title_label")}
 | 
			
		||||
                        placeholder={t("publish_dialog_title_placeholder")}
 | 
			
		||||
                        value={title}
 | 
			
		||||
                        onChange={ev => setTitle(ev.target.value)}
 | 
			
		||||
| 
						 | 
				
			
			@ -276,6 +279,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
                    <TextField
 | 
			
		||||
                        margin="dense"
 | 
			
		||||
                        label={t("publish_dialog_message_label")}
 | 
			
		||||
                        aria-label={t("publish_dialog_message_label")}
 | 
			
		||||
                        placeholder={t("publish_dialog_message_placeholder")}
 | 
			
		||||
                        value={message}
 | 
			
		||||
                        onChange={ev => setMessage(ev.target.value)}
 | 
			
		||||
| 
						 | 
				
			
			@ -293,12 +297,13 @@ const PublishDialog = (props) => {
 | 
			
		|||
                            onEmojiPick={handleEmojiPick}
 | 
			
		||||
                            onClose={handleEmojiClose}
 | 
			
		||||
                        />
 | 
			
		||||
                        <DialogIconButton disabled={disabled} onClick={handleEmojiClick}>
 | 
			
		||||
                        <DialogIconButton disabled={disabled} onClick={handleEmojiClick} aria-label={t("publish_dialog_emoji_picker_show")}>
 | 
			
		||||
                            <InsertEmoticonIcon/>
 | 
			
		||||
                        </DialogIconButton>
 | 
			
		||||
                        <TextField
 | 
			
		||||
                            margin="dense"
 | 
			
		||||
                            label={t("publish_dialog_tags_label")}
 | 
			
		||||
                            aria-label={t("publish_dialog_tags_label")}
 | 
			
		||||
                            placeholder={t("publish_dialog_tags_placeholder")}
 | 
			
		||||
                            value={tags}
 | 
			
		||||
                            onChange={ev => setTags(ev.target.value)}
 | 
			
		||||
| 
						 | 
				
			
			@ -315,15 +320,16 @@ const PublishDialog = (props) => {
 | 
			
		|||
                            <InputLabel/>
 | 
			
		||||
                            <Select
 | 
			
		||||
                                label={t("publish_dialog_priority_label")}
 | 
			
		||||
                                aria-label={t("publish_dialog_priority_label")}
 | 
			
		||||
                                margin="dense"
 | 
			
		||||
                                value={priority}
 | 
			
		||||
                                onChange={(ev) => setPriority(ev.target.value)}
 | 
			
		||||
                                disabled={disabled}
 | 
			
		||||
                            >
 | 
			
		||||
                                {[5,4,3,2,1].map(priority =>
 | 
			
		||||
                                    <MenuItem key={`priorityMenuItem${priority}`} value={priority}>
 | 
			
		||||
                                    <MenuItem key={`priorityMenuItem${priority}`} value={priority} aria-label={t("notifications_priority_x", { priority: priority })}>
 | 
			
		||||
                                        <div style={{ display: 'flex', alignItems: 'center' }}>
 | 
			
		||||
                                            <img src={priorities[priority].file} style={{marginRight: "8px"}}/>
 | 
			
		||||
                                            <img src={priorities[priority].file} style={{marginRight: "8px"}} alt={t("notifications_priority_x", { priority: priority })}/>
 | 
			
		||||
                                            <div>{priorities[priority].label}</div>
 | 
			
		||||
                                        </div>
 | 
			
		||||
                                    </MenuItem>
 | 
			
		||||
| 
						 | 
				
			
			@ -339,6 +345,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
                            <TextField
 | 
			
		||||
                                margin="dense"
 | 
			
		||||
                                label={t("publish_dialog_click_label")}
 | 
			
		||||
                                aria-label={t("publish_dialog_click_label")}
 | 
			
		||||
                                placeholder={t("publish_dialog_click_placeholder")}
 | 
			
		||||
                                value={clickUrl}
 | 
			
		||||
                                onChange={ev => setClickUrl(ev.target.value)}
 | 
			
		||||
| 
						 | 
				
			
			@ -357,6 +364,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
                            <TextField
 | 
			
		||||
                                margin="dense"
 | 
			
		||||
                                label={t("publish_dialog_email_label")}
 | 
			
		||||
                                aria-label={t("publish_dialog_email_label")}
 | 
			
		||||
                                placeholder={t("publish_dialog_email_placeholder")}
 | 
			
		||||
                                value={email}
 | 
			
		||||
                                onChange={ev => setEmail(ev.target.value)}
 | 
			
		||||
| 
						 | 
				
			
			@ -377,6 +385,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
                            <TextField
 | 
			
		||||
                                margin="dense"
 | 
			
		||||
                                label={t("publish_dialog_attach_label")}
 | 
			
		||||
                                aria-label={t("publish_dialog_attach_label")}
 | 
			
		||||
                                placeholder={t("publish_dialog_attach_placeholder")}
 | 
			
		||||
                                value={attachUrl}
 | 
			
		||||
                                onChange={ev => {
 | 
			
		||||
| 
						 | 
				
			
			@ -402,6 +411,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
                            <TextField
 | 
			
		||||
                                margin="dense"
 | 
			
		||||
                                label={t("publish_dialog_filename_label")}
 | 
			
		||||
                                aria-label={t("publish_dialog_filename_label")}
 | 
			
		||||
                                placeholder={t("publish_dialog_filename_placeholder")}
 | 
			
		||||
                                value={filename}
 | 
			
		||||
                                onChange={ev => {
 | 
			
		||||
| 
						 | 
				
			
			@ -441,6 +451,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
                            <TextField
 | 
			
		||||
                                margin="dense"
 | 
			
		||||
                                label={t("publish_dialog_delay_label")}
 | 
			
		||||
                                aria-label={t("publish_dialog_delay_label")}
 | 
			
		||||
                                placeholder={t("publish_dialog_delay_placeholder", {
 | 
			
		||||
                                    unixTimestamp: "1649029748",
 | 
			
		||||
                                    relativeTime: "30m",
 | 
			
		||||
| 
						 | 
				
			
			@ -459,12 +470,12 @@ const PublishDialog = (props) => {
 | 
			
		|||
                        {t("publish_dialog_other_features")}
 | 
			
		||||
                    </Typography>
 | 
			
		||||
                    <div>
 | 
			
		||||
                        {!showClickUrl && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_click_label")} onClick={() => setShowClickUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showEmail && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_email_label")} onClick={() => setShowEmail(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showAttachUrl && !showAttachFile && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_attach_url_label")} onClick={() => setShowAttachUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showAttachFile && !showAttachUrl && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_attach_file_label")} onClick={() => handleAttachFileClick()} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showDelay && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_delay_label")} onClick={() => setShowDelay(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showTopicUrl && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_topic_label")} onClick={() => setShowTopicUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showClickUrl && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_click_label")} aria-label={t("publish_dialog_chip_click_label")} onClick={() => setShowClickUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showEmail && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_email_label")} aria-label={t("publish_dialog_chip_email_label")} onClick={() => setShowEmail(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showAttachUrl && !showAttachFile && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_attach_url_label")} aria-label={t("publish_dialog_chip_attach_url_label")} onClick={() => setShowAttachUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showAttachFile && !showAttachUrl && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_attach_file_label")} aria-label={t("publish_dialog_chip_attach_file_label")} onClick={() => handleAttachFileClick()} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showDelay && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_delay_label")} aria-label={t("publish_dialog_chip_delay_label")} onClick={() => setShowDelay(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                        {!showTopicUrl && <Chip clickable disabled={disabled} label={t("publish_dialog_chip_topic_label")} aria-label={t("publish_dialog_chip_topic_label")} onClick={() => setShowTopicUrl(true)} sx={{marginRight: 1, marginBottom: 1}}/>}
 | 
			
		||||
                    </div>
 | 
			
		||||
                    <Typography variant="body1" sx={{marginTop: 1, marginBottom: 1}}>
 | 
			
		||||
                        <Trans
 | 
			
		||||
| 
						 | 
				
			
			@ -483,7 +494,13 @@ const PublishDialog = (props) => {
 | 
			
		|||
                                label={t("publish_dialog_checkbox_publish_another")}
 | 
			
		||||
                                sx={{marginRight: 2}}
 | 
			
		||||
                                control={
 | 
			
		||||
                                    <Checkbox size="small" checked={publishAnother} onChange={(ev) => setPublishAnother(ev.target.checked)} />
 | 
			
		||||
                                    <Checkbox
 | 
			
		||||
                                        size="small"
 | 
			
		||||
                                        checked={publishAnother}
 | 
			
		||||
                                        onChange={(ev) => setPublishAnother(ev.target.checked)}
 | 
			
		||||
                                        inputProps={{
 | 
			
		||||
                                            "aria-label": t("publish_dialog_checkbox_publish_another")
 | 
			
		||||
                                        }} />
 | 
			
		||||
                                } />
 | 
			
		||||
                            <Button onClick={props.onClose}>{t("publish_dialog_button_cancel")}</Button>
 | 
			
		||||
                            <Button onClick={handleSubmit} disabled={!sendButtonEnabled}>{t("publish_dialog_button_send")}</Button>
 | 
			
		||||
| 
						 | 
				
			
			@ -497,7 +514,7 @@ const PublishDialog = (props) => {
 | 
			
		|||
 | 
			
		||||
const Row = (props) => {
 | 
			
		||||
    return (
 | 
			
		||||
        <div style={{display: 'flex'}}>
 | 
			
		||||
        <div style={{display: 'flex'}} role="row">
 | 
			
		||||
            {props.children}
 | 
			
		||||
        </div>
 | 
			
		||||
    );
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue