Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
ce1c652623
55 changed files with 292 additions and 304 deletions
12
Gemfile.lock
12
Gemfile.lock
|
@ -154,6 +154,7 @@ GEM
|
|||
net-http-persistent (~> 4.0)
|
||||
nokogiri (~> 1, >= 1.10.8)
|
||||
base64 (0.1.1)
|
||||
bcp47_spec (0.2.1)
|
||||
bcrypt (3.1.19)
|
||||
better_errors (2.10.1)
|
||||
erubi (>= 1.0.0)
|
||||
|
@ -375,19 +376,19 @@ GEM
|
|||
reline (>= 0.3.8)
|
||||
jmespath (1.6.2)
|
||||
json (2.6.3)
|
||||
json-canonicalization (0.3.2)
|
||||
json-canonicalization (1.0.0)
|
||||
json-jwt (1.15.3)
|
||||
activesupport (>= 4.2)
|
||||
aes_key_wrap
|
||||
bindata
|
||||
httpclient
|
||||
json-ld (3.2.5)
|
||||
json-ld (3.3.1)
|
||||
htmlentities (~> 4.3)
|
||||
json-canonicalization (~> 0.3, >= 0.3.2)
|
||||
json-canonicalization (~> 1.0)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
multi_json (~> 1.15)
|
||||
rack (>= 2.2, < 4)
|
||||
rdf (~> 3.2, >= 3.2.10)
|
||||
rdf (~> 3.3)
|
||||
json-ld-preloaded (3.2.2)
|
||||
json-ld (~> 3.2)
|
||||
rdf (~> 3.2)
|
||||
|
@ -596,7 +597,8 @@ GEM
|
|||
zeitwerk (~> 2.6)
|
||||
rainbow (3.1.1)
|
||||
rake (13.0.6)
|
||||
rdf (3.2.11)
|
||||
rdf (3.3.1)
|
||||
bcp47_spec (~> 0.2)
|
||||
link_header (~> 0.0, >= 0.0.8)
|
||||
rdf-normalize (0.6.1)
|
||||
rdf (~> 3.2)
|
||||
|
|
|
@ -17,7 +17,6 @@ import { Avatar } from './avatar';
|
|||
import { Button } from './button';
|
||||
import { FollowersCounter } from './counters';
|
||||
import { DisplayName } from './display_name';
|
||||
import { IconButton } from './icon_button';
|
||||
import Permalink from './permalink';
|
||||
import { RelativeTimestamp } from './relative_timestamp';
|
||||
|
||||
|
@ -45,10 +44,7 @@ class Account extends ImmutablePureComponent {
|
|||
intl: PropTypes.object.isRequired,
|
||||
hidden: PropTypes.bool,
|
||||
minimal: PropTypes.bool,
|
||||
actionIcon: PropTypes.string,
|
||||
actionTitle: PropTypes.string,
|
||||
defaultAction: PropTypes.string,
|
||||
onActionClick: PropTypes.func,
|
||||
withBio: PropTypes.bool,
|
||||
};
|
||||
|
||||
|
@ -76,12 +72,8 @@ class Account extends ImmutablePureComponent {
|
|||
this.props.onMuteNotifications(this.props.account, false);
|
||||
};
|
||||
|
||||
handleAction = () => {
|
||||
this.props.onActionClick(this.props.account);
|
||||
};
|
||||
|
||||
render () {
|
||||
const { account, intl, hidden, withBio, onActionClick, actionIcon, actionTitle, defaultAction, size, minimal } = this.props;
|
||||
const { account, intl, hidden, withBio, defaultAction, size, minimal } = this.props;
|
||||
|
||||
if (!account) {
|
||||
return <EmptyAccount size={size} minimal={minimal} />;
|
||||
|
@ -98,9 +90,7 @@ class Account extends ImmutablePureComponent {
|
|||
|
||||
let buttons;
|
||||
|
||||
if (actionIcon && onActionClick) {
|
||||
buttons = <IconButton icon={actionIcon} title={actionTitle} onClick={this.handleAction} />;
|
||||
} else if (!actionIcon && account.get('id') !== me && account.get('relationship', null) !== null) {
|
||||
if (account.get('id') !== me && account.get('relationship', null) !== null) {
|
||||
const following = account.getIn(['relationship', 'following']);
|
||||
const requested = account.getIn(['relationship', 'requested']);
|
||||
const blocking = account.getIn(['relationship', 'blocking']);
|
||||
|
|
|
@ -5,49 +5,44 @@ import { autoPlayGif } from '../initial_state';
|
|||
import type { Account } from '../types/resources';
|
||||
|
||||
interface Props {
|
||||
account: Account | undefined;
|
||||
className?: string;
|
||||
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
size: number;
|
||||
style?: React.CSSProperties;
|
||||
inline?: boolean;
|
||||
animate?: boolean;
|
||||
}
|
||||
|
||||
export const Avatar: React.FC<Props> = ({
|
||||
account,
|
||||
className,
|
||||
animate = autoPlayGif,
|
||||
size = 20,
|
||||
inline = false,
|
||||
style: styleFromParent,
|
||||
}) => {
|
||||
const { hovering, handleMouseEnter, handleMouseLeave } =
|
||||
useHovering(autoPlayGif);
|
||||
const { hovering, handleMouseEnter, handleMouseLeave } = useHovering(animate);
|
||||
|
||||
const style = {
|
||||
...styleFromParent,
|
||||
width: `${size}px`,
|
||||
height: `${size}px`,
|
||||
backgroundSize: `${size}px ${size}px`,
|
||||
};
|
||||
|
||||
if (account) {
|
||||
style.backgroundImage = `url(${account.get(
|
||||
hovering ? 'avatar' : 'avatar_static',
|
||||
)})`;
|
||||
}
|
||||
const src =
|
||||
hovering || animate
|
||||
? account?.get('avatar')
|
||||
: account?.get('avatar_static');
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'account__avatar',
|
||||
{ 'account__avatar-inline': inline },
|
||||
className,
|
||||
)}
|
||||
className={classNames('account__avatar', {
|
||||
'account__avatar-inline': inline,
|
||||
})}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
style={style}
|
||||
data-avatar-of={account && `@${account.get('acct')}`}
|
||||
role='img'
|
||||
aria-label={account?.get('acct')}
|
||||
/>
|
||||
>
|
||||
{src && <img src={src} alt={account?.get('acct')} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,6 +5,8 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
|
||||
import { Avatar } from './avatar';
|
||||
|
||||
export default class AvatarComposite extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
|
@ -76,12 +78,12 @@ export default class AvatarComposite extends PureComponent {
|
|||
bottom: bottom,
|
||||
width: `${width}%`,
|
||||
height: `${height}%`,
|
||||
backgroundSize: 'cover',
|
||||
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div key={account.get('id')} style={style} data-avatar-of={`@${account.get('acct')}`} />
|
||||
<div key={account.get('id')} style={style}>
|
||||
<Avatar account={account} animate={animate} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,39 +0,0 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import { autoPlayGif } from 'flavours/glitch/initial_state';
|
||||
|
||||
export default class AvatarOverlay extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
friend: ImmutablePropTypes.map.isRequired,
|
||||
animate: PropTypes.bool,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
animate: autoPlayGif,
|
||||
};
|
||||
|
||||
render() {
|
||||
const { account, friend, animate } = this.props;
|
||||
|
||||
const baseStyle = {
|
||||
backgroundImage: `url(${account.get(animate ? 'avatar' : 'avatar_static')})`,
|
||||
};
|
||||
|
||||
const overlayStyle = {
|
||||
backgroundImage: `url(${friend.get(animate ? 'avatar' : 'avatar_static')})`,
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='account__avatar-overlay'>
|
||||
<div className='account__avatar-overlay-base' style={baseStyle} data-avatar-of={`@${account.get('acct')}`} />
|
||||
<div className='account__avatar-overlay-overlay' style={overlayStyle} data-avatar-of={`@${friend.get('acct')}`} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
}
|
56
app/javascript/flavours/glitch/components/avatar_overlay.tsx
Normal file
56
app/javascript/flavours/glitch/components/avatar_overlay.tsx
Normal file
|
@ -0,0 +1,56 @@
|
|||
import { useHovering } from '../hooks/useHovering';
|
||||
import { autoPlayGif } from '../initial_state';
|
||||
import type { Account } from '../types/resources';
|
||||
|
||||
interface Props {
|
||||
account: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
friend: Account | undefined; // FIXME: remove `undefined` once we know for sure its always there
|
||||
size?: number;
|
||||
baseSize?: number;
|
||||
overlaySize?: number;
|
||||
}
|
||||
|
||||
export const AvatarOverlay: React.FC<Props> = ({
|
||||
account,
|
||||
friend,
|
||||
size = 46,
|
||||
baseSize = 36,
|
||||
overlaySize = 24,
|
||||
}) => {
|
||||
const { hovering, handleMouseEnter, handleMouseLeave } =
|
||||
useHovering(autoPlayGif);
|
||||
const accountSrc = hovering
|
||||
? account?.get('avatar')
|
||||
: account?.get('avatar_static');
|
||||
const friendSrc = hovering
|
||||
? friend?.get('avatar')
|
||||
: friend?.get('avatar_static');
|
||||
|
||||
return (
|
||||
<div
|
||||
className='account__avatar-overlay'
|
||||
style={{ width: size, height: size }}
|
||||
onMouseEnter={handleMouseEnter}
|
||||
onMouseLeave={handleMouseLeave}
|
||||
>
|
||||
<div className='account__avatar-overlay-base'>
|
||||
<div
|
||||
className='account__avatar'
|
||||
style={{ width: `${baseSize}px`, height: `${baseSize}px` }}
|
||||
data-avatar-of={`@${account?.get('acct')}`}
|
||||
>
|
||||
{accountSrc && <img src={accountSrc} alt={account?.get('acct')} />}
|
||||
</div>
|
||||
</div>
|
||||
<div className='account__avatar-overlay-overlay'>
|
||||
<div
|
||||
className='account__avatar'
|
||||
style={{ width: `${overlaySize}px`, height: `${overlaySize}px` }}
|
||||
data-avatar-of={`@${friend?.get('acct')}`}
|
||||
>
|
||||
{friendSrc && <img src={friendSrc} alt={friend?.get('acct')} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
|
@ -12,7 +12,6 @@ export default class Column extends PureComponent {
|
|||
static propTypes = {
|
||||
children: PropTypes.node,
|
||||
extraClasses: PropTypes.string,
|
||||
name: PropTypes.string,
|
||||
label: PropTypes.string,
|
||||
bindToDocument: PropTypes.bool,
|
||||
};
|
||||
|
@ -62,10 +61,10 @@ export default class Column extends PureComponent {
|
|||
}
|
||||
|
||||
render () {
|
||||
const { children, extraClasses, name, label } = this.props;
|
||||
const { label, children, extraClasses } = this.props;
|
||||
|
||||
return (
|
||||
<div role='region' aria-label={label} data-column={name} className={`column ${extraClasses || ''}`} ref={this.setRef}>
|
||||
<div role='region' aria-label={label} className={`column ${extraClasses || ''}`} ref={this.setRef}>
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -84,6 +84,7 @@ class Status extends ImmutablePureComponent {
|
|||
previousId: PropTypes.string,
|
||||
nextInReplyToId: PropTypes.string,
|
||||
rootId: PropTypes.string,
|
||||
onClick: PropTypes.func,
|
||||
onReply: PropTypes.func,
|
||||
onFavourite: PropTypes.func,
|
||||
onReblog: PropTypes.func,
|
||||
|
@ -116,7 +117,6 @@ class Status extends ImmutablePureComponent {
|
|||
intl: PropTypes.object.isRequired,
|
||||
cacheMediaWidth: PropTypes.func,
|
||||
cachedMediaWidth: PropTypes.number,
|
||||
onClick: PropTypes.func,
|
||||
scrollKey: PropTypes.string,
|
||||
deployPictureInPicture: PropTypes.func,
|
||||
settings: ImmutablePropTypes.map.isRequired,
|
||||
|
@ -576,7 +576,7 @@ class Status extends ImmutablePureComponent {
|
|||
openProfile: this.handleHotkeyOpenProfile,
|
||||
moveUp: this.handleHotkeyMoveUp,
|
||||
moveDown: this.handleHotkeyMoveDown,
|
||||
toggleSpoiler: this.handleExpandedToggle,
|
||||
toggleHidden: this.handleExpandedToggle,
|
||||
bookmark: this.handleHotkeyBookmark,
|
||||
toggleCollapse: this.handleHotkeyCollapse,
|
||||
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||||
|
|
|
@ -6,7 +6,7 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
|
|||
|
||||
// Mastodon imports.
|
||||
import { Avatar } from './avatar';
|
||||
import AvatarOverlay from './avatar_overlay';
|
||||
import { AvatarOverlay } from './avatar_overlay';
|
||||
import { DisplayName } from './display_name';
|
||||
|
||||
export default class StatusHeader extends PureComponent {
|
||||
|
@ -39,7 +39,7 @@ export default class StatusHeader extends PureComponent {
|
|||
|
||||
let statusAvatar;
|
||||
if (friend === undefined || friend === null) {
|
||||
statusAvatar = <Avatar account={account} size={48} />;
|
||||
statusAvatar = <Avatar account={account} size={46} />;
|
||||
} else {
|
||||
statusAvatar = <AvatarOverlay account={account} friend={friend} />;
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import { store } from 'flavours/glitch/store';
|
|||
const title = process.env.NODE_ENV === 'production' ? siteTitle : `${siteTitle} (Dev)`;
|
||||
|
||||
const hydrateAction = hydrateStore(initialState);
|
||||
|
||||
store.dispatch(hydrateAction);
|
||||
|
||||
// check for deprecated local settings
|
||||
|
@ -71,8 +72,8 @@ export default class Mastodon extends PureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
shouldUpdateScroll (_, { location }) {
|
||||
return !(location.state?.mastodonModalKey);
|
||||
shouldUpdateScroll (prevRouterProps, { location }) {
|
||||
return !(location.state?.mastodonModalKey && location.state?.mastodonModalKey !== prevRouterProps?.location?.state?.mastodonModalKey);
|
||||
}
|
||||
|
||||
render () {
|
||||
|
|
|
@ -38,7 +38,7 @@ class FeaturedTags extends ImmutablePureComponent {
|
|||
name={featuredTag.get('name')}
|
||||
href={featuredTag.get('url')}
|
||||
to={`/@${account.get('acct')}/tagged/${featuredTag.get('name')}`}
|
||||
uses={featuredTag.get('statuses_count')}
|
||||
uses={featuredTag.get('statuses_count') * 1}
|
||||
withGraph={false}
|
||||
description={((featuredTag.get('statuses_count') * 1) > 0) ? intl.formatMessage(messages.lastStatusAt, { date: intl.formatDate(featuredTag.get('last_status_at'), { month: 'short', day: '2-digit' }) }) : intl.formatMessage(messages.empty)}
|
||||
/>
|
||||
|
|
|
@ -11,7 +11,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
|
||||
import { Avatar } from 'flavours/glitch/components/avatar';
|
||||
import { Button } from 'flavours/glitch/components/button';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { IconButton } from 'flavours/glitch/components/icon_button';
|
||||
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
|
||||
import { autoPlayGif, me, domain } from 'flavours/glitch/initial_state';
|
||||
|
|
|
@ -76,7 +76,7 @@ export default class MediaItem extends ImmutablePureComponent {
|
|||
if (['audio', 'video'].includes(attachment.get('type'))) {
|
||||
content = (
|
||||
<img
|
||||
src={attachment.get('preview_url') || attachment.getIn(['account', 'avatar_static'])}
|
||||
src={attachment.get('preview_url') || status.getIn(['account', 'avatar_static'])}
|
||||
alt={attachment.get('description')}
|
||||
lang={status.get('language')}
|
||||
onLoad={this.handleImageLoad}
|
||||
|
|
|
@ -8,7 +8,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { WithRouterPropTypes } from 'flavours/glitch/utils/react_router';
|
||||
|
||||
import AvatarOverlay from '../../../components/avatar_overlay';
|
||||
import { AvatarOverlay } from '../../../components/avatar_overlay';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
|
||||
class MovedNote extends ImmutablePureComponent {
|
||||
|
|
|
@ -184,7 +184,7 @@ class AccountTimeline extends ImmutablePureComponent {
|
|||
const remoteMessage = remote ? <RemoteHint url={remoteUrl} /> : null;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef} name='account'>
|
||||
<Column ref={this.setRef}>
|
||||
<ProfileColumnHeader onClick={this.handleHeaderClick} multiColumn={multiColumn} />
|
||||
|
||||
<StatusList
|
||||
|
|
|
@ -9,7 +9,7 @@ import { is } from 'immutable';
|
|||
|
||||
import { throttle, debounce } from 'lodash';
|
||||
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { formatTime, getPointerPosition, fileNameFromURL } from 'flavours/glitch/features/video';
|
||||
|
||||
import { Blurhash } from '../../components/blurhash';
|
||||
|
|
|
@ -59,7 +59,7 @@ class Blocks extends ImmutablePureComponent {
|
|||
const emptyMessage = <FormattedMessage id='empty_column.blocks' defaultMessage="You haven't blocked any users yet." />;
|
||||
|
||||
return (
|
||||
<Column name='blocks' bindToDocument={!multiColumn} icon='ban' heading={intl.formatMessage(messages.heading)}>
|
||||
<Column bindToDocument={!multiColumn} icon='ban' heading={intl.formatMessage(messages.heading)}>
|
||||
<ColumnBackButtonSlim />
|
||||
<ScrollableList
|
||||
scrollKey='blocks'
|
||||
|
|
|
@ -77,7 +77,7 @@ class Bookmarks extends ImmutablePureComponent {
|
|||
const emptyMessage = <FormattedMessage id='empty_column.bookmarked_statuses' defaultMessage="You don't have any bookmarked posts yet. When you bookmark one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} name='bookmarks'>
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef}>
|
||||
<ColumnHeader
|
||||
icon='bookmark'
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
|
|
|
@ -40,14 +40,14 @@ const mapStateToProps = (state, { columnId }) => {
|
|||
|
||||
class CommunityTimeline extends PureComponent {
|
||||
|
||||
static defaultProps = {
|
||||
onlyMedia: false,
|
||||
};
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
onlyMedia: false,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
columnId: PropTypes.string,
|
||||
|
@ -128,7 +128,7 @@ class CommunityTimeline extends PureComponent {
|
|||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
<Column ref={this.setRef} name='local' bindToDocument={!multiColumn} label={intl.formatMessage(messages.title)}>
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='users'
|
||||
active={hasUnread}
|
||||
|
|
|
@ -17,19 +17,21 @@ export default class NavigationBar extends ImmutablePureComponent {
|
|||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
onLogout: PropTypes.func.isRequired,
|
||||
onClose: PropTypes.func,
|
||||
};
|
||||
|
||||
render () {
|
||||
const username = this.props.account.get('acct');
|
||||
return (
|
||||
<div className='navigation-bar'>
|
||||
<Permalink className='avatar' href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
|
||||
<span style={{ display: 'none' }}>{this.props.account.get('acct')}</span>
|
||||
<Avatar account={this.props.account} size={48} />
|
||||
<Permalink className='avatar' href={this.props.account.get('url')} to={`/@${username}`}>
|
||||
<span style={{ display: 'none' }}>{username}</span>
|
||||
<Avatar account={this.props.account} size={46} />
|
||||
</Permalink>
|
||||
|
||||
<div className='navigation-bar__profile'>
|
||||
<Permalink className='acct' href={this.props.account.get('url')} to={`/@${this.props.account.get('acct')}`}>
|
||||
<strong>@{this.props.account.get('acct')}</strong>
|
||||
<strong className='navigation-bar__profile-account'>@{username}</strong>
|
||||
</Permalink>
|
||||
|
||||
{ profileLink !== undefined && (
|
||||
|
|
|
@ -92,25 +92,6 @@ class Search extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
this.setState({ expanded: false, selectedOption: -1 });
|
||||
};
|
||||
|
||||
handleFocus = () => {
|
||||
const { onShow, singleColumn } = this.props;
|
||||
|
||||
this.setState({ expanded: true, selectedOption: -1 });
|
||||
onShow();
|
||||
|
||||
if (this.searchForm && !singleColumn) {
|
||||
const { left, right } = this.searchForm.getBoundingClientRect();
|
||||
|
||||
if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) {
|
||||
this.searchForm.scrollIntoView();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleKeyDown = (e) => {
|
||||
const { selectedOption } = this.state;
|
||||
const options = searchEnabled ? this._getOptions().concat(this.defaultOptions) : this._getOptions();
|
||||
|
@ -161,8 +142,23 @@ class Search extends PureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
findTarget = () => {
|
||||
return this.searchForm;
|
||||
handleFocus = () => {
|
||||
const { onShow, singleColumn } = this.props;
|
||||
|
||||
this.setState({ expanded: true, selectedOption: -1 });
|
||||
onShow();
|
||||
|
||||
if (this.searchForm && !singleColumn) {
|
||||
const { left, right } = this.searchForm.getBoundingClientRect();
|
||||
|
||||
if (left < 0 || right > (window.innerWidth || document.documentElement.clientWidth)) {
|
||||
this.searchForm.scrollIntoView();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
handleBlur = () => {
|
||||
this.setState({ expanded: false, selectedOption: -1 });
|
||||
};
|
||||
|
||||
handleHashtagClick = () => {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { FormattedMessage } from 'react-intl';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { LoadMore } from 'flavours/glitch/components/load_more';
|
||||
import { SearchSection } from 'flavours/glitch/features/explore/components/search_section';
|
||||
|
||||
|
@ -69,6 +69,7 @@ class SearchResults extends ImmutablePureComponent {
|
|||
);
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<div className='drawer--results'>
|
||||
<header className='search-results__header'>
|
||||
|
|
|
@ -77,7 +77,7 @@ class Favourites extends ImmutablePureComponent {
|
|||
const emptyMessage = <FormattedMessage id='empty_column.favourited_statuses' defaultMessage="You don't have any favorite posts yet. When you favorite one, it will show up here." />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} name='favourites' label={intl.formatMessage(messages.heading)}>
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.heading)}>
|
||||
<ColumnHeader
|
||||
icon='star'
|
||||
title={intl.formatMessage(messages.heading)}
|
||||
|
|
|
@ -85,9 +85,10 @@ class Favourites extends ImmutablePureComponent {
|
|||
showBackButton
|
||||
multiColumn={multiColumn}
|
||||
extraButton={(
|
||||
<button className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
|
||||
<button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ScrollableList
|
||||
scrollKey='favourites'
|
||||
onLoadMore={this.handleLoadMore}
|
||||
|
|
|
@ -67,7 +67,7 @@ class FollowRequests extends ImmutablePureComponent {
|
|||
);
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} name='follow-requests' icon='user-plus' heading={intl.formatMessage(messages.heading)}>
|
||||
<Column bindToDocument={!multiColumn} icon='user-plus' heading={intl.formatMessage(messages.heading)}>
|
||||
<ColumnBackButtonSlim />
|
||||
<ScrollableList
|
||||
scrollKey='follow_requests'
|
||||
|
|
|
@ -394,7 +394,7 @@ class Announcements extends ImmutablePureComponent {
|
|||
_markAnnouncementAsRead () {
|
||||
const { dismissAnnouncement, announcements } = this.props;
|
||||
const { index } = this.state;
|
||||
const announcement = announcements.get(index);
|
||||
const announcement = announcements.get(announcements.size - 1 - index);
|
||||
if (!announcement.get('read')) dismissAnnouncement(announcement.get('id'));
|
||||
}
|
||||
|
||||
|
@ -435,7 +435,7 @@ class Announcements extends ImmutablePureComponent {
|
|||
selected={index === idx}
|
||||
disabled={disableSwiping}
|
||||
/>
|
||||
))}
|
||||
)).reverse()}
|
||||
</ReactSwipeableViews>
|
||||
|
||||
{announcements.size > 1 && (
|
||||
|
|
|
@ -173,7 +173,7 @@ class GettingStarted extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} name='getting-started' icon='asterisk' heading={intl.formatMessage(messages.heading)} label={intl.formatMessage(messages.menu)} hideHeadingOnMobile>
|
||||
<Column bindToDocument={!multiColumn} icon='asterisk' heading={intl.formatMessage(messages.heading)} label={intl.formatMessage(messages.menu)} hideHeadingOnMobile>
|
||||
<div className='scrollable optionally-scrollable'>
|
||||
<div className='getting-started__wrapper'>
|
||||
{!multiColumn && signedIn && <NavigationBar account={myAccount} />}
|
||||
|
|
|
@ -196,7 +196,7 @@ class HomeTimeline extends PureComponent {
|
|||
}
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} name='home' label={intl.formatMessage(messages.title)}>
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='home'
|
||||
active={hasUnread}
|
||||
|
|
|
@ -16,7 +16,7 @@ const messages = defineMessages({
|
|||
add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
||||
});
|
||||
|
||||
const mapStateToProps = (state, { listId, added }) => ({
|
||||
const MapStateToProps = (state, { listId, added }) => ({
|
||||
list: state.get('lists').get(listId),
|
||||
added: typeof added === 'undefined' ? state.getIn(['listAdder', 'lists', 'items']).includes(listId) : added,
|
||||
});
|
||||
|
@ -69,4 +69,4 @@ class List extends ImmutablePureComponent {
|
|||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(List));
|
||||
export default connect(MapStateToProps, mapDispatchToProps)(injectIntl(List));
|
||||
|
|
|
@ -1,20 +1,39 @@
|
|||
import PropTypes from 'prop-types';
|
||||
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { removeFromListEditor, addToListEditor } from '../../../actions/lists';
|
||||
import { Avatar } from '../../../components/avatar';
|
||||
import { DisplayName } from '../../../components/display_name';
|
||||
import { IconButton } from '../../../components/icon_button';
|
||||
import { makeGetAccount } from '../../../selectors';
|
||||
|
||||
const messages = defineMessages({
|
||||
remove: { id: 'lists.account.remove', defaultMessage: 'Remove from list' },
|
||||
add: { id: 'lists.account.add', defaultMessage: 'Add to list' },
|
||||
});
|
||||
|
||||
export default class Account extends ImmutablePureComponent {
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const mapStateToProps = (state, { accountId, added }) => ({
|
||||
account: getAccount(state, accountId),
|
||||
added: typeof added === 'undefined' ? state.getIn(['listEditor', 'accounts', 'items']).includes(accountId) : added,
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
||||
onRemove: () => dispatch(removeFromListEditor(accountId)),
|
||||
onAdd: () => dispatch(addToListEditor(accountId)),
|
||||
});
|
||||
|
||||
class Account extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
account: ImmutablePropTypes.map.isRequired,
|
||||
|
@ -56,3 +75,5 @@ export default class Account extends ImmutablePureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(makeMapStateToProps, mapDispatchToProps)(injectIntl(Account));
|
||||
|
|
|
@ -1,17 +1,31 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { defineMessages } from 'react-intl';
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
import classNames from 'classnames';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
|
||||
|
||||
const messages = defineMessages({
|
||||
search: { id: 'lists.search', defaultMessage: 'Search among people you follow' },
|
||||
});
|
||||
|
||||
export default class Search extends PureComponent {
|
||||
const mapStateToProps = state => ({
|
||||
value: state.getIn(['listEditor', 'suggestions', 'value']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onSubmit: value => dispatch(fetchListSuggestions(value)),
|
||||
onClear: () => dispatch(clearListSuggestions()),
|
||||
onChange: value => dispatch(changeListSuggestions(value)),
|
||||
});
|
||||
|
||||
class Search extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
intl: PropTypes.object.isRequired,
|
||||
|
@ -63,3 +77,5 @@ export default class Search extends PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(injectIntl(Search));
|
||||
|
|
|
@ -1,26 +0,0 @@
|
|||
import { injectIntl } from 'react-intl';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { removeFromListEditor, addToListEditor } from 'flavours/glitch/actions/lists';
|
||||
import { makeGetAccount } from 'flavours/glitch/selectors';
|
||||
|
||||
import Account from '../components/account';
|
||||
|
||||
const makeMapStateToProps = () => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
const mapStateToProps = (state, { accountId, added }) => ({
|
||||
account: getAccount(state, accountId),
|
||||
added: typeof added === 'undefined' ? state.getIn(['listEditor', 'accounts', 'items']).includes(accountId) : added,
|
||||
});
|
||||
|
||||
return mapStateToProps;
|
||||
};
|
||||
|
||||
const mapDispatchToProps = (dispatch, { accountId }) => ({
|
||||
onRemove: () => dispatch(removeFromListEditor(accountId)),
|
||||
onAdd: () => dispatch(addToListEditor(accountId)),
|
||||
});
|
||||
|
||||
export default injectIntl(connect(makeMapStateToProps, mapDispatchToProps)(Account));
|
|
@ -1,18 +0,0 @@
|
|||
import { injectIntl } from 'react-intl';
|
||||
|
||||
import { connect } from 'react-redux';
|
||||
|
||||
import { fetchListSuggestions, clearListSuggestions, changeListSuggestions } from '../../../actions/lists';
|
||||
import Search from '../components/search';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
value: state.getIn(['listEditor', 'suggestions', 'value']),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = dispatch => ({
|
||||
onSubmit: value => dispatch(fetchListSuggestions(value)),
|
||||
onClear: () => dispatch(clearListSuggestions()),
|
||||
onChange: value => dispatch(changeListSuggestions(value)),
|
||||
});
|
||||
|
||||
export default injectIntl(connect(mapStateToProps, mapDispatchToProps)(Search));
|
|
@ -11,10 +11,9 @@ import spring from 'react-motion/lib/spring';
|
|||
import { setupListEditor, clearListSuggestions, resetListEditor } from '../../actions/lists';
|
||||
import Motion from '../ui/util/optional_motion';
|
||||
|
||||
import Account from './components/account';
|
||||
import EditListForm from './components/edit_list_form';
|
||||
import AccountContainer from './containers/account_container';
|
||||
import SearchContainer from './containers/search_container';
|
||||
|
||||
import Search from './components/search';
|
||||
|
||||
const mapStateToProps = state => ({
|
||||
accountIds: state.getIn(['listEditor', 'accounts', 'items']),
|
||||
|
@ -58,21 +57,21 @@ class ListEditor extends ImmutablePureComponent {
|
|||
<div className='modal-root__modal list-editor'>
|
||||
<EditListForm />
|
||||
|
||||
<SearchContainer />
|
||||
<Search />
|
||||
|
||||
<div className='drawer__pager'>
|
||||
<div className='drawer__inner list-editor__accounts'>
|
||||
{accountIds.map(accountId => <AccountContainer key={accountId} accountId={accountId} added />)}
|
||||
{accountIds.map(accountId => <Account key={accountId} accountId={accountId} added />)}
|
||||
</div>
|
||||
|
||||
{showSearch && <div role='button' tabIndex={-1} className='drawer__backdrop' onClick={onClear} />}
|
||||
|
||||
<Motion defaultStyle={{ x: -100 }} style={{ x: spring(showSearch ? 0 : -100, { stiffness: 210, damping: 20 }) }}>
|
||||
{({ x }) =>
|
||||
(<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
|
||||
{searchAccountIds.map(accountId => <AccountContainer key={accountId} accountId={accountId} />)}
|
||||
</div>)
|
||||
}
|
||||
{({ x }) => (
|
||||
<div className='drawer__inner backdrop' style={{ transform: x === 0 ? null : `translateX(${x}%)`, visibility: x === -100 ? 'hidden' : 'visible' }}>
|
||||
{searchAccountIds.map(accountId => <Account key={accountId} accountId={accountId} />)}
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -61,7 +61,7 @@ class Mutes extends ImmutablePureComponent {
|
|||
const emptyMessage = <FormattedMessage id='empty_column.mutes' defaultMessage="You haven't muted any users yet." />;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} name='mutes' icon='volume-off' heading={intl.formatMessage(messages.heading)}>
|
||||
<Column bindToDocument={!multiColumn} icon='volume-off' heading={intl.formatMessage(messages.heading)}>
|
||||
<ColumnBackButtonSlim />
|
||||
<ScrollableList
|
||||
scrollKey='mutes'
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
|
||||
export default class ClearColumnButton extends Component {
|
||||
export default class ClearColumnButton extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onClick: PropTypes.func.isRequired,
|
||||
|
|
|
@ -5,7 +5,7 @@ import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
|||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
|
||||
import AvatarOverlay from 'flavours/glitch/components/avatar_overlay';
|
||||
import { AvatarOverlay } from 'flavours/glitch/components/avatar_overlay';
|
||||
import { RelativeTimestamp } from 'flavours/glitch/components/relative_timestamp';
|
||||
|
||||
// This needs to be kept in sync with app/models/report.rb
|
||||
|
|
|
@ -333,7 +333,6 @@ class Notifications extends PureComponent {
|
|||
<Column
|
||||
bindToDocument={!multiColumn}
|
||||
ref={this.setColumnRef}
|
||||
name='notifications'
|
||||
extraClasses={this.props.notifCleaningActive ? 'notif-cleaning' : null}
|
||||
label={intl.formatMessage(messages.title)}
|
||||
>
|
||||
|
|
|
@ -13,7 +13,7 @@ import SwipeableViews from 'react-swipeable-views';
|
|||
|
||||
import Column from 'flavours/glitch/components/column';
|
||||
import ColumnBackButton from 'flavours/glitch/components/column_back_button';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { me, domain } from 'flavours/glitch/initial_state';
|
||||
|
||||
import ArrowSmallRight from './components/arrow_small_right';
|
||||
|
|
|
@ -122,8 +122,8 @@ class Footer extends ImmutablePureComponent {
|
|||
}
|
||||
};
|
||||
|
||||
_performReblog = (privacy) => {
|
||||
const { dispatch, status } = this.props;
|
||||
_performReblog = (status, privacy) => {
|
||||
const { dispatch } = this.props;
|
||||
dispatch(reblog(status, privacy));
|
||||
};
|
||||
|
||||
|
@ -135,7 +135,7 @@ class Footer extends ImmutablePureComponent {
|
|||
if (status.get('reblogged')) {
|
||||
dispatch(unreblog(status));
|
||||
} else if ((e && e.shiftKey) || !boostModal) {
|
||||
this._performReblog();
|
||||
this._performReblog(status);
|
||||
} else {
|
||||
dispatch(initBoostModal({ status, onReblog: this._performReblog }));
|
||||
}
|
||||
|
|
|
@ -133,7 +133,7 @@ class PublicTimeline extends PureComponent {
|
|||
const pinned = !!columnId;
|
||||
|
||||
return (
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} name='federated' label={intl.formatMessage(messages.title)}>
|
||||
<Column bindToDocument={!multiColumn} ref={this.setRef} label={intl.formatMessage(messages.title)}>
|
||||
<ColumnHeader
|
||||
icon='globe'
|
||||
active={hasUnread}
|
||||
|
|
|
@ -12,7 +12,7 @@ import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
|
|||
|
||||
import Option from './option';
|
||||
|
||||
export default class StatusCheckBox extends PureComponent {
|
||||
class StatusCheckBox extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
id: PropTypes.string.isRequired,
|
||||
|
@ -40,7 +40,9 @@ export default class StatusCheckBox extends PureComponent {
|
|||
<Avatar account={status.get('account')} size={46} />
|
||||
</div>
|
||||
|
||||
<div><DisplayName account={status.get('account')} /> · <VisibilityIcon visibility={status.get('visibility')} /><RelativeTimestamp timestamp={status.get('created_at')} /></div>
|
||||
<div>
|
||||
<DisplayName account={status.get('account')} /> · <VisibilityIcon visibility={status.get('visibility')} /><RelativeTimestamp timestamp={status.get('created_at')} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<StatusContent status={status} media={<MediaAttachments status={status} revealed={false} />} />
|
||||
|
@ -61,3 +63,5 @@ export default class StatusCheckBox extends PureComponent {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
export default StatusCheckBox;
|
||||
|
|
|
@ -61,13 +61,13 @@ class ActionBar extends PureComponent {
|
|||
onFavourite: PropTypes.func.isRequired,
|
||||
onReactionAdd: PropTypes.func.isRequired,
|
||||
onBookmark: PropTypes.func.isRequired,
|
||||
onMute: PropTypes.func,
|
||||
onMuteConversation: PropTypes.func,
|
||||
onBlock: PropTypes.func,
|
||||
onDelete: PropTypes.func.isRequired,
|
||||
onEdit: PropTypes.func.isRequired,
|
||||
onDirect: PropTypes.func.isRequired,
|
||||
onMention: PropTypes.func.isRequired,
|
||||
onMute: PropTypes.func,
|
||||
onBlock: PropTypes.func,
|
||||
onMuteConversation: PropTypes.func,
|
||||
onReport: PropTypes.func,
|
||||
onPin: PropTypes.func,
|
||||
onEmbed: PropTypes.func,
|
||||
|
@ -119,14 +119,14 @@ class ActionBar extends PureComponent {
|
|||
this.props.onMute(this.props.status.get('account'));
|
||||
};
|
||||
|
||||
handleConversationMuteClick = () => {
|
||||
this.props.onMuteConversation(this.props.status);
|
||||
};
|
||||
|
||||
handleBlockClick = () => {
|
||||
this.props.onBlock(this.props.status);
|
||||
};
|
||||
|
||||
handleConversationMuteClick = () => {
|
||||
this.props.onMuteConversation(this.props.status);
|
||||
};
|
||||
|
||||
handleReport = () => {
|
||||
this.props.onReport(this.props.status);
|
||||
};
|
||||
|
|
|
@ -3,13 +3,13 @@ import { PureComponent } from 'react';
|
|||
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
import classnames from 'classnames';
|
||||
import classNames from 'classnames';
|
||||
|
||||
import Immutable from 'immutable';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
|
||||
import { Blurhash } from 'flavours/glitch/components/blurhash';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { useBlurhash } from 'flavours/glitch/initial_state';
|
||||
import { decode as decodeIDNA } from 'flavours/glitch/utils/idna';
|
||||
|
||||
|
@ -148,7 +148,7 @@ export default class Card extends PureComponent {
|
|||
const provider = card.get('provider_name').length === 0 ? decodeIDNA(getHostname(card.get('url'))) : card.get('provider_name');
|
||||
const horizontal = (!compact && card.get('width') > card.get('height')) || card.get('type') !== 'link' || embedded;
|
||||
const interactive = card.get('type') !== 'link';
|
||||
const className = classnames('status-card', { horizontal, compact, interactive });
|
||||
const className = classNames('status-card', { horizontal, compact, interactive });
|
||||
const title = interactive ? <a className='status-card__title' href={card.get('url')} title={card.get('title')} rel='noopener noreferrer' target='_blank'><strong>{card.get('title')}</strong></a> : <strong className='status-card__title' title={card.get('title')}>{card.get('title')}</strong>;
|
||||
const language = card.get('language') || '';
|
||||
|
||||
|
@ -171,15 +171,17 @@ export default class Card extends PureComponent {
|
|||
let embed = '';
|
||||
let canvas = (
|
||||
<Blurhash
|
||||
className={classnames('status-card__image-preview', {
|
||||
className={classNames('status-card__image-preview', {
|
||||
'status-card__image-preview--hidden': revealed && this.state.previewLoaded,
|
||||
})}
|
||||
hash={card.get('blurhash')}
|
||||
dummy={!useBlurhash}
|
||||
/>
|
||||
);
|
||||
|
||||
const thumbnailDescription = card.get('image_description');
|
||||
const thumbnail = <img src={card.get('image')} alt={thumbnailDescription} title={thumbnailDescription} lang={language} style={thumbnailStyle} onLoad={this.handleImageLoad} className='status-card__image-image' />;
|
||||
|
||||
let spoilerButton = (
|
||||
<button type='button' onClick={this.handleReveal} className='spoiler-button__overlay'>
|
||||
<span className='spoiler-button__overlay__label'>
|
||||
|
@ -188,8 +190,9 @@ export default class Card extends PureComponent {
|
|||
</span>
|
||||
</button>
|
||||
);
|
||||
|
||||
spoilerButton = (
|
||||
<div className={classnames('spoiler-button', { 'spoiler-button--minified': revealed })}>
|
||||
<div className={classNames('spoiler-button', { 'spoiler-button--minified': revealed })}>
|
||||
{spoilerButton}
|
||||
</div>
|
||||
);
|
||||
|
@ -209,15 +212,14 @@ export default class Card extends PureComponent {
|
|||
{canvas}
|
||||
{thumbnail}
|
||||
|
||||
{revealed && (
|
||||
{revealed ? (
|
||||
<div className='status-card__actions'>
|
||||
<div>
|
||||
<button onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
|
||||
<button type='button' onClick={this.handleEmbedClick}><Icon id={iconVariant} /></button>
|
||||
{horizontal && <a href={card.get('url')} target='_blank' rel='noopener noreferrer'><Icon id='external-link' /></a>}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!revealed && spoilerButton}
|
||||
) : spoilerButton}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { AnimatedNumber } from 'flavours/glitch/components/animated_number';
|
|||
import AttachmentList from 'flavours/glitch/components/attachment_list';
|
||||
import EditedTimestamp from 'flavours/glitch/components/edited_timestamp';
|
||||
import { getHashtagBarForStatus } from 'flavours/glitch/components/hashtag_bar';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import { Icon } from 'flavours/glitch/components/icon';
|
||||
import PictureInPicturePlaceholder from 'flavours/glitch/components/picture_in_picture_placeholder';
|
||||
import VisibilityIcon from 'flavours/glitch/components/status_visibility_icon';
|
||||
import PollContainer from 'flavours/glitch/containers/poll_container';
|
||||
|
|
|
@ -726,7 +726,7 @@ class Status extends ImmutablePureComponent {
|
|||
bookmark: this.handleHotkeyBookmark,
|
||||
mention: this.handleHotkeyMention,
|
||||
openProfile: this.handleHotkeyOpenProfile,
|
||||
toggleSpoiler: this.handleToggleHidden,
|
||||
toggleHidden: this.handleToggleHidden,
|
||||
toggleSensitive: this.handleHotkeyToggleSensitive,
|
||||
openMedia: this.handleHotkeyOpenMedia,
|
||||
};
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
const emptyComponent = () => null;
|
||||
const noop = () => { };
|
||||
|
||||
class Bundle extends Component {
|
||||
class Bundle extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
fetchComponent: PropTypes.func.isRequired,
|
||||
|
@ -26,7 +26,7 @@ class Bundle extends Component {
|
|||
onFetchFail: noop,
|
||||
};
|
||||
|
||||
static cache = {};
|
||||
static cache = new Map;
|
||||
|
||||
state = {
|
||||
mod: undefined,
|
||||
|
@ -51,6 +51,7 @@ class Bundle extends Component {
|
|||
|
||||
load = (props) => {
|
||||
const { fetchComponent, onFetch, onFetchSuccess, onFetchFail, renderDelay } = props || this.props;
|
||||
const cachedMod = Bundle.cache.get(fetchComponent);
|
||||
|
||||
if (fetchComponent === undefined) {
|
||||
this.setState({ mod: null });
|
||||
|
@ -59,10 +60,8 @@ class Bundle extends Component {
|
|||
|
||||
onFetch();
|
||||
|
||||
if (Bundle.cache[fetchComponent.name]) {
|
||||
const mod = Bundle.cache[fetchComponent.name];
|
||||
|
||||
this.setState({ mod: mod.default });
|
||||
if (cachedMod) {
|
||||
this.setState({ mod: cachedMod.default });
|
||||
onFetchSuccess();
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
@ -76,7 +75,7 @@ class Bundle extends Component {
|
|||
|
||||
return fetchComponent()
|
||||
.then((mod) => {
|
||||
Bundle.cache[fetchComponent.name] = mod;
|
||||
Bundle.cache.set(fetchComponent, mod);
|
||||
this.setState({ mod: mod.default });
|
||||
onFetchSuccess();
|
||||
})
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { Component } from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { defineMessages, injectIntl } from 'react-intl';
|
||||
|
||||
|
@ -11,7 +11,7 @@ const messages = defineMessages({
|
|||
close: { id: 'bundle_modal_error.close', defaultMessage: 'Close' },
|
||||
});
|
||||
|
||||
class BundleModalError extends Component {
|
||||
class BundleModalError extends PureComponent {
|
||||
|
||||
static propTypes = {
|
||||
onRetry: PropTypes.func.isRequired,
|
||||
|
|
|
@ -75,7 +75,7 @@ export default class ColumnsArea extends ImmutablePureComponent {
|
|||
this.isRtlLayout = document.getElementsByTagName('body')[0].classList.contains('rtl');
|
||||
}
|
||||
|
||||
componentWillUpdate(nextProps) {
|
||||
UNSAFE_componentWillUpdate(nextProps) {
|
||||
if (this.props.singleColumn !== nextProps.singleColumn && nextProps.singleColumn) {
|
||||
this.node.removeEventListener('wheel', this.handleWheel);
|
||||
}
|
||||
|
|
|
@ -96,24 +96,10 @@ class MediaModal extends ImmutablePureComponent {
|
|||
|
||||
componentDidMount () {
|
||||
window.addEventListener('keydown', this.handleKeyDown, false);
|
||||
|
||||
this._sendBackgroundColor();
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
this.props.onChangeBackgroundColor(null);
|
||||
}
|
||||
|
||||
getIndex () {
|
||||
return this.state.index !== null ? this.state.index : this.props.index;
|
||||
}
|
||||
|
||||
toggleNavigation = () => {
|
||||
this.setState(prevState => ({
|
||||
navigationHidden: !prevState.navigationHidden,
|
||||
}));
|
||||
};
|
||||
|
||||
componentDidUpdate (prevProps, prevState) {
|
||||
if (prevState.index !== this.state.index) {
|
||||
this._sendBackgroundColor();
|
||||
|
@ -131,6 +117,22 @@ class MediaModal extends ImmutablePureComponent {
|
|||
}
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
window.removeEventListener('keydown', this.handleKeyDown);
|
||||
|
||||
this.props.onChangeBackgroundColor(null);
|
||||
}
|
||||
|
||||
getIndex () {
|
||||
return this.state.index !== null ? this.state.index : this.props.index;
|
||||
}
|
||||
|
||||
toggleNavigation = () => {
|
||||
this.setState(prevState => ({
|
||||
navigationHidden: !prevState.navigationHidden,
|
||||
}));
|
||||
};
|
||||
|
||||
render () {
|
||||
const { media, statusId, lang, intl, onClose } = this.props;
|
||||
const { navigationHidden } = this.state;
|
||||
|
|
|
@ -48,7 +48,7 @@ class NavigationPanel extends Component {
|
|||
return match || location.pathname.startsWith('/public');
|
||||
};
|
||||
|
||||
render() {
|
||||
render () {
|
||||
const { intl, onOpenSettings } = this.props;
|
||||
const { signedIn, disabledAccountId } = this.context.identity;
|
||||
|
||||
|
|
|
@ -40,14 +40,14 @@ export default class UploadArea extends PureComponent {
|
|||
|
||||
return (
|
||||
<Motion defaultStyle={{ backgroundOpacity: 0, backgroundScale: 0.95 }} style={{ backgroundOpacity: spring(active ? 1 : 0, { stiffness: 150, damping: 15 }), backgroundScale: spring(active ? 1 : 0.95, { stiffness: 200, damping: 3 }) }}>
|
||||
{({ backgroundOpacity, backgroundScale }) =>
|
||||
(<div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
|
||||
{({ backgroundOpacity, backgroundScale }) => (
|
||||
<div className='upload-area' style={{ visibility: active ? 'visible' : 'hidden', opacity: backgroundOpacity }}>
|
||||
<div className='upload-area__drop'>
|
||||
<div className='upload-area__background' style={{ transform: `scale(${backgroundScale})` }} />
|
||||
<div className='upload-area__content'><FormattedMessage id='upload_area.title' defaultMessage='Drag & drop to upload' /></div>
|
||||
</div>
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
)}
|
||||
</Motion>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import PropTypes from 'prop-types';
|
||||
import { PureComponent, Component } from 'react';
|
||||
import { PureComponent } from 'react';
|
||||
|
||||
import { defineMessages, FormattedMessage, injectIntl } from 'react-intl';
|
||||
|
||||
|
@ -68,9 +68,10 @@ import {
|
|||
PrivacyPolicy,
|
||||
} from './util/async-components';
|
||||
import { WrappedSwitch, WrappedRoute } from './util/react_router_helpers';
|
||||
|
||||
// Dummy import, to make sure that <Status /> ends up in the application bundle.
|
||||
// Without this it ends up in ~8 very commonly used bundles.
|
||||
import "../../components/status";
|
||||
import '../../components/status';
|
||||
|
||||
const messages = defineMessages({
|
||||
beforeUnload: { id: 'ui.beforeunload', defaultMessage: 'Your draft will be lost if you leave Mastodon.' },
|
||||
|
@ -119,7 +120,7 @@ const keyMap = {
|
|||
goToBlocked: 'g b',
|
||||
goToMuted: 'g m',
|
||||
goToRequests: 'g r',
|
||||
toggleSpoiler: 'x',
|
||||
toggleHidden: 'x',
|
||||
bookmark: 'd',
|
||||
toggleCollapse: 'shift+x',
|
||||
toggleSensitive: 'h',
|
||||
|
@ -255,7 +256,7 @@ class SwitchingColumnsArea extends PureComponent {
|
|||
|
||||
}
|
||||
|
||||
class UI extends Component {
|
||||
class UI extends PureComponent {
|
||||
|
||||
static contextTypes = {
|
||||
identity: PropTypes.object.isRequired,
|
||||
|
@ -270,7 +271,6 @@ class UI extends Component {
|
|||
hasComposingText: PropTypes.bool,
|
||||
hasMediaAttachments: PropTypes.bool,
|
||||
canUploadMore: PropTypes.bool,
|
||||
match: PropTypes.object.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
dropdownMenuIsOpen: PropTypes.bool,
|
||||
unreadNotifications: PropTypes.number,
|
||||
|
@ -287,7 +287,7 @@ class UI extends Component {
|
|||
draggingOver: false,
|
||||
};
|
||||
|
||||
handleBeforeUnload = (e) => {
|
||||
handleBeforeUnload = e => {
|
||||
const { intl, dispatch, hasComposingText, hasMediaAttachments } = this.props;
|
||||
|
||||
dispatch(synchronouslySubmitMarkers());
|
||||
|
@ -300,6 +300,14 @@ class UI extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
handleVisibilityChange = () => {
|
||||
const visibility = !document[this.visibilityHiddenProp];
|
||||
this.props.dispatch(notificationsSetVisibility(visibility));
|
||||
if (visibility) {
|
||||
this.props.dispatch(submitMarkers({ immediate: true }));
|
||||
}
|
||||
};
|
||||
|
||||
handleDragEnter = (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
|
@ -311,13 +319,14 @@ class UI extends Component {
|
|||
this.dragTargets.push(e.target);
|
||||
}
|
||||
|
||||
if (e.dataTransfer && e.dataTransfer.types.includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) {
|
||||
if (e.dataTransfer && Array.from(e.dataTransfer.types).includes('Files') && this.props.canUploadMore && this.context.identity.signedIn) {
|
||||
this.setState({ draggingOver: true });
|
||||
}
|
||||
};
|
||||
|
||||
handleDragOver = (e) => {
|
||||
if (this.dataTransferIsText(e.dataTransfer)) return false;
|
||||
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
|
||||
|
@ -372,14 +381,6 @@ class UI extends Component {
|
|||
}
|
||||
};
|
||||
|
||||
handleVisibilityChange = () => {
|
||||
const visibility = !document[this.visibilityHiddenProp];
|
||||
this.props.dispatch(notificationsSetVisibility(visibility));
|
||||
if (visibility) {
|
||||
this.props.dispatch(submitMarkers({ immediate: true }));
|
||||
}
|
||||
};
|
||||
|
||||
handleLayoutChange = debounce(() => {
|
||||
this.props.dispatch(clearHeight()); // The cached heights are no longer accurate, invalidate
|
||||
}, 500, {
|
||||
|
|
|
@ -85,10 +85,14 @@
|
|||
|
||||
display: block;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background-size: 36px 36px;
|
||||
overflow: hidden;
|
||||
|
||||
img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
&-inline {
|
||||
display: inline-block;
|
||||
|
@ -102,7 +106,7 @@
|
|||
overflow: hidden;
|
||||
position: relative;
|
||||
|
||||
& div {
|
||||
& > div {
|
||||
@include avatar-radius;
|
||||
|
||||
float: left;
|
||||
|
@ -110,6 +114,11 @@
|
|||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.account__avatar {
|
||||
width: 100% !important;
|
||||
height: 100% !important;
|
||||
}
|
||||
|
||||
&__label {
|
||||
display: block;
|
||||
position: absolute;
|
||||
|
@ -125,37 +134,13 @@
|
|||
}
|
||||
|
||||
.account__avatar-overlay {
|
||||
@include avatar-size(48px);
|
||||
|
||||
position: relative;
|
||||
|
||||
&-base {
|
||||
@include avatar-radius;
|
||||
@include avatar-size(36px);
|
||||
|
||||
img {
|
||||
@include avatar-radius;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
&-overlay {
|
||||
@include avatar-radius;
|
||||
@include avatar-size(24px);
|
||||
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
inset-inline-end: 0;
|
||||
z-index: 1;
|
||||
|
||||
img {
|
||||
@include avatar-radius;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -701,8 +701,6 @@ a.status__display-name,
|
|||
.status__avatar {
|
||||
flex: none;
|
||||
margin-inline-end: 10px;
|
||||
height: 48px;
|
||||
width: 48px;
|
||||
}
|
||||
|
||||
.muted {
|
||||
|
@ -1068,7 +1066,7 @@ a.status-card.compact:hover {
|
|||
width: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
inset-inline-start: 14px + ((48px - 2px) * 0.5);
|
||||
inset-inline-start: 14px + ((46px - 2px) * 0.5);
|
||||
|
||||
&--full {
|
||||
top: 0;
|
||||
|
@ -1079,7 +1077,7 @@ a.status-card.compact:hover {
|
|||
display: block;
|
||||
position: absolute;
|
||||
top: 10px - 4px;
|
||||
height: 48px + 4px + 4px;
|
||||
height: 46px + 4px + 4px;
|
||||
width: 2px;
|
||||
background: $ui-base-color;
|
||||
inset-inline-start: -2px;
|
||||
|
@ -1087,8 +1085,8 @@ a.status-card.compact:hover {
|
|||
}
|
||||
|
||||
&--first {
|
||||
top: 10px + 48px + 4px;
|
||||
height: calc(100% - (10px + 48px + 4px));
|
||||
top: 10px + 46px + 4px;
|
||||
height: calc(100% - (10px + 46px + 4px));
|
||||
|
||||
&::before {
|
||||
display: none;
|
||||
|
|
Loading…
Reference in a new issue