Merge commit '5241f7b2fd
' into kb_migration_development
This commit is contained in:
commit
cd452874a7
71 changed files with 875 additions and 287 deletions
|
@ -1,11 +1,12 @@
|
|||
import { createAction } from '@reduxjs/toolkit';
|
||||
|
||||
import type { LayoutType } from '../is_mobile';
|
||||
|
||||
export const focusApp = createAction('APP_FOCUS');
|
||||
export const unfocusApp = createAction('APP_UNFOCUS');
|
||||
|
||||
type ChangeLayoutPayload = {
|
||||
interface ChangeLayoutPayload {
|
||||
layout: LayoutType;
|
||||
};
|
||||
}
|
||||
export const changeLayout =
|
||||
createAction<ChangeLayoutPayload>('APP_LAYOUT_CHANGE');
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
import api from '../api';
|
||||
import { importFetchedStatuses } from './importer';
|
||||
|
||||
import { me } from '../initial_state';
|
||||
|
||||
export const PINNED_STATUSES_FETCH_REQUEST = 'PINNED_STATUSES_FETCH_REQUEST';
|
||||
export const PINNED_STATUSES_FETCH_SUCCESS = 'PINNED_STATUSES_FETCH_SUCCESS';
|
||||
export const PINNED_STATUSES_FETCH_FAIL = 'PINNED_STATUSES_FETCH_FAIL';
|
||||
|
||||
import { me } from '../initial_state';
|
||||
|
||||
export function fetchPinnedStatuses() {
|
||||
return (dispatch, getState) => {
|
||||
dispatch(fetchPinnedStatusesRequest());
|
||||
|
|
|
@ -8,12 +8,12 @@ import { defineMessages, injectIntl } from 'react-intl';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { me } from '../initial_state';
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
import Skeleton from 'mastodon/components/skeleton';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { counterRenderer } from 'mastodon/components/common_counter';
|
||||
import ShortNumber from 'mastodon/components/short_number';
|
||||
import classNames from 'classnames';
|
||||
import { VerifiedBadge } from 'mastodon/components/verified_badge';
|
||||
import { EmptyAccount } from 'mastodon/components/empty_account';
|
||||
|
||||
const messages = defineMessages({
|
||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||
|
@ -78,20 +78,7 @@ class Account extends ImmutablePureComponent {
|
|||
const { account, intl, hidden, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal, children } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return (
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<div className='account__display-name'>
|
||||
<div className='account__avatar-wrapper'><Skeleton width={size} height={size} /></div>
|
||||
|
||||
<div>
|
||||
<DisplayName />
|
||||
<Skeleton width='7ch' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
return <EmptyAccount size={size} minimal={minimal} />;
|
||||
}
|
||||
|
||||
if (hidden) {
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import ShortNumber from './short_number';
|
||||
|
||||
import { TransitionMotion, spring } from 'react-motion';
|
||||
|
||||
import { reduceMotion } from '../initial_state';
|
||||
|
||||
import ShortNumber from './short_number';
|
||||
|
||||
const obfuscatedCount = (count: number) => {
|
||||
if (count < 0) {
|
||||
return 0;
|
||||
|
@ -13,10 +16,10 @@ const obfuscatedCount = (count: number) => {
|
|||
}
|
||||
};
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
value: number;
|
||||
obfuscate?: boolean;
|
||||
};
|
||||
}
|
||||
export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => {
|
||||
const [previousValue, setPreviousValue] = useState(value);
|
||||
const [direction, setDirection] = useState<1 | -1>(1);
|
||||
|
@ -64,7 +67,11 @@ export const AnimatedNumber: React.FC<Props> = ({ value, obfuscate }) => {
|
|||
transform: `translateY(${style.y * 100}%)`,
|
||||
}}
|
||||
>
|
||||
{obfuscate ? obfuscatedCount(data) : <ShortNumber value={data} />}
|
||||
{obfuscate ? (
|
||||
obfuscatedCount(data as number)
|
||||
) : (
|
||||
<ShortNumber value={data as number} />
|
||||
)}
|
||||
</span>
|
||||
))}
|
||||
</span>
|
||||
|
|
|
@ -1,16 +1,18 @@
|
|||
import * as React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
import { useHovering } from '../../hooks/useHovering';
|
||||
import type { Account } from '../../types/resources';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
account: Account;
|
||||
size: number;
|
||||
style?: React.CSSProperties;
|
||||
inline?: boolean;
|
||||
animate?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
export const Avatar: React.FC<Props> = ({
|
||||
account,
|
||||
|
|
|
@ -1,15 +1,16 @@
|
|||
import React from 'react';
|
||||
import type { Account } from '../../types/resources';
|
||||
|
||||
import { useHovering } from '../../hooks/useHovering';
|
||||
import type { Account } from '../../types/resources';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
account: Account;
|
||||
friend: Account;
|
||||
size?: number;
|
||||
baseSize?: number;
|
||||
overlaySize?: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const AvatarOverlay: React.FC<Props> = ({
|
||||
account,
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
import { decode } from 'blurhash';
|
||||
import React, { useRef, useEffect } from 'react';
|
||||
|
||||
type Props = {
|
||||
import { decode } from 'blurhash';
|
||||
|
||||
interface Props extends React.HTMLAttributes<HTMLCanvasElement> {
|
||||
hash: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
dummy?: boolean; // Whether dummy mode is enabled. If enabled, nothing is rendered and canvas left untouched
|
||||
children?: never;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
const Blurhash: React.FC<Props> = ({
|
||||
hash,
|
||||
width = 32,
|
||||
|
@ -21,6 +21,7 @@ const Blurhash: React.FC<Props> = ({
|
|||
useEffect(() => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const canvas = canvasRef.current!;
|
||||
|
||||
// eslint-disable-next-line no-self-assign
|
||||
canvas.width = canvas.width; // resets canvas
|
||||
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
import React from 'react';
|
||||
import { autoPlayGif } from '..//initial_state';
|
||||
import Skeleton from './skeleton';
|
||||
import { Account } from '../../types/resources';
|
||||
import { List } from 'immutable';
|
||||
|
||||
type Props = {
|
||||
account: Account;
|
||||
others: List<Account>;
|
||||
localDomain: string;
|
||||
};
|
||||
import type { List } from 'immutable';
|
||||
|
||||
import type { Account } from '../../types/resources';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
import Skeleton from './skeleton';
|
||||
|
||||
interface Props {
|
||||
account?: Account;
|
||||
others?: List<Account>;
|
||||
localDomain?: string;
|
||||
}
|
||||
|
||||
export class DisplayName extends React.PureComponent<Props> {
|
||||
handleMouseEnter: React.ReactEventHandler<HTMLSpanElement> = ({
|
||||
currentTarget,
|
||||
|
@ -45,7 +49,15 @@ export class DisplayName extends React.PureComponent<Props> {
|
|||
render() {
|
||||
const { others, localDomain } = this.props;
|
||||
|
||||
let displayName: React.ReactNode, suffix: React.ReactNode, account: Account;
|
||||
let displayName: React.ReactNode,
|
||||
suffix: React.ReactNode,
|
||||
account: Account | undefined;
|
||||
|
||||
if (others && others.size > 0) {
|
||||
account = others.first();
|
||||
} else if (this.props.account) {
|
||||
account = this.props.account;
|
||||
}
|
||||
|
||||
if (others && others.size > 1) {
|
||||
displayName = others
|
||||
|
@ -63,13 +75,7 @@ export class DisplayName extends React.PureComponent<Props> {
|
|||
if (others.size - 2 > 0) {
|
||||
suffix = `+${others.size - 2}`;
|
||||
}
|
||||
} else if ((others && others.size > 0) || this.props.account) {
|
||||
if (others && others.size > 0) {
|
||||
account = others.first();
|
||||
} else {
|
||||
account = this.props.account;
|
||||
}
|
||||
|
||||
} else if (account) {
|
||||
let acct = account.get('acct');
|
||||
|
||||
if (acct.indexOf('@') === -1 && localDomain) {
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
import React, { useCallback } from 'react';
|
||||
|
||||
import type { InjectedIntl } from 'react-intl';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import { IconButton } from './icon_button';
|
||||
import { InjectedIntl, defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
unblockDomain: {
|
||||
|
@ -9,11 +12,11 @@ const messages = defineMessages({
|
|||
},
|
||||
});
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
domain: string;
|
||||
onUnblockDomain: (domain: string) => void;
|
||||
intl: InjectedIntl;
|
||||
};
|
||||
}
|
||||
const _Domain: React.FC<Props> = ({ domain, onUnblockDomain, intl }) => {
|
||||
const handleDomainUnblock = useCallback(() => {
|
||||
onUnblockDomain(domain);
|
||||
|
|
33
app/javascript/mastodon/components/empty_account.tsx
Normal file
33
app/javascript/mastodon/components/empty_account.tsx
Normal file
|
@ -0,0 +1,33 @@
|
|||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { DisplayName } from 'mastodon/components/display_name';
|
||||
import Skeleton from 'mastodon/components/skeleton';
|
||||
|
||||
interface Props {
|
||||
size?: number;
|
||||
minimal?: boolean;
|
||||
}
|
||||
|
||||
export const EmptyAccount: React.FC<Props> = ({
|
||||
size = 46,
|
||||
minimal = false,
|
||||
}) => {
|
||||
return (
|
||||
<div className={classNames('account', { 'account--minimal': minimal })}>
|
||||
<div className='account__wrapper'>
|
||||
<div className='account__display-name'>
|
||||
<div className='account__avatar-wrapper'>
|
||||
<Skeleton width={size} height={size} />
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<DisplayName />
|
||||
<Skeleton width='7ch' />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -1,6 +1,6 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
src: string;
|
||||
key: string;
|
||||
alt?: string;
|
||||
|
@ -8,7 +8,7 @@ type Props = {
|
|||
width: number;
|
||||
height: number;
|
||||
onClick?: () => void;
|
||||
};
|
||||
}
|
||||
|
||||
export const GIFV: React.FC<Props> = ({
|
||||
src,
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
interface Props extends React.HTMLAttributes<HTMLImageElement> {
|
||||
id: string;
|
||||
className?: string;
|
||||
fixedWidth?: boolean;
|
||||
children?: never;
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
|
||||
export const Icon: React.FC<Props> = ({
|
||||
id,
|
||||
className,
|
||||
|
|
|
@ -1,9 +1,11 @@
|
|||
import React from 'react';
|
||||
import classNames from 'classnames';
|
||||
import { Icon } from './icon';
|
||||
import { AnimatedNumber } from './animated_number';
|
||||
|
||||
type Props = {
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { AnimatedNumber } from './animated_number';
|
||||
import { Icon } from './icon';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
title: string;
|
||||
icon: string;
|
||||
|
@ -25,11 +27,11 @@ type Props = {
|
|||
obfuscateCount?: boolean;
|
||||
href?: string;
|
||||
ariaHidden: boolean;
|
||||
};
|
||||
type States = {
|
||||
}
|
||||
interface States {
|
||||
activate: boolean;
|
||||
deactivate: boolean;
|
||||
};
|
||||
}
|
||||
export class IconButton extends React.PureComponent<Props, States> {
|
||||
static defaultProps = {
|
||||
size: 18,
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Icon } from './icon';
|
||||
|
||||
const formatNumber = (num: number): number | string => (num > 40 ? '40+' : num);
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
id: string;
|
||||
count: number;
|
||||
issueBadge: boolean;
|
||||
className: string;
|
||||
};
|
||||
}
|
||||
export const IconWithBadge: React.FC<Props> = ({
|
||||
id,
|
||||
count,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
|
||||
import logo from 'mastodon/../images/logo.svg';
|
||||
|
||||
export const WordmarkLogo: React.FC = () => (
|
||||
|
|
|
@ -266,7 +266,7 @@ class MediaGallery extends React.PureComponent {
|
|||
};
|
||||
|
||||
handleClick = (index) => {
|
||||
this.props.onOpenMedia(this.props.media, index);
|
||||
this.props.onOpenMedia(this.props.media, index, this.props.lang);
|
||||
};
|
||||
|
||||
handleRef = c => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import React from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
export const NotSignedInIndicator: React.FC = () => (
|
||||
|
@ -6,7 +7,7 @@ export const NotSignedInIndicator: React.FC = () => (
|
|||
<div className='empty-column-indicator'>
|
||||
<FormattedMessage
|
||||
id='not_signed_in_indicator.not_signed_in'
|
||||
defaultMessage='You need to sign in to access this resource.'
|
||||
defaultMessage='You need to login to access this resource.'
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import React from 'react';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
value: string;
|
||||
checked: boolean;
|
||||
name: string;
|
||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
|
||||
label: React.ReactNode;
|
||||
};
|
||||
}
|
||||
|
||||
export const RadioButton: React.FC<Props> = ({
|
||||
name,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import React from 'react';
|
||||
import { injectIntl, defineMessages, InjectedIntl } from 'react-intl';
|
||||
|
||||
import type { InjectedIntl } from 'react-intl';
|
||||
import { injectIntl, defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
today: { id: 'relative_time.today', defaultMessage: 'today' },
|
||||
|
@ -187,16 +189,16 @@ const timeRemainingString = (
|
|||
return relativeTime;
|
||||
};
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
intl: InjectedIntl;
|
||||
timestamp: string;
|
||||
year: number;
|
||||
futureDate?: boolean;
|
||||
short?: boolean;
|
||||
};
|
||||
type States = {
|
||||
}
|
||||
interface States {
|
||||
now: number;
|
||||
};
|
||||
}
|
||||
class RelativeTimestamp extends React.Component<Props, States> {
|
||||
state = {
|
||||
now: this.props.intl.now(),
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
import React, { useCallback, useState } from 'react';
|
||||
import { Blurhash } from './blurhash';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
type Props = {
|
||||
import { Blurhash } from './blurhash';
|
||||
|
||||
interface Props {
|
||||
src: string;
|
||||
srcSet?: string;
|
||||
blurhash?: string;
|
||||
className?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export const ServerHeroImage: React.FC<Props> = ({
|
||||
src,
|
||||
|
|
|
@ -199,11 +199,12 @@ class Status extends ImmutablePureComponent {
|
|||
|
||||
handleOpenVideo = (options) => {
|
||||
const status = this._properStatus();
|
||||
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), options);
|
||||
this.props.onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), status.get('language'), options);
|
||||
};
|
||||
|
||||
handleOpenMedia = (media, index) => {
|
||||
this.props.onOpenMedia(this._properStatus().get('id'), media, index);
|
||||
const status = this._properStatus();
|
||||
this.props.onOpenMedia(status.get('id'), media, index, status.get('language'));
|
||||
};
|
||||
|
||||
handleHotkeyOpenMedia = e => {
|
||||
|
@ -213,10 +214,11 @@ class Status extends ImmutablePureComponent {
|
|||
e.preventDefault();
|
||||
|
||||
if (status.get('media_attachments').size > 0) {
|
||||
const lang = status.get('language');
|
||||
if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
|
||||
onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), { startTime: 0 });
|
||||
onOpenVideo(status.get('id'), status.getIn(['media_attachments', 0]), lang, { startTime: 0 });
|
||||
} else {
|
||||
onOpenMedia(status.get('id'), status.get('media_attachments'), 0);
|
||||
onOpenMedia(status.get('id'), status.get('media_attachments'), 0, lang);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
import React from 'react';
|
||||
|
||||
import { Icon } from './icon';
|
||||
|
||||
type Props = {
|
||||
interface Props {
|
||||
link: string;
|
||||
};
|
||||
}
|
||||
export const VerifiedBadge: React.FC<Props> = ({ link }) => (
|
||||
<span className='verified-badge'>
|
||||
<Icon id='check' className='verified-badge__mark' />
|
||||
|
|
|
@ -29,19 +29,20 @@ export default class MediaContainer extends PureComponent {
|
|||
state = {
|
||||
media: null,
|
||||
index: null,
|
||||
lang: null,
|
||||
time: null,
|
||||
backgroundColor: null,
|
||||
options: null,
|
||||
};
|
||||
|
||||
handleOpenMedia = (media, index) => {
|
||||
handleOpenMedia = (media, index, lang) => {
|
||||
document.body.classList.add('with-modals--active');
|
||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
||||
|
||||
this.setState({ media, index });
|
||||
this.setState({ media, index, lang });
|
||||
};
|
||||
|
||||
handleOpenVideo = (options) => {
|
||||
handleOpenVideo = (lang, options) => {
|
||||
const { components } = this.props;
|
||||
const { media } = JSON.parse(components[options.componentIndex].getAttribute('data-props'));
|
||||
const mediaList = fromJS(media);
|
||||
|
@ -49,7 +50,7 @@ export default class MediaContainer extends PureComponent {
|
|||
document.body.classList.add('with-modals--active');
|
||||
document.documentElement.style.marginRight = `${getScrollbarWidth()}px`;
|
||||
|
||||
this.setState({ media: mediaList, options });
|
||||
this.setState({ media: mediaList, lang, options });
|
||||
};
|
||||
|
||||
handleCloseMedia = () => {
|
||||
|
@ -105,6 +106,7 @@ export default class MediaContainer extends PureComponent {
|
|||
<MediaModal
|
||||
media={this.state.media}
|
||||
index={this.state.index || 0}
|
||||
lang={this.state.lang}
|
||||
currentTime={this.state.options?.startTime}
|
||||
autoPlay={this.state.options?.autoPlay}
|
||||
volume={this.state.options?.defaultVolume}
|
||||
|
|
|
@ -192,12 +192,12 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
|
|||
dispatch(mentionCompose(account, router));
|
||||
},
|
||||
|
||||
onOpenMedia (statusId, media, index) {
|
||||
dispatch(openModal('MEDIA', { statusId, media, index }));
|
||||
onOpenMedia (statusId, media, index, lang) {
|
||||
dispatch(openModal('MEDIA', { statusId, media, index, lang }));
|
||||
},
|
||||
|
||||
onOpenVideo (statusId, media, options) {
|
||||
dispatch(openModal('VIDEO', { statusId, media, options }));
|
||||
onOpenVideo (statusId, media, lang, options) {
|
||||
dispatch(openModal('VIDEO', { statusId, media, lang, options }));
|
||||
},
|
||||
|
||||
onBlock (status) {
|
||||
|
|
|
@ -136,16 +136,17 @@ class AccountGallery extends ImmutablePureComponent {
|
|||
handleOpenMedia = attachment => {
|
||||
const { dispatch } = this.props;
|
||||
const statusId = attachment.getIn(['status', 'id']);
|
||||
const lang = attachment.getIn(['status', 'language']);
|
||||
|
||||
if (attachment.get('type') === 'video') {
|
||||
dispatch(openModal('VIDEO', { media: attachment, statusId, options: { autoPlay: true } }));
|
||||
dispatch(openModal('VIDEO', { media: attachment, statusId, lang, options: { autoPlay: true } }));
|
||||
} else if (attachment.get('type') === 'audio') {
|
||||
dispatch(openModal('AUDIO', { media: attachment, statusId, options: { autoPlay: true } }));
|
||||
dispatch(openModal('AUDIO', { media: attachment, statusId, lang, options: { autoPlay: true } }));
|
||||
} else {
|
||||
const media = attachment.getIn(['status', 'media_attachments']);
|
||||
const index = media.findIndex(x => x.get('id') === attachment.get('id'));
|
||||
|
||||
dispatch(openModal('MEDIA', { media, index, statusId }));
|
||||
dispatch(openModal('MEDIA', { media, index, statusId, lang }));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { connect } from 'react-redux';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import { lookupAccount, fetchAccount } from '../../actions/accounts';
|
||||
import { expandAccountFeaturedTimeline, expandAccountTimeline } from '../../actions/timelines';
|
||||
import { expandAccountFeaturedTimeline, expandAccountTimeline, connectTimeline, disconnectTimeline } from '../../actions/timelines';
|
||||
import StatusList from '../../components/status_list';
|
||||
import LoadingIndicator from '../../components/loading_indicator';
|
||||
import Column from '../ui/components/column';
|
||||
|
@ -14,7 +14,6 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import { FormattedMessage } from 'react-intl';
|
||||
import TimelineHint from 'mastodon/components/timeline_hint';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
import { connectTimeline, disconnectTimeline } from 'mastodon/actions/timelines';
|
||||
import LimitedAccountHint from './components/limited_account_hint';
|
||||
import { getAccountHidden } from 'mastodon/selectors';
|
||||
import { fetchFeaturedTags } from '../../actions/featured_tags';
|
||||
|
|
|
@ -148,7 +148,7 @@ class InteractionModal extends React.PureComponent {
|
|||
<div className='interaction-modal__choices'>
|
||||
<div className='interaction-modal__choices__choice'>
|
||||
<h3><FormattedMessage id='interaction_modal.on_this_server' defaultMessage='On this server' /></h3>
|
||||
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
||||
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||
{signupButton}
|
||||
</div>
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { fetchSuggestions } from 'mastodon/actions/suggestions';
|
|||
import { markAsPartial } from 'mastodon/actions/timelines';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import Account from 'mastodon/containers/account_container';
|
||||
import EmptyAccount from 'mastodon/components/account';
|
||||
import { EmptyAccount } from 'mastodon/components/empty_account';
|
||||
import { FormattedMessage, FormattedHTMLMessage } from 'react-intl';
|
||||
import { makeGetAccount } from 'mastodon/selectors';
|
||||
import { me } from 'mastodon/initial_state';
|
||||
|
@ -31,6 +31,7 @@ class Follows extends React.PureComponent {
|
|||
suggestions: ImmutablePropTypes.list,
|
||||
account: ImmutablePropTypes.map,
|
||||
isLoading: PropTypes.bool,
|
||||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
componentDidMount () {
|
||||
|
@ -44,7 +45,7 @@ class Follows extends React.PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { onBack, isLoading, suggestions, account } = this.props;
|
||||
const { onBack, isLoading, suggestions, account, multiColumn } = this.props;
|
||||
|
||||
let loadedContent;
|
||||
|
||||
|
@ -58,7 +59,7 @@ class Follows extends React.PureComponent {
|
|||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton onClick={onBack} />
|
||||
<ColumnBackButton multiColumn={multiColumn} onClick={onBack} />
|
||||
|
||||
<div className='scrollable privacy-policy'>
|
||||
<div className='column-title'>
|
||||
|
@ -84,4 +85,4 @@ class Follows extends React.PureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(Follows);
|
||||
export default connect(mapStateToProps)(Follows);
|
||||
|
|
|
@ -40,6 +40,7 @@ class Onboarding extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
account: ImmutablePropTypes.map,
|
||||
multiColumn: PropTypes.bool,
|
||||
};
|
||||
|
||||
state = {
|
||||
|
@ -93,14 +94,14 @@ class Onboarding extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { account } = this.props;
|
||||
const { account, multiColumn } = this.props;
|
||||
const { step, shareClicked } = this.state;
|
||||
|
||||
switch(step) {
|
||||
case 'follows':
|
||||
return <Follows onBack={this.handleBackClick} />;
|
||||
return <Follows onBack={this.handleBackClick} multiColumn={multiColumn} />;
|
||||
case 'share':
|
||||
return <Share onBack={this.handleBackClick} />;
|
||||
return <Share onBack={this.handleBackClick} multiColumn={multiColumn} />;
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -140,17 +140,18 @@ class Share extends React.PureComponent {
|
|||
static propTypes = {
|
||||
onBack: PropTypes.func,
|
||||
account: ImmutablePropTypes.map,
|
||||
multiColumn: PropTypes.bool,
|
||||
intl: PropTypes.object,
|
||||
};
|
||||
|
||||
render () {
|
||||
const { onBack, account, intl } = this.props;
|
||||
const { onBack, account, multiColumn, intl } = this.props;
|
||||
|
||||
const url = (new URL(`/@${account.get('username')}`, document.baseURI)).href;
|
||||
|
||||
return (
|
||||
<Column>
|
||||
<ColumnBackButton onClick={onBack} />
|
||||
<ColumnBackButton multiColumn={multiColumn} onClick={onBack} />
|
||||
|
||||
<div className='scrollable privacy-policy'>
|
||||
<div className='column-title'>
|
||||
|
|
|
@ -138,12 +138,12 @@ const mapDispatchToProps = (dispatch, { intl }) => ({
|
|||
dispatch(mentionCompose(account, router));
|
||||
},
|
||||
|
||||
onOpenMedia (media, index) {
|
||||
dispatch(openModal('MEDIA', { media, index }));
|
||||
onOpenMedia (media, index, lang) {
|
||||
dispatch(openModal('MEDIA', { media, index, lang }));
|
||||
},
|
||||
|
||||
onOpenVideo (media, options) {
|
||||
dispatch(openModal('VIDEO', { media, options }));
|
||||
onOpenVideo (media, lang, options) {
|
||||
dispatch(openModal('VIDEO', { media, lang, options }));
|
||||
},
|
||||
|
||||
onBlock (status) {
|
||||
|
|
|
@ -367,12 +367,12 @@ class Status extends ImmutablePureComponent {
|
|||
this.props.dispatch(mentionCompose(account, router));
|
||||
};
|
||||
|
||||
handleOpenMedia = (media, index) => {
|
||||
this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index }));
|
||||
handleOpenMedia = (media, index, lang) => {
|
||||
this.props.dispatch(openModal('MEDIA', { statusId: this.props.status.get('id'), media, index, lang }));
|
||||
};
|
||||
|
||||
handleOpenVideo = (media, options) => {
|
||||
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, options }));
|
||||
handleOpenVideo = (media, lang, options) => {
|
||||
this.props.dispatch(openModal('VIDEO', { statusId: this.props.status.get('id'), media, lang, options }));
|
||||
};
|
||||
|
||||
handleHotkeyOpenMedia = e => {
|
||||
|
|
|
@ -19,7 +19,7 @@ import {
|
|||
BookmarkedStatuses,
|
||||
ListTimeline,
|
||||
Directory,
|
||||
} from '../../ui/util/async-components';
|
||||
} from '../util/async-components';
|
||||
import ComposePanel from './compose_panel';
|
||||
import NavigationPanel from './navigation_panel';
|
||||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
|
|
|
@ -5,11 +5,10 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import { changeUploadCompose, uploadThumbnail, onChangeMediaDescription, onChangeMediaFocus } from '../../../actions/compose';
|
||||
import { getPointerPosition } from '../../video';
|
||||
import Video, { getPointerPosition } from '../../video';
|
||||
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
import Button from 'mastodon/components/button';
|
||||
import Video from 'mastodon/features/video';
|
||||
import Audio from 'mastodon/features/audio';
|
||||
import Textarea from 'react-textarea-autosize';
|
||||
import UploadProgress from 'mastodon/features/compose/components/upload_progress';
|
||||
|
|
|
@ -51,13 +51,13 @@ class Header extends React.PureComponent {
|
|||
|
||||
if (registrationsOpen) {
|
||||
signupButton = (
|
||||
<a href='/auth/sign_up' className='button button-tertiary'>
|
||||
<a href='/auth/sign_up' className='button'>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
signupButton = (
|
||||
<button className='button button-tertiary' onClick={openClosedRegistrationsModal}>
|
||||
<button className='button' onClick={openClosedRegistrationsModal}>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</button>
|
||||
);
|
||||
|
@ -65,8 +65,8 @@ class Header extends React.PureComponent {
|
|||
|
||||
content = (
|
||||
<>
|
||||
<a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
||||
{signupButton}
|
||||
<a href='/auth/sign_in' className='button button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ import ReactSwipeableViews from 'react-swipeable-views';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import PropTypes from 'prop-types';
|
||||
import Video from 'mastodon/features/video';
|
||||
import { connect } from 'react-redux';
|
||||
import classNames from 'classnames';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
import { IconButton } from 'mastodon/components/icon_button';
|
||||
|
@ -21,15 +20,12 @@ const messages = defineMessages({
|
|||
next: { id: 'lightbox.next', defaultMessage: 'Next' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { statusId }) => ({
|
||||
language: state.getIn(['statuses', statusId, 'language']),
|
||||
});
|
||||
|
||||
class MediaModal extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
media: ImmutablePropTypes.list.isRequired,
|
||||
statusId: PropTypes.string,
|
||||
lang: PropTypes.string,
|
||||
index: PropTypes.number.isRequired,
|
||||
onClose: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
@ -133,7 +129,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||
};
|
||||
|
||||
render () {
|
||||
const { media, language, statusId, intl, onClose } = this.props;
|
||||
const { media, statusId, lang, intl, onClose } = this.props;
|
||||
const { navigationHidden } = this.state;
|
||||
|
||||
const index = this.getIndex();
|
||||
|
@ -153,7 +149,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||
width={width}
|
||||
height={height}
|
||||
alt={image.get('description')}
|
||||
lang={language}
|
||||
lang={lang}
|
||||
key={image.get('url')}
|
||||
onClick={this.toggleNavigation}
|
||||
zoomButtonHidden={this.state.zoomButtonHidden}
|
||||
|
@ -176,7 +172,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||
onCloseVideo={onClose}
|
||||
detailed
|
||||
alt={image.get('description')}
|
||||
lang={language}
|
||||
lang={lang}
|
||||
key={image.get('url')}
|
||||
/>
|
||||
);
|
||||
|
@ -188,7 +184,7 @@ class MediaModal extends ImmutablePureComponent {
|
|||
height={height}
|
||||
key={image.get('url')}
|
||||
alt={image.get('description')}
|
||||
lang={language}
|
||||
lang={lang}
|
||||
onClick={this.toggleNavigation}
|
||||
/>
|
||||
);
|
||||
|
@ -256,4 +252,4 @@ class MediaModal extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, null, null, { forwardRef: true })(injectIntl(MediaModal));
|
||||
export default injectIntl(MediaModal);
|
||||
|
|
|
@ -16,13 +16,13 @@ const SignInBanner = () => {
|
|||
|
||||
if (registrationsOpen) {
|
||||
signupButton = (
|
||||
<a href='/auth/sign_up' className='button button--block button-tertiary'>
|
||||
<a href='/auth/sign_up' className='button button--block'>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</a>
|
||||
);
|
||||
} else {
|
||||
signupButton = (
|
||||
<button className='button button--block button-tertiary' onClick={openClosedRegistrationsModal}>
|
||||
<button className='button button--block' onClick={openClosedRegistrationsModal}>
|
||||
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
|
||||
</button>
|
||||
);
|
||||
|
@ -30,9 +30,9 @@ const SignInBanner = () => {
|
|||
|
||||
return (
|
||||
<div className='sign-in-banner'>
|
||||
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.' /></p>
|
||||
<a href='/auth/sign_in' className='button button--block'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
|
||||
<p><FormattedMessage id='sign_in_banner.text' defaultMessage='Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.' /></p>
|
||||
{signupButton}
|
||||
<a href='/auth/sign_in' className='button button--block button-tertiary'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Login' /></a>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import Motion from '../../ui/util/optional_motion';
|
||||
import Motion from '../util/optional_motion';
|
||||
import spring from 'react-motion/lib/spring';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
|
|
|
@ -469,7 +469,7 @@ class Video extends React.PureComponent {
|
|||
handleOpenVideo = () => {
|
||||
this.video.pause();
|
||||
|
||||
this.props.onOpenVideo({
|
||||
this.props.onOpenVideo(this.props.lang, {
|
||||
startTime: this.video.currentTime,
|
||||
autoPlay: !this.state.paused,
|
||||
defaultVolume: this.state.volume,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { supportsPassiveEvents } from 'detect-passive-events';
|
||||
|
||||
import { forceSingleColumn } from './initial_state';
|
||||
|
||||
const LAYOUT_BREAKPOINT = 630;
|
||||
|
|
|
@ -356,7 +356,7 @@
|
|||
{
|
||||
"descriptors": [
|
||||
{
|
||||
"defaultMessage": "You need to sign in to access this resource.",
|
||||
"defaultMessage": "You need to login to access this resource.",
|
||||
"id": "not_signed_in_indicator.not_signed_in"
|
||||
}
|
||||
],
|
||||
|
@ -2623,7 +2623,7 @@
|
|||
"id": "interaction_modal.on_this_server"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Sign in",
|
||||
"defaultMessage": "Login",
|
||||
"id": "sign_in_banner.sign_in"
|
||||
},
|
||||
{
|
||||
|
@ -4175,7 +4175,7 @@
|
|||
"id": "sign_in_banner.create_account"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Sign in",
|
||||
"defaultMessage": "Login",
|
||||
"id": "sign_in_banner.sign_in"
|
||||
}
|
||||
],
|
||||
|
@ -4374,11 +4374,11 @@
|
|||
"id": "sign_in_banner.create_account"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
|
||||
"defaultMessage": "Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
|
||||
"id": "sign_in_banner.text"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Sign in",
|
||||
"defaultMessage": "Login",
|
||||
"id": "sign_in_banner.sign_in"
|
||||
}
|
||||
],
|
||||
|
|
|
@ -394,7 +394,7 @@
|
|||
"navigation_bar.public_timeline": "Federated timeline",
|
||||
"navigation_bar.search": "Search",
|
||||
"navigation_bar.security": "Security",
|
||||
"not_signed_in_indicator.not_signed_in": "You need to sign in to access this resource.",
|
||||
"not_signed_in_indicator.not_signed_in": "You need to login to access this resource.",
|
||||
"notification.admin.report": "{name} reported {target}",
|
||||
"notification.admin.sign_up": "{name} signed up",
|
||||
"notification.emoji_reaction": "{name} reacted your post with emoji",
|
||||
|
@ -588,8 +588,8 @@
|
|||
"server_banner.learn_more": "Learn more",
|
||||
"server_banner.server_stats": "Server stats:",
|
||||
"sign_in_banner.create_account": "Create account",
|
||||
"sign_in_banner.sign_in": "Sign in",
|
||||
"sign_in_banner.text": "Sign in to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
|
||||
"sign_in_banner.sign_in": "Login",
|
||||
"sign_in_banner.text": "Login to follow profiles or hashtags, favourite, share and reply to posts. You can also interact from your account on a different server.",
|
||||
"status.admin_account": "Open moderation interface for @{name}",
|
||||
"status.admin_domain": "Open moderation interface for {domain}",
|
||||
"status.admin_status": "Open this post in the moderation interface",
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*eslint no-nested-ternary: "off"*/
|
||||
/*eslint quotes: "off"*/
|
||||
|
||||
export default [{
|
||||
const rules = [{
|
||||
locale: "co",
|
||||
pluralRuleFunction: function (e, a) {
|
||||
return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
|
||||
|
@ -106,3 +106,5 @@ export default [{
|
|||
},
|
||||
},
|
||||
}];
|
||||
|
||||
export default rules;
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
/*eslint no-nested-ternary: "off"*/
|
||||
/*eslint quotes: "off"*/
|
||||
|
||||
export default [{
|
||||
const rules = [{
|
||||
locale: "oc",
|
||||
pluralRuleFunction: function (e, a) {
|
||||
return a ? 1 == e ? "one" : "other" : e >= 0 && e < 2 ? "one" : "other";
|
||||
|
@ -106,3 +106,5 @@ export default [{
|
|||
},
|
||||
},
|
||||
}];
|
||||
|
||||
export default rules;
|
||||
|
|
|
@ -2,9 +2,8 @@
|
|||
/*eslint no-nested-ternary: "off"*/
|
||||
/*eslint quotes: "off"*/
|
||||
/*eslint comma-dangle: "off"*/
|
||||
/*eslint semi: "off"*/
|
||||
|
||||
export default [
|
||||
const rules = [
|
||||
{
|
||||
locale: "sa",
|
||||
fields: {
|
||||
|
@ -94,4 +93,6 @@ export default [
|
|||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
];
|
||||
|
||||
export default rules;
|
||||
|
|
|
@ -10,8 +10,13 @@ if (!HTMLCanvasElement.prototype.toBlob) {
|
|||
const BASE64_MARKER = ';base64,';
|
||||
|
||||
Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', {
|
||||
value(callback: BlobCallback, type = 'image/png', quality: any) {
|
||||
const dataURL = this.toDataURL(type, quality);
|
||||
value: function (
|
||||
this: HTMLCanvasElement,
|
||||
callback: BlobCallback,
|
||||
type = 'image/png',
|
||||
quality: unknown
|
||||
) {
|
||||
const dataURL: string = this.toDataURL(type, quality);
|
||||
let data;
|
||||
|
||||
if (dataURL.indexOf(BASE64_MARKER) >= 0) {
|
||||
|
|
|
@ -1,49 +1,50 @@
|
|||
import { combineReducers } from 'redux-immutable';
|
||||
import dropdown_menu from './dropdown_menu';
|
||||
import timelines from './timelines';
|
||||
import meta from './meta';
|
||||
import alerts from './alerts';
|
||||
import { loadingBarReducer } from 'react-redux-loading-bar';
|
||||
import modal from './modal';
|
||||
import user_lists from './user_lists';
|
||||
import domain_lists from './domain_lists';
|
||||
import { combineReducers } from 'redux-immutable';
|
||||
|
||||
import accounts from './accounts';
|
||||
import accounts_counters from './accounts_counters';
|
||||
import statuses from './statuses';
|
||||
import relationships from './relationships';
|
||||
import settings from './settings';
|
||||
import push_notifications from './push_notifications';
|
||||
import status_lists from './status_lists';
|
||||
import mutes from './mutes';
|
||||
import accounts_map from './accounts_map';
|
||||
import alerts from './alerts';
|
||||
import announcements from './announcements';
|
||||
import antennaAdder from './antenna_adder';
|
||||
import antennaEditor from './antenna_editor';
|
||||
import antennas from './antennas';
|
||||
import blocks from './blocks';
|
||||
import boosts from './boosts';
|
||||
import server from './server';
|
||||
import contexts from './contexts';
|
||||
import compose from './compose';
|
||||
import search from './search';
|
||||
import media_attachments from './media_attachments';
|
||||
import notifications from './notifications';
|
||||
import height_cache from './height_cache';
|
||||
import custom_emojis from './custom_emojis';
|
||||
import lists from './lists';
|
||||
import listEditor from './list_editor';
|
||||
import listAdder from './list_adder';
|
||||
import antennas from './antennas';
|
||||
import antennaEditor from './antenna_editor';
|
||||
import antennaAdder from './antenna_adder';
|
||||
import filters from './filters';
|
||||
import contexts from './contexts';
|
||||
import conversations from './conversations';
|
||||
import suggestions from './suggestions';
|
||||
import polls from './polls';
|
||||
import trends from './trends';
|
||||
import { missedUpdatesReducer } from './missed_updates';
|
||||
import announcements from './announcements';
|
||||
import markers from './markers';
|
||||
import picture_in_picture from './picture_in_picture';
|
||||
import accounts_map from './accounts_map';
|
||||
import history from './history';
|
||||
import tags from './tags';
|
||||
import custom_emojis from './custom_emojis';
|
||||
import domain_lists from './domain_lists';
|
||||
import dropdown_menu from './dropdown_menu';
|
||||
import filters from './filters';
|
||||
import followed_tags from './followed_tags';
|
||||
import height_cache from './height_cache';
|
||||
import history from './history';
|
||||
import listAdder from './list_adder';
|
||||
import listEditor from './list_editor';
|
||||
import lists from './lists';
|
||||
import markers from './markers';
|
||||
import media_attachments from './media_attachments';
|
||||
import meta from './meta';
|
||||
import { missedUpdatesReducer } from './missed_updates';
|
||||
import modal from './modal';
|
||||
import mutes from './mutes';
|
||||
import notifications from './notifications';
|
||||
import picture_in_picture from './picture_in_picture';
|
||||
import polls from './polls';
|
||||
import push_notifications from './push_notifications';
|
||||
import relationships from './relationships';
|
||||
import search from './search';
|
||||
import server from './server';
|
||||
import settings from './settings';
|
||||
import status_lists from './status_lists';
|
||||
import statuses from './statuses';
|
||||
import suggestions from './suggestions';
|
||||
import tags from './tags';
|
||||
import timelines from './timelines';
|
||||
import trends from './trends';
|
||||
import user_lists from './user_lists';
|
||||
|
||||
const reducers = {
|
||||
announcements,
|
||||
|
|
|
@ -2,13 +2,13 @@ import {
|
|||
MARKERS_SUBMIT_SUCCESS,
|
||||
} from '../actions/markers';
|
||||
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
home: '0',
|
||||
notifications: '0',
|
||||
});
|
||||
|
||||
import { Map as ImmutableMap } from 'immutable';
|
||||
|
||||
export default function markers(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case MARKERS_SUBMIT_SUCCESS:
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import { Record } from 'immutable';
|
||||
import type { Action } from 'redux';
|
||||
import { NOTIFICATIONS_UPDATE } from '../actions/notifications';
|
||||
import { focusApp, unfocusApp } from '../actions/app';
|
||||
|
||||
type MissedUpdatesState = {
|
||||
import type { Action } from 'redux';
|
||||
|
||||
import { focusApp, unfocusApp } from '../actions/app';
|
||||
import { NOTIFICATIONS_UPDATE } from '../actions/notifications';
|
||||
|
||||
interface MissedUpdatesState {
|
||||
focused: boolean;
|
||||
unread: number;
|
||||
};
|
||||
}
|
||||
const initialState = Record<MissedUpdatesState>({
|
||||
focused: true,
|
||||
unread: 0,
|
||||
|
|
|
@ -1,9 +1,13 @@
|
|||
import type { TypedUseSelectorHook } from 'react-redux';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
import { configureStore } from '@reduxjs/toolkit';
|
||||
|
||||
import { rootReducer } from '../reducers';
|
||||
import { loadingBarMiddleware } from './middlewares/loading_bar';
|
||||
|
||||
import { errorsMiddleware } from './middlewares/errors';
|
||||
import { loadingBarMiddleware } from './middlewares/loading_bar';
|
||||
import { soundsMiddleware } from './middlewares/sounds';
|
||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux';
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: rootReducer,
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
import { Middleware } from 'redux';
|
||||
import type { AnyAction, Middleware } from 'redux';
|
||||
|
||||
import type { RootState } from '..';
|
||||
import { showAlertForError } from '../../actions/alerts';
|
||||
import { RootState } from '..';
|
||||
|
||||
const defaultFailSuffix = 'FAIL';
|
||||
|
||||
export const errorsMiddleware: Middleware<Record<string, never>, RootState> =
|
||||
({ dispatch }) =>
|
||||
(next) =>
|
||||
(action) => {
|
||||
(action: AnyAction & { skipAlert?: boolean; skipNotFound?: boolean }) => {
|
||||
if (action.type && !action.skipAlert) {
|
||||
const isFail = new RegExp(`${defaultFailSuffix}$`, 'g');
|
||||
|
||||
if (action.type.match(isFail)) {
|
||||
if (typeof action.type === 'string' && action.type.match(isFail)) {
|
||||
dispatch(showAlertForError(action.error, action.skipNotFound));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { showLoading, hideLoading } from 'react-redux-loading-bar';
|
||||
import { Middleware } from 'redux';
|
||||
import { RootState } from '..';
|
||||
import type { AnyAction, Middleware } from 'redux';
|
||||
|
||||
import type { RootState } from '..';
|
||||
|
||||
interface Config {
|
||||
promiseTypeSuffixes?: string[];
|
||||
|
@ -19,7 +20,7 @@ export const loadingBarMiddleware = (
|
|||
|
||||
return ({ dispatch }) =>
|
||||
(next) =>
|
||||
(action) => {
|
||||
(action: AnyAction) => {
|
||||
if (action.type && !action.skipLoading) {
|
||||
const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes;
|
||||
|
||||
|
@ -27,13 +28,15 @@ export const loadingBarMiddleware = (
|
|||
const isFulfilled = new RegExp(`${FULFILLED}$`, 'g');
|
||||
const isRejected = new RegExp(`${REJECTED}$`, 'g');
|
||||
|
||||
if (action.type.match(isPending)) {
|
||||
dispatch(showLoading());
|
||||
} else if (
|
||||
action.type.match(isFulfilled) ||
|
||||
action.type.match(isRejected)
|
||||
) {
|
||||
dispatch(hideLoading());
|
||||
if (typeof action.type === 'string') {
|
||||
if (action.type.match(isPending)) {
|
||||
dispatch(showLoading());
|
||||
} else if (
|
||||
action.type.match(isFulfilled) ||
|
||||
action.type.match(isRejected)
|
||||
) {
|
||||
dispatch(hideLoading());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Middleware, AnyAction } from 'redux';
|
||||
import { RootState } from '..';
|
||||
import type { Middleware, AnyAction } from 'redux';
|
||||
|
||||
import type { RootState } from '..';
|
||||
|
||||
interface AudioSource {
|
||||
src: string;
|
||||
|
@ -27,7 +28,7 @@ const play = (audio: HTMLAudioElement) => {
|
|||
}
|
||||
}
|
||||
|
||||
audio.play();
|
||||
void audio.play();
|
||||
};
|
||||
|
||||
export const soundsMiddleware = (): Middleware<
|
||||
|
@ -47,13 +48,15 @@ export const soundsMiddleware = (): Middleware<
|
|||
]),
|
||||
};
|
||||
|
||||
return () => (next) => (action: AnyAction) => {
|
||||
const sound = action?.meta?.sound;
|
||||
return () =>
|
||||
(next) =>
|
||||
(action: AnyAction & { meta?: { sound?: string } }) => {
|
||||
const sound = action?.meta?.sound;
|
||||
|
||||
if (sound && soundCache[sound]) {
|
||||
play(soundCache[sound]);
|
||||
}
|
||||
if (sound && soundCache[sound]) {
|
||||
play(soundCache[sound]);
|
||||
}
|
||||
|
||||
return next(action);
|
||||
};
|
||||
return next(action);
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
export function uuid(a?: string): string {
|
||||
return a
|
||||
? (
|
||||
(a as any as number) ^
|
||||
((Math.random() * 16) >> ((a as any as number) / 4))
|
||||
(a as unknown as number) ^
|
||||
((Math.random() * 16) >> ((a as unknown as number) / 4))
|
||||
).toString(16)
|
||||
: ('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid);
|
||||
: // eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
||||
('' + 1e7 + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, uuid);
|
||||
}
|
||||
|
|
|
@ -6526,13 +6526,6 @@ a.status-card.compact:hover {
|
|||
&--wide {
|
||||
grid-column: span 2;
|
||||
}
|
||||
|
||||
&.standalone {
|
||||
.media-gallery__item-gifv-thumbnail {
|
||||
transform: none;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.media-gallery__item-thumbnail {
|
||||
|
@ -6580,11 +6573,7 @@ a.status-card.compact:hover {
|
|||
cursor: zoom-in;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.media-gallery__item-thumbnail-label {
|
||||
|
@ -6683,6 +6672,8 @@ a.status-card.compact:hover {
|
|||
border-radius: 4px;
|
||||
box-sizing: border-box;
|
||||
color: $white;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
&.editable {
|
||||
border-radius: 0;
|
||||
|
@ -6717,9 +6708,6 @@ a.status-card.compact:hover {
|
|||
&.inline {
|
||||
video {
|
||||
object-fit: contain;
|
||||
position: relative;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ type AccountField = Record<{
|
|||
verified_at: string | null;
|
||||
}>;
|
||||
|
||||
type AccountApiResponseValues = {
|
||||
interface AccountApiResponseValues {
|
||||
acct: string;
|
||||
avatar: string;
|
||||
avatar_static: string;
|
||||
|
@ -34,7 +34,7 @@ type AccountApiResponseValues = {
|
|||
statuses_count: number;
|
||||
url: string;
|
||||
username: string;
|
||||
};
|
||||
}
|
||||
|
||||
type NormalizedAccountField = Record<{
|
||||
name_emojified: string;
|
||||
|
@ -42,12 +42,12 @@ type NormalizedAccountField = Record<{
|
|||
value_plain: string;
|
||||
}>;
|
||||
|
||||
type NormalizedAccountValues = {
|
||||
interface NormalizedAccountValues {
|
||||
display_name_html: string;
|
||||
fields: NormalizedAccountField[];
|
||||
note_emojified: string;
|
||||
note_plain: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type Account = Record<
|
||||
AccountApiResponseValues & NormalizedAccountValues
|
||||
|
|
|
@ -9,10 +9,12 @@ class Vacuum::AccessTokensVacuum
|
|||
private
|
||||
|
||||
def vacuum_revoked_access_tokens!
|
||||
Doorkeeper::AccessToken.where.not(revoked_at: nil).where('revoked_at < NOW()').delete_all
|
||||
Doorkeeper::AccessToken.where.not(expires_in: nil).where('created_at + make_interval(secs => expires_in) < NOW()').in_batches.delete_all
|
||||
Doorkeeper::AccessToken.where.not(revoked_at: nil).where('revoked_at < NOW()').in_batches.delete_all
|
||||
end
|
||||
|
||||
def vacuum_revoked_access_grants!
|
||||
Doorkeeper::AccessGrant.where.not(revoked_at: nil).where('revoked_at < NOW()').delete_all
|
||||
Doorkeeper::AccessGrant.where.not(expires_in: nil).where('created_at + make_interval(secs => expires_in) < NOW()').in_batches.delete_all
|
||||
Doorkeeper::AccessGrant.where.not(revoked_at: nil).where('revoked_at < NOW()').in_batches.delete_all
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
- if status.ordered_media_attachments.first.video?
|
||||
- video = status.ordered_media_attachments.first
|
||||
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), frameRate: video.file.meta.dig('original', 'frame_rate'), blurhash: video.blurhash, sensitive: status.sensitive?, visible: false, width: 610, height: 343, inline: true, alt: video.description, media: [ActiveModelSerializers::SerializableResource.new(video, serializer: REST::MediaAttachmentSerializer)].as_json
|
||||
= react_component :video, src: video.file.url(:original), preview: video.file.url(:small), frameRate: video.file.meta.dig('original', 'frame_rate'), blurhash: video.blurhash, sensitive: status.sensitive?, visible: false, width: 610, height: 343, inline: true, alt: video.description, lang: status.language, media: [ActiveModelSerializers::SerializableResource.new(video, serializer: REST::MediaAttachmentSerializer)].as_json
|
||||
- elsif status.ordered_media_attachments.first.audio?
|
||||
- audio = status.ordered_media_attachments.first
|
||||
= react_component :audio, src: audio.file.url(:original), height: 110, alt: audio.description, duration: audio.file.meta.dig(:original, :duration)
|
||||
= react_component :audio, src: audio.file.url(:original), height: 110, alt: audio.description, lang: status.language, duration: audio.file.meta.dig(:original, :duration)
|
||||
- else
|
||||
= react_component :media_gallery, height: 343, sensitive: status.sensitive?, visible: false, media: status.ordered_media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
|
||||
= react_component :media_gallery, height: 343, sensitive: status.sensitive?, visible: false, lang: status.language, media: status.ordered_media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue