Merge commit '079d681ac6f279f3a7a6be5c3734549f3be8f912' into glitch-soc/merge-upstream
Conflicts: - `app/javascript/material-icons/400-24px/code.svg`: Icon newly-introduced upstream. Used upstream's more recent version. - `app/javascript/material-icons/400-24px/mail.svg`: Icon newly-introduced upstream. Used upstream's more recent version. - `app/javascript/material-icons/400-24px/manufacturing.svg`: Icon newly-introduced upstream. Used upstream's more recent version. - `app/views/admin/settings/shared/_links.html.haml`: Glitch-soc had extra menu items, ported the changes for them too. - `config/navigation.rb`: Glitch-soc had extra menu items, ported the changes for them too.
This commit is contained in:
commit
dd2a991e24
83 files changed
+476
-277
No files matched your search
@@ -0,0 +1,185 @@
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
|
||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||
|
||||
import type { IconProp } from './icon';
|
||||
import { Icon } from './icon';
|
||||
|
||||
const listenerOptions = supportsPassiveEvents
|
||||
? { passive: true, capture: true }
|
||||
: true;
|
||||
|
||||
interface SelectItem {
|
||||
value: string;
|
||||
icon?: string;
|
||||
iconComponent?: IconProp;
|
||||
text: string;
|
||||
meta: string;
|
||||
extra?: string;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
value: string;
|
||||
classNamePrefix: string;
|
||||
style?: React.CSSProperties;
|
||||
items: SelectItem[];
|
||||
onChange: (value: string) => void;
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export const DropdownSelector: React.FC<Props> = ({
|
||||
style,
|
||||
items,
|
||||
value,
|
||||
classNamePrefix = 'privacy-dropdown',
|
||||
onClose,
|
||||
onChange,
|
||||
}) => {
|
||||
const nodeRef = useRef<HTMLUListElement>(null);
|
||||
const focusedItemRef = useRef<HTMLLIElement>(null);
|
||||
const [currentValue, setCurrentValue] = useState(value);
|
||||
|
||||
const handleDocumentClick = useCallback(
|
||||
(e: MouseEvent | TouchEvent) => {
|
||||
if (
|
||||
nodeRef.current &&
|
||||
e.target instanceof Node &&
|
||||
!nodeRef.current.contains(e.target)
|
||||
) {
|
||||
onClose();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
[nodeRef, onClose],
|
||||
);
|
||||
|
||||
const handleClick = useCallback(
|
||||
(
|
||||
e: React.MouseEvent<HTMLLIElement> | React.KeyboardEvent<HTMLLIElement>,
|
||||
) => {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
onClose();
|
||||
if (value) onChange(value);
|
||||
},
|
||||
[onClose, onChange],
|
||||
);
|
||||
|
||||
const handleKeyDown = useCallback(
|
||||
(e: React.KeyboardEvent<HTMLLIElement>) => {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
const index = items.findIndex((item) => item.value === value);
|
||||
|
||||
let element: Element | null | undefined = null;
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
onClose();
|
||||
break;
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
handleClick(e);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
element =
|
||||
nodeRef.current?.children[index + 1] ??
|
||||
nodeRef.current?.firstElementChild;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element =
|
||||
nodeRef.current?.children[index - 1] ??
|
||||
nodeRef.current?.lastElementChild;
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element =
|
||||
nodeRef.current?.children[index + 1] ??
|
||||
nodeRef.current?.firstElementChild;
|
||||
} else {
|
||||
element =
|
||||
nodeRef.current?.children[index - 1] ??
|
||||
nodeRef.current?.lastElementChild;
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = nodeRef.current?.firstElementChild;
|
||||
break;
|
||||
case 'End':
|
||||
element = nodeRef.current?.lastElementChild;
|
||||
break;
|
||||
}
|
||||
|
||||
if (element && element instanceof HTMLElement) {
|
||||
const selectedValue = element.getAttribute('data-index');
|
||||
element.focus();
|
||||
if (selectedValue) setCurrentValue(selectedValue);
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
},
|
||||
[nodeRef, items, onClose, handleClick, setCurrentValue],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('click', handleDocumentClick, { capture: true });
|
||||
document.addEventListener('touchend', handleDocumentClick, listenerOptions);
|
||||
focusedItemRef.current?.focus({ preventScroll: true });
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleDocumentClick, {
|
||||
capture: true,
|
||||
});
|
||||
document.removeEventListener(
|
||||
'touchend',
|
||||
handleDocumentClick,
|
||||
listenerOptions,
|
||||
);
|
||||
};
|
||||
}, [handleDocumentClick]);
|
||||
|
||||
return (
|
||||
<ul style={style} role='listbox' ref={nodeRef}>
|
||||
{items.map((item) => (
|
||||
<li
|
||||
role='option'
|
||||
tabIndex={0}
|
||||
key={item.value}
|
||||
data-index={item.value}
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={handleClick}
|
||||
className={classNames(`${classNamePrefix}__option`, {
|
||||
active: item.value === currentValue,
|
||||
})}
|
||||
aria-selected={item.value === currentValue}
|
||||
ref={item.value === currentValue ? focusedItemRef : null}
|
||||
>
|
||||
{item.icon && item.iconComponent && (
|
||||
<div className={`${classNamePrefix}__option__icon`}>
|
||||
<Icon id={item.icon} icon={item.iconComponent} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={`${classNamePrefix}__option__content`}>
|
||||
<strong>{item.text}</strong>
|
||||
{item.meta}
|
||||
</div>
|
||||
|
||||
{item.extra && (
|
||||
<div
|
||||
className={`${classNamePrefix}__option__additional`}
|
||||
title={item.extra}
|
||||
>
|
||||
<Icon id='info-circle' icon={InfoIcon} />
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
@@ -11,10 +11,9 @@ import AlternateEmailIcon from '@/material-icons/400-24px/alternate_email.svg?re
|
||||
import LockIcon from '@/material-icons/400-24px/lock.svg?react';
|
||||
import PublicIcon from '@/material-icons/400-24px/public.svg?react';
|
||||
import QuietTimeIcon from '@/material-icons/400-24px/quiet_time.svg?react';
|
||||
import { DropdownSelector } from 'mastodon/components/dropdown_selector';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
import { PrivacyDropdownMenu } from './privacy_dropdown_menu';
|
||||
|
||||
const messages = defineMessages({
|
||||
public_short: { id: 'privacy.public.short', defaultMessage: 'Public' },
|
||||
public_long: { id: 'privacy.public.long', defaultMessage: 'Anyone on and off Mastodon' },
|
||||
@@ -143,7 +142,7 @@ class PrivacyDropdown extends PureComponent {
|
||||
{({ props, placement }) => (
|
||||
<div {...props}>
|
||||
<div className={`dropdown-animation privacy-dropdown__dropdown ${placement}`}>
|
||||
<PrivacyDropdownMenu
|
||||
<DropdownSelector
|
||||
items={this.options}
|
||||
value={value}
|
||||
onClose={this.handleClose}
|
||||
|
||||
@@ -1,128 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useCallback, useEffect, useRef, useState } from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
|
||||
import InfoIcon from '@/material-icons/400-24px/info.svg?react';
|
||||
import { Icon } from 'mastodon/components/icon';
|
||||
|
||||
const listenerOptions = supportsPassiveEvents ? { passive: true, capture: true } : true;
|
||||
|
||||
export const PrivacyDropdownMenu = ({ style, items, value, onClose, onChange }) => {
|
||||
const nodeRef = useRef(null);
|
||||
const focusedItemRef = useRef(null);
|
||||
const [currentValue, setCurrentValue] = useState(value);
|
||||
|
||||
const handleDocumentClick = useCallback((e) => {
|
||||
if (nodeRef.current && !nodeRef.current.contains(e.target)) {
|
||||
onClose();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}, [nodeRef, onClose]);
|
||||
|
||||
const handleClick = useCallback((e) => {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
|
||||
e.preventDefault();
|
||||
|
||||
onClose();
|
||||
onChange(value);
|
||||
}, [onClose, onChange]);
|
||||
|
||||
const handleKeyDown = useCallback((e) => {
|
||||
const value = e.currentTarget.getAttribute('data-index');
|
||||
const index = items.findIndex(item => (item.value === value));
|
||||
|
||||
let element = null;
|
||||
|
||||
switch (e.key) {
|
||||
case 'Escape':
|
||||
onClose();
|
||||
break;
|
||||
case ' ':
|
||||
case 'Enter':
|
||||
handleClick(e);
|
||||
break;
|
||||
case 'ArrowDown':
|
||||
element = nodeRef.current.childNodes[index + 1] || nodeRef.current.firstChild;
|
||||
break;
|
||||
case 'ArrowUp':
|
||||
element = nodeRef.current.childNodes[index - 1] || nodeRef.current.lastChild;
|
||||
break;
|
||||
case 'Tab':
|
||||
if (e.shiftKey) {
|
||||
element = nodeRef.current.childNodes[index + 1] || nodeRef.current.firstChild;
|
||||
} else {
|
||||
element = nodeRef.current.childNodes[index - 1] || nodeRef.current.lastChild;
|
||||
}
|
||||
break;
|
||||
case 'Home':
|
||||
element = nodeRef.current.firstChild;
|
||||
break;
|
||||
case 'End':
|
||||
element = nodeRef.current.lastChild;
|
||||
break;
|
||||
}
|
||||
|
||||
if (element) {
|
||||
element.focus();
|
||||
setCurrentValue(element.getAttribute('data-index'));
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
}
|
||||
}, [nodeRef, items, onClose, handleClick, setCurrentValue]);
|
||||
|
||||
useEffect(() => {
|
||||
document.addEventListener('click', handleDocumentClick, { capture: true });
|
||||
document.addEventListener('touchend', handleDocumentClick, listenerOptions);
|
||||
focusedItemRef.current?.focus({ preventScroll: true });
|
||||
|
||||
return () => {
|
||||
document.removeEventListener('click', handleDocumentClick, { capture: true });
|
||||
document.removeEventListener('touchend', handleDocumentClick, listenerOptions);
|
||||
};
|
||||
}, [handleDocumentClick]);
|
||||
|
||||
return (
|
||||
<ul style={{ ...style }} role='listbox' ref={nodeRef}>
|
||||
{items.map(item => (
|
||||
<li
|
||||
role='option'
|
||||
tabIndex={0}
|
||||
key={item.value}
|
||||
data-index={item.value}
|
||||
onKeyDown={handleKeyDown}
|
||||
onClick={handleClick}
|
||||
className={classNames('privacy-dropdown__option', { active: item.value === currentValue })}
|
||||
aria-selected={item.value === currentValue}
|
||||
ref={item.value === currentValue ? focusedItemRef : null}
|
||||
>
|
||||
<div className='privacy-dropdown__option__icon'>
|
||||
<Icon id={item.icon} icon={item.iconComponent} />
|
||||
</div>
|
||||
|
||||
<div className='privacy-dropdown__option__content'>
|
||||
<strong>{item.text}</strong>
|
||||
{item.meta}
|
||||
</div>
|
||||
|
||||
{item.extra && (
|
||||
<div className='privacy-dropdown__option__additional' title={item.extra}>
|
||||
<Icon id='info-circle' icon={InfoIcon} />
|
||||
</div>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
};
|
||||
|
||||
PrivacyDropdownMenu.propTypes = {
|
||||
style: PropTypes.object,
|
||||
items: PropTypes.array.isRequired,
|
||||
value: PropTypes.string.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
onChange: PropTypes.func.isRequired,
|
||||
};
|
||||
@@ -7,6 +7,9 @@ import { useAppSelector, useAppDispatch } from 'mastodon/store';
|
||||
|
||||
import { CheckboxWithLabel } from './checkbox_with_label';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
||||
const noop = () => {};
|
||||
|
||||
export const PolicyControls: React.FC = () => {
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
@@ -135,6 +138,21 @@ export const PolicyControls: React.FC = () => {
|
||||
/>
|
||||
</span>
|
||||
</CheckboxWithLabel>
|
||||
|
||||
<CheckboxWithLabel checked disabled onChange={noop}>
|
||||
<strong>
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_limited_accounts_title'
|
||||
defaultMessage='Moderated accounts'
|
||||
/>
|
||||
</strong>
|
||||
<span className='hint'>
|
||||
<FormattedMessage
|
||||
id='notifications.policy.filter_limited_accounts_hint'
|
||||
defaultMessage='Limited by server moderators'
|
||||
/>
|
||||
</span>
|
||||
</CheckboxWithLabel>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import PropTypes from 'prop-types';
|
||||
import { useRef, useCallback, useEffect } from 'react';
|
||||
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Helmet } from 'react-helmet';
|
||||
|
||||
@@ -90,6 +90,23 @@ export const NotificationRequest = ({ multiColumn, params: { id } }) => {
|
||||
|
||||
const columnTitle = intl.formatMessage(messages.title, { name: account?.get('display_name') || account?.get('username') });
|
||||
|
||||
let explainer = null;
|
||||
|
||||
if (account?.limited) {
|
||||
const isLocal = account.acct.indexOf('@') === -1;
|
||||
explainer = (
|
||||
<div className='dismissable-banner'>
|
||||
<div className='dismissable-banner__message'>
|
||||
{isLocal ? (
|
||||
<FormattedMessage id='notification_requests.explainer_for_limited_account' defaultMessage='Notifications from this account have been filtered because the account has been limited by a moderator.' />
|
||||
) : (
|
||||
<FormattedMessage id='notification_requests.explainer_for_limited_remote_account' defaultMessage='Notifications from this account have been filtered because the account or its server has been limited by a moderator.' />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={columnRef} label={columnTitle}>
|
||||
<ColumnHeader
|
||||
@@ -109,6 +126,7 @@ export const NotificationRequest = ({ multiColumn, params: { id } }) => {
|
||||
|
||||
<SensitiveMediaContextProvider hideMediaByDefault>
|
||||
<ScrollableList
|
||||
prepend={explainer}
|
||||
scrollKey={`notification_requests/${id}`}
|
||||
trackScroll={!multiColumn}
|
||||
bindToDocument={!multiColumn}
|
||||
|
||||
@@ -545,6 +545,8 @@
|
||||
"notifications.permission_denied": "Computernotifikationer er utilgængelige grundet tidligere afvist browsertilladelsesanmodning",
|
||||
"notifications.permission_denied_alert": "Computernotifikationer kan ikke aktiveres, da browsertilladelse tidligere blev nægtet",
|
||||
"notifications.permission_required": "Computernotifikationer er utilgængelige, da den krævede tilladelse ikke er tildelt.",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Begrænset af servermoderatorer",
|
||||
"notifications.policy.filter_limited_accounts_title": "Modererede konti",
|
||||
"notifications.policy.filter_new_accounts.hint": "Oprettet indenfor {days, plural, one {den seneste dag} other {de seneste # dage}}",
|
||||
"notifications.policy.filter_new_accounts_title": "Ny konti",
|
||||
"notifications.policy.filter_not_followers_hint": "Inklusiv personer, som har fulgt dig {days, plural, one {mindre end én dag} other {færre end # dage}}",
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
"about.not_available": "Αυτές οι πληροφορίες δεν έχουν είναι διαθέσιμες σε αυτόν τον διακομιστή.",
|
||||
"about.powered_by": "Αποκεντρωμένα μέσα κοινωνικής δικτύωσης που βασίζονται στο {mastodon}",
|
||||
"about.rules": "Κανόνες διακομιστή",
|
||||
"account.account_note_header": "Προσωπική σημείωση",
|
||||
"account.add_or_remove_from_list": "Προσθήκη ή Αφαίρεση από λίστες",
|
||||
"account.badges.bot": "Αυτοματοποιημένος",
|
||||
"account.badges.group": "Ομάδα",
|
||||
@@ -170,21 +171,28 @@
|
||||
"confirmations.block.confirm": "Αποκλεισμός",
|
||||
"confirmations.delete.confirm": "Διαγραφή",
|
||||
"confirmations.delete.message": "Σίγουρα θες να διαγράψεις αυτή τη δημοσίευση;",
|
||||
"confirmations.delete.title": "Διαγραφή ανάρτησης;",
|
||||
"confirmations.delete_list.confirm": "Διαγραφή",
|
||||
"confirmations.delete_list.message": "Σίγουρα θες να διαγράψεις οριστικά αυτή τη λίστα;",
|
||||
"confirmations.delete_list.title": "Διαγραφή λίστας;",
|
||||
"confirmations.discard_edit_media.confirm": "Απόρριψη",
|
||||
"confirmations.discard_edit_media.message": "Έχεις μη αποθηκευμένες αλλαγές στην περιγραφή πολυμέσων ή στην προεπισκόπηση, απόρριψη ούτως ή άλλως;",
|
||||
"confirmations.edit.confirm": "Επεξεργασία",
|
||||
"confirmations.edit.message": "Αν το επεξεργαστείς τώρα θα αντικατασταθεί το μήνυμα που συνθέτεις. Είσαι σίγουρος ότι θέλεις να συνεχίσεις;",
|
||||
"confirmations.edit.title": "Αντικατάσταση ανάρτησης;",
|
||||
"confirmations.logout.confirm": "Αποσύνδεση",
|
||||
"confirmations.logout.message": "Σίγουρα θέλεις να αποσυνδεθείς;",
|
||||
"confirmations.logout.title": "Αποσύνδεση;",
|
||||
"confirmations.mute.confirm": "Αποσιώπηση",
|
||||
"confirmations.redraft.confirm": "Διαγραφή & ξαναγράψιμο",
|
||||
"confirmations.redraft.message": "Σίγουρα θέλεις να σβήσεις αυτή την ανάρτηση και να την ξαναγράψεις; Οι προτιμήσεις και προωθήσεις θα χαθούν και οι απαντήσεις στην αρχική ανάρτηση θα μείνουν ορφανές.",
|
||||
"confirmations.redraft.title": "Διαγραφή & επανασύνταξη;",
|
||||
"confirmations.reply.confirm": "Απάντησε",
|
||||
"confirmations.reply.message": "Απαντώντας τώρα θα αντικαταστήσεις το κείμενο που ήδη γράφεις. Σίγουρα θέλεις να συνεχίσεις;",
|
||||
"confirmations.reply.title": "Αντικατάσταση ανάρτησης;",
|
||||
"confirmations.unfollow.confirm": "Άρση ακολούθησης",
|
||||
"confirmations.unfollow.message": "Σίγουρα θες να πάψεις να ακολουθείς τον/την {name};",
|
||||
"confirmations.unfollow.title": "Άρση ακολούθησης;",
|
||||
"conversation.delete": "Διαγραφή συζήτησης",
|
||||
"conversation.mark_as_read": "Σήμανση ως αναγνωσμένο",
|
||||
"conversation.open": "Προβολή συνομιλίας",
|
||||
@@ -292,6 +300,7 @@
|
||||
"filter_modal.select_filter.subtitle": "Χρησιμοποιήστε μια υπάρχουσα κατηγορία ή δημιουργήστε μια νέα",
|
||||
"filter_modal.select_filter.title": "Φιλτράρισμα αυτής της ανάρτησης",
|
||||
"filter_modal.title.status": "Φιλτράρισμα μιας ανάρτησης",
|
||||
"filtered_notifications_banner.pending_requests": "Από {count, plural, =0 {κανένα} one {ένα άτομο} other {# άτομα}} που μπορεί να ξέρεις",
|
||||
"filtered_notifications_banner.title": "Φιλτραρισμένες ειδοποιήσεις",
|
||||
"firehose.all": "Όλα",
|
||||
"firehose.local": "Αυτός ο διακομιστής",
|
||||
@@ -496,10 +505,13 @@
|
||||
"notification.update": "ο/η {name} επεξεργάστηκε μια ανάρτηση",
|
||||
"notification_requests.accept": "Αποδοχή",
|
||||
"notification_requests.dismiss": "Απόρριψη",
|
||||
"notification_requests.maximize": "Μεγιστοποίηση",
|
||||
"notification_requests.minimize_banner": "Ελαχιστοποίηση μπάνερ φιλτραρισμένων ειδοποιήσεων",
|
||||
"notification_requests.notifications_from": "Ειδοποιήσεις από {name}",
|
||||
"notification_requests.title": "Φιλτραρισμένες ειδοποιήσεις",
|
||||
"notifications.clear": "Καθαρισμός ειδοποιήσεων",
|
||||
"notifications.clear_confirmation": "Σίγουρα θέλεις να καθαρίσεις μόνιμα όλες τις ειδοποιήσεις σου;",
|
||||
"notifications.clear_title": "Εκκαθάριση ειδοποιήσεων;",
|
||||
"notifications.column_settings.admin.report": "Νέες αναφορές:",
|
||||
"notifications.column_settings.admin.sign_up": "Νέες εγγραφές:",
|
||||
"notifications.column_settings.alert": "Ειδοποιήσεις επιφάνειας εργασίας",
|
||||
|
||||
@@ -505,6 +505,8 @@
|
||||
"notification.update": "{name} edited a post",
|
||||
"notification_requests.accept": "Accept",
|
||||
"notification_requests.dismiss": "Dismiss",
|
||||
"notification_requests.explainer_for_limited_account": "Notifications from this account have been filtered because the account has been limited by a moderator.",
|
||||
"notification_requests.explainer_for_limited_remote_account": "Notifications from this account have been filtered because the account or its server has been limited by a moderator.",
|
||||
"notification_requests.maximize": "Maximize",
|
||||
"notification_requests.minimize_banner": "Minimize filtered notifications banner",
|
||||
"notification_requests.notifications_from": "Notifications from {name}",
|
||||
@@ -545,6 +547,8 @@
|
||||
"notifications.permission_denied": "Desktop notifications are unavailable due to previously denied browser permissions request",
|
||||
"notifications.permission_denied_alert": "Desktop notifications can't be enabled, as browser permission has been denied before",
|
||||
"notifications.permission_required": "Desktop notifications are unavailable because the required permission has not been granted.",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Limited by server moderators",
|
||||
"notifications.policy.filter_limited_accounts_title": "Moderated accounts",
|
||||
"notifications.policy.filter_new_accounts.hint": "Created within the past {days, plural, one {one day} other {# days}}",
|
||||
"notifications.policy.filter_new_accounts_title": "New accounts",
|
||||
"notifications.policy.filter_not_followers_hint": "Including people who have been following you fewer than {days, plural, one {one day} other {# days}}",
|
||||
|
||||
@@ -545,6 +545,8 @@
|
||||
"notifications.permission_denied": "Las notificaciones de escritorio no están disponibles, debido a una solicitud de permiso del navegador web previamente denegada",
|
||||
"notifications.permission_denied_alert": "No se pueden habilitar las notificaciones de escritorio, ya que el permiso del navegador fue denegado antes",
|
||||
"notifications.permission_required": "Las notificaciones de escritorio no están disponibles porque no se concedió el permiso requerido.",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Limitada por los moderadores del servidor",
|
||||
"notifications.policy.filter_limited_accounts_title": "Cuentas moderadas",
|
||||
"notifications.policy.filter_new_accounts.hint": "Creada hace {days, plural, one {un día} other {# días}}",
|
||||
"notifications.policy.filter_new_accounts_title": "Nuevas cuentas",
|
||||
"notifications.policy.filter_not_followers_hint": "Incluyendo cuentas que te han estado siguiendo menos de {days, plural, one {un día} other {# días}}",
|
||||
|
||||
@@ -545,6 +545,8 @@
|
||||
"notifications.permission_denied": "Työpöytäilmoitukset eivät ole käytettävissä, koska selaimen käyttöoikeuspyyntö on aiemmin evätty",
|
||||
"notifications.permission_denied_alert": "Työpöytäilmoituksia ei voi ottaa käyttöön, koska selaimen käyttöoikeus on aiemmin evätty",
|
||||
"notifications.permission_required": "Työpöytäilmoitukset eivät ole käytettävissä, koska siihen tarvittavaa käyttöoikeutta ei ole myönnetty.",
|
||||
"notifications.policy.filter_limited_accounts_hint": "Palvelimen moderaattorien rajoittamat",
|
||||
"notifications.policy.filter_limited_accounts_title": "Moderoidut tilit",
|
||||
"notifications.policy.filter_new_accounts.hint": "Luotu {days, plural, one {viime päivän} other {viimeisen # päivän}} aikana",
|
||||
"notifications.policy.filter_new_accounts_title": "Uudet tilit",
|
||||
"notifications.policy.filter_not_followers_hint": "Mukaan lukien alle {days, plural, one {päivän} other {# päivää}} sinua seuranneet",
|
||||
|
||||
@@ -75,8 +75,9 @@
|
||||
"block_modal.show_less": "Ssken-d drus",
|
||||
"block_modal.show_more": "Ssken-d ugar",
|
||||
"block_modal.they_cant_mention": "Ur zmiren ad k·m-id-bedren, ur zmiren ad k·m-ḍefren.",
|
||||
"block_modal.they_cant_see_posts": "Ur zmiren ad walin tisufaɣ-nwen, ur tettwalim tid-nsen.",
|
||||
"block_modal.they_cant_see_posts": "Ur yezmir ad wali tisuffaɣ-ik·im, ur tettwaliḍ tidak-is.",
|
||||
"block_modal.title": "Sewḥel aseqdac ?",
|
||||
"block_modal.you_wont_see_mentions": "Ur tezmireḍ ara ad twaliḍ tisuffaɣ anda d-yettwabdar.",
|
||||
"boost_modal.combo": "Tzemreḍ ad tsiteḍ ɣef {combo} akken ad tzegleḍ aya tikelt i d-iteddun",
|
||||
"bundle_column_error.copy_stacktrace": "Nɣel tuccḍa n uneqqis",
|
||||
"bundle_column_error.error.title": "Uh, ala !",
|
||||
@@ -176,7 +177,9 @@
|
||||
"dismissable_banner.explore_tags": "D wiyi i d ihacṭagen i d-yettawin tamyigawt deg web anmetti ass-a. Ihacṭagen i sseqdacen ugar n medden, εlayit d imezwura.",
|
||||
"domain_block_modal.block": "Sewḥel aqeddac",
|
||||
"domain_block_modal.they_cant_follow": "Yiwen ur yezmir ad k·m-id-yeḍfer seg uqeddac-a.",
|
||||
"domain_block_modal.they_wont_know": "Ur-d yettawi ara s lexbaṛ belli yettuseḥbes.",
|
||||
"domain_block_modal.title": "Sewḥel taɣult?",
|
||||
"domain_block_modal.you_wont_see_posts": "Ur tettuɣaleḍ ara ttwaliḍ tisuffaɣ neɣ ulɣuten n iseqdacen n uqeddac-a.",
|
||||
"domain_pill.activitypub_like_language": "ActivityPub am tutlayt yettmeslay Mastodon d izeḍwan inmettiyen nniḍen.",
|
||||
"domain_pill.server": "Aqeddac",
|
||||
"domain_pill.username": "Isem n useqdac",
|
||||
@@ -348,9 +351,15 @@
|
||||
"load_pending": "{count, plural, one {# n uferdis amaynut} other {# n yiferdisen imaynuten}}",
|
||||
"loading_indicator.label": "Yessalay-d …",
|
||||
"media_gallery.toggle_visible": "{number, plural, one {Ffer tugna} other {Ffer tugniwin}}",
|
||||
"mute_modal.hide_from_notifications": "Ffer-it deg ulɣuten",
|
||||
"mute_modal.hide_options": "Ffer tinefrunin",
|
||||
"mute_modal.indefinite": "Alamma ssnesreɣ asgugem fell-as",
|
||||
"mute_modal.show_options": "Sken-d tinefrunin",
|
||||
"mute_modal.title": "Sgugem aseqdac?",
|
||||
"mute_modal.they_can_mention_and_follow": "Yezmer ad k·m-id-yebder, ad k·m-yeḍfer, maca ur t-tettwaliḍt ara.",
|
||||
"mute_modal.they_wont_know": "Ur yezmir ara ad iẓer dakken tesgugmeṭ.",
|
||||
"mute_modal.title": "Asgugem n useqdac?",
|
||||
"mute_modal.you_wont_see_mentions": "Ur tezmireḍ ara ad twaliḍ tisuffaɣ anda d-yettwabdar.",
|
||||
"mute_modal.you_wont_see_posts": "Yezmer ad yettwali tisuffaɣ-ik·im, maca ur tettwaliḍ ara tidak-is.",
|
||||
"navigation_bar.about": "Ɣef",
|
||||
"navigation_bar.advanced_interface": "Ldi deg ugrudem n web leqqayen",
|
||||
"navigation_bar.blocks": "Iseqdacen yettusḥebsen",
|
||||
|
||||
@@ -545,6 +545,8 @@
|
||||
"notifications.permission_denied": "권한이 거부되었기 때문에 데스크탑 알림을 활성화할 수 없음",
|
||||
"notifications.permission_denied_alert": "이전에 브라우저 권한이 거부되었기 때문에, 데스크탑 알림이 활성화 될 수 없습니다.",
|
||||
"notifications.permission_required": "필요한 권한이 승인되지 않아 데스크탑 알림을 사용할 수 없습니다.",
|
||||
"notifications.policy.filter_limited_accounts_hint": "서버 중재자에 의해 제한됨",
|
||||
"notifications.policy.filter_limited_accounts_title": "중재된 계정",
|
||||
"notifications.policy.filter_new_accounts.hint": "{days, plural, one {하루} other {#일}} 안에 만들어진",
|
||||
"notifications.policy.filter_new_accounts_title": "새 계정",
|
||||
"notifications.policy.filter_not_followers_hint": "나를 팔로우 한 지 {days, plural, other {# 일}}이 되지 않은 사람들을 포함",
|
||||
|
||||
@@ -610,7 +610,7 @@
|
||||
"poll_button.remove_poll": "移除投票",
|
||||
"privacy.change": "设置嘟文的可见范围",
|
||||
"privacy.direct.long": "帖子中提到的每个人",
|
||||
"privacy.direct.short": "具体的人",
|
||||
"privacy.direct.short": "特定的人",
|
||||
"privacy.private.long": "仅限您的关注者",
|
||||
"privacy.private.short": "关注者",
|
||||
"privacy.public.long": "所有 Mastodon 内外的人",
|
||||
|
||||
@@ -474,8 +474,11 @@ export const notificationGroupsReducer = createReducer<NotificationGroupsState>(
|
||||
state.lastReadId,
|
||||
action.payload.markers.notifications.last_read_id,
|
||||
) < 0
|
||||
)
|
||||
) {
|
||||
state.lastReadId = action.payload.markers.notifications.last_read_id;
|
||||
state.readMarkerId =
|
||||
action.payload.markers.notifications.last_read_id;
|
||||
}
|
||||
})
|
||||
.addCase(mountNotifications, (state) => {
|
||||
state.mounted += 1;
|
||||
|
||||
Reference in new issue
Block a user