This commit is contained in:
mgabdev
2020-03-04 17:26:01 -05:00
parent 143725b5bd
commit 567894f614
109 changed files with 976 additions and 1643 deletions

View File

@@ -0,0 +1,132 @@
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { NavLink } from 'react-router-dom'
import { decode } from 'blurhash'
import { autoPlayGif, displayMedia } from '../initial_state'
import Icon from './icon'
import Image from './image'
import Text from './text'
export default class MediaItem extends ImmutablePureComponent {
static propTypes = {
attachment: ImmutablePropTypes.map.isRequired,
}
state = {
visible: displayMedia !== 'hide_all' && !this.props.attachment.getIn(['status', 'sensitive']) || displayMedia === 'show_all',
loaded: false,
}
componentDidMount() {
if (this.props.attachment.get('blurhash')) {
this._decode()
}
}
componentDidUpdate(prevProps) {
if (prevProps.attachment.get('blurhash') !== this.props.attachment.get('blurhash') && this.props.attachment.get('blurhash')) {
this._decode()
}
}
_decode() {
const hash = this.props.attachment.get('blurhash')
const pixels = decode(hash, 160, 160)
if (pixels && this.canvas) {
const ctx = this.canvas.getContext('2d')
const imageData = new ImageData(pixels, 160, 160)
ctx.putImageData(imageData, 0, 0)
}
}
setCanvasRef = c => {
this.canvas = c
}
handleImageLoad = () => {
this.setState({ loaded: true })
}
hoverToPlay() {
return !autoPlayGif && ['gifv', 'video'].indexOf(this.props.attachment.get('type')) !== -1
}
render() {
const { attachment } = this.props
const { visible, loaded } = this.state
const status = attachment.get('status')
const title = status.get('spoiler_text') || attachment.get('description')
const attachmentType = attachment.get('type')
let badge = null
if (attachmentType === 'video') {
const duration = attachment.getIn(['meta', 'duration'])
badge = (duration / 60).toFixed(2)
} else if (attachmentType === 'gifv') {
badge = 'GIF'
}
return (
<div className={[_s.default, _s.width25PC, _s.paddingTop25PC].join(' ')}>
<div className={[_s.default, _s.positionAbsolute, _s.top0, _s.height100PC, _s.width100PC, _s.paddingVertical5PX, _s.paddingHorizontal5PX].join(' ')}>
<NavLink
to={status.get('url')} /* : todo : */
title={title}
className={[_s.default, _s.width100PC, _s.height100PC, _s.border1PX, _s.borderColorSecondary, _s.overflowHidden].join(' ')}
>
{
(!loaded || !visible) &&
<canvas
height='100%'
width='100%'
ref={this.setCanvasRef}
className={[_s.default, _s.width100PC, _s.height100PC, _s.z2].join(' ')}
/>
}
{
visible &&
<Image
height='100%'
src={attachment.get('preview_url')}
alt={attachment.get('description')}
title={attachment.get('description')}
onLoad={this.handleImageLoad}
className={_s.z1}
/>
}
<div className={[_s.default, _s.alignItemsCenter, _s.justifyContentCenter, _s.height100PC, _s.width100PC, _s.z3, _s.positionAbsolute].join(' ')}>
{
!visible &&
<Icon
id='hidden'
width='22px'
height='22px'
className={[_s.fillColorWhite].join('')}
/>
}
{
!!badge &&
<div className={[_s.default, _s.positionAbsolute, _s.radiusSmall, _s.backgroundColorOpaque, _s.paddingHorizontal5PX, _s.paddingVertical5PX, _s.marginRight5PX, _s.marginVertical5PX, _s.bottom0, _s.right0].join(' ')}>
<Text size='extraSmall' color='white'>
{badge}
</Text>
</div>
}
</div>
</NavLink>
</div>
</div>
)
}
}

View File

@@ -24,7 +24,7 @@ class BoostModal extends ImmutablePureComponent {
static propTypes = {
status: ImmutablePropTypes.map.isRequired,
onReblog: PropTypes.func.isRequired,
onRepost: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
intl: PropTypes.object.isRequired,
};
@@ -33,8 +33,8 @@ class BoostModal extends ImmutablePureComponent {
this.button.focus();
}
handleReblog = () => {
this.props.onReblog(this.props.status);
handleRepost = () => {
this.props.onRepost(this.props.status);
this.props.onClose();
}
@@ -96,7 +96,7 @@ class BoostModal extends ImmutablePureComponent {
}
})}
</div>
<Button text={intl.formatMessage(buttonText)} onClick={this.handleReblog} ref={this.setRef} />
<Button text={intl.formatMessage(buttonText)} onClick={this.handleRepost} ref={this.setRef} />
</div>
</div>
);

View File

@@ -11,7 +11,7 @@ const messages = defineMessages({
reply: { id: 'keyboard_shortcuts.reply', defaultMessage: 'reply' },
mention: { id: 'keyboard_shortcuts.mention', defaultMessage: 'mention author' },
profile: { id: 'keyboard_shortcuts.profile', defaultMessage: 'open author\'s profile' },
favourite: { id: 'keyboard_shortcuts.favourite', defaultMessage: 'favorite' },
favorite: { id: 'keyboard_shortcuts.favorite', defaultMessage: 'favorite' },
boost: { id: 'keyboard_shortcuts.boost', defaultMessage: 'repost' },
enter: { id: 'keyboard_shortcuts.enter', defaultMessage: 'open status' },
toggle_hidden: { id: 'keyboard_shortcuts.toggle_hidden', defaultMessage: 'show/hide text behind CW' },
@@ -28,7 +28,7 @@ const messages = defineMessages({
notifications: { id: 'keyboard_shortcuts.notifications', defaultMessage: 'open notifications column' },
direct: { id: 'keyboard_shortcuts.direct', defaultMessage: 'open direct messages column' },
start: { id: 'keyboard_shortcuts.start', defaultMessage: 'open "get started" column' },
favourites: { id: 'keyboard_shortcuts.favourites', defaultMessage: 'open favorites list' },
favorites: { id: 'keyboard_shortcuts.favorites', defaultMessage: 'open favorites list' },
pinned: { id: 'keyboard_shortcuts.pinned', defaultMessage: 'open pinned gabs list' },
my_profile: { id: 'keyboard_shortcuts.my_profile', defaultMessage: 'open your profile' },
blocked: { id: 'keyboard_shortcuts.blocked', defaultMessage: 'open blocked users list' },
@@ -65,7 +65,7 @@ class HotkeysModal extends ImmutablePureComponent {
<HotKeysModalRow hotkey='r' action={intl.formatMessage(messages.reply)} />
<HotKeysModalRow hotkey='m' action={intl.formatMessage(messages.mention)} />
<HotKeysModalRow hotkey='p' action={intl.formatMessage(messages.profile)} />
<HotKeysModalRow hotkey='f' action={intl.formatMessage(messages.favourite)} />
<HotKeysModalRow hotkey='f' action={intl.formatMessage(messages.favorite)} />
<HotKeysModalRow hotkey='b' action={intl.formatMessage(messages.boost)} />
<HotKeysModalRow hotkey='enter, o' action={intl.formatMessage(messages.enter)} />
<HotKeysModalRow hotkey='x' action={intl.formatMessage(messages.toggle_hidden)} />
@@ -108,7 +108,7 @@ class HotkeysModal extends ImmutablePureComponent {
</thead>
<tbody>
<HotKeysModalRow hotkey='g + s' action={intl.formatMessage(messages.start)} />
<HotKeysModalRow hotkey='g + f' action={intl.formatMessage(messages.favourites)} />
<HotKeysModalRow hotkey='g + f' action={intl.formatMessage(messages.favorites)} />
<HotKeysModalRow hotkey='g + p' action={intl.formatMessage(messages.pinned)} />
<HotKeysModalRow hotkey='g + u' action={intl.formatMessage(messages.my_profile)} />
<HotKeysModalRow hotkey='g + b' action={intl.formatMessage(messages.blocked)} />

View File

@@ -51,7 +51,7 @@ class ProfileStatsPanel extends ImmutablePureComponent {
account.get('id') === me &&
<UserStat
title={intl.formatMessage(messages.favorites)}
value={shortNumberFormat(account.get('favourite_count'))}
value={shortNumberFormat(account.get('favourite_count'))} /* : todo : */
to={`/${account.get('acct')}/favorites`}
/>
}

View File

@@ -12,7 +12,7 @@ export default class StatusOptionsPopover extends PureComponent {
// menu.push({ text: intl.formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
// } else {
// if (status.get('visibility') === 'private') {
// menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
// menu.push({ text: intl.formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleRepostClick });
// }
// }

View File

@@ -73,7 +73,7 @@ export default class ScrollableList extends PureComponent {
this.scrollToTopOnMouseIdle = false;
}
componentDidMount () {
componentDidMount() {
this.window = window;
this.documentElement = document.scrollingElement || document.documentElement;
@@ -97,7 +97,7 @@ export default class ScrollableList extends PureComponent {
this.setScrollTop(newScrollTop);
}
componentDidUpdate (prevProps, prevState, snapshot) {
componentDidUpdate(prevProps, prevState, snapshot) {
// Reset the scroll position when a new child comes in in order not to
// jerk the scrollbar around if you're already scrolled down the page.
if (snapshot !== null) {
@@ -105,12 +105,12 @@ export default class ScrollableList extends PureComponent {
}
}
attachScrollListener () {
attachScrollListener() {
this.window.addEventListener('scroll', this.handleScroll);
this.window.addEventListener('wheel', this.handleWheel);
}
detachScrollListener () {
detachScrollListener() {
this.window.removeEventListener('scroll', this.handleScroll);
this.window.removeEventListener('wheel', this.handleWheel);
}
@@ -139,16 +139,16 @@ export default class ScrollableList extends PureComponent {
this.lastScrollWasSynthetic = false;
}
}, 150, {
trailing: true,
});
trailing: true,
});
handleWheel = throttle(() => {
this.scrollToTopOnMouseIdle = false;
}, 150, {
trailing: true,
});
trailing: true,
});
getSnapshotBeforeUpdate (prevProps) {
getSnapshotBeforeUpdate(prevProps) {
const someItemInserted = React.Children.count(prevProps.children) > 0 &&
React.Children.count(prevProps.children) < React.Children.count(this.props.children) &&
this.getFirstChildKey(prevProps) !== this.getFirstChildKey(this.props);
@@ -166,21 +166,21 @@ export default class ScrollableList extends PureComponent {
}
}
componentWillUnmount () {
componentWillUnmount() {
this.clearMouseIdleTimer();
this.detachScrollListener();
this.detachIntersectionObserver();
}
attachIntersectionObserver () {
attachIntersectionObserver() {
this.intersectionObserverWrapper.connect();
}
detachIntersectionObserver () {
detachIntersectionObserver() {
this.intersectionObserverWrapper.disconnect();
}
getFirstChildKey (props) {
getFirstChildKey(props) {
const { children } = props;
let firstChild = children;
@@ -198,7 +198,7 @@ export default class ScrollableList extends PureComponent {
this.props.onLoadMore();
}
render () {
render() {
const { children, scrollKey, showLoading, isLoading, hasMore, emptyMessage, onLoadMore } = this.props;
const childrenCount = React.Children.count(children);
@@ -207,28 +207,33 @@ export default class ScrollableList extends PureComponent {
const loadMore = (hasMore && onLoadMore) ? <LoadMore visible={!isLoading} onClick={this.handleLoadMore} /> : null;
if (showLoading) {
return ( <ColumnIndicator type='loading' /> );
return <ColumnIndicator type='loading' />
} else if (isLoading || childrenCount > 0 || hasMore || !emptyMessage) {
return (
<div className='scrollable-list' onMouseMove={this.handleMouseMove}>
<div onMouseMove={this.handleMouseMove}>
<div role='feed'>
{React.Children.map(this.props.children, (child, index) => (
<IntersectionObserverArticle
key={child.key}
id={child.key}
index={index}
listLength={childrenCount}
intersectionObserverWrapper={this.intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
>
{React.cloneElement(child, {
getScrollPosition: this.getScrollPosition,
updateScrollBottom: this.updateScrollBottom,
cachedMediaWidth: this.state.cachedMediaWidth,
cacheMediaWidth: this.cacheMediaWidth,
})}
</IntersectionObserverArticle>
))}
{
!!this.props.children &&
React.Children.map(this.props.children, (child, index) => (
<IntersectionObserverArticle
key={child.key}
id={child.key}
index={index}
listLength={childrenCount}
intersectionObserverWrapper={this.intersectionObserverWrapper}
saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null}
>
{
React.cloneElement(child, {
getScrollPosition: this.getScrollPosition,
updateScrollBottom: this.updateScrollBottom,
cachedMediaWidth: this.state.cachedMediaWidth,
cacheMediaWidth: this.cacheMediaWidth,
})
}
</IntersectionObserverArticle>
))
}
{loadMore}
</div>
@@ -236,7 +241,7 @@ export default class ScrollableList extends PureComponent {
);
}
return ( <ColumnIndicator type='error' message={emptyMessage} /> );
return <ColumnIndicator type='error' message={emptyMessage} />
}
}

View File

@@ -76,8 +76,8 @@ class Status extends ImmutablePureComponent {
onReply: PropTypes.func,
onShowRevisions: PropTypes.func,
onQuote: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onFavorite: PropTypes.func,
onRepost: PropTypes.func,
onDelete: PropTypes.func,
onEdit: PropTypes.func,
onDirect: PropTypes.func,
@@ -212,12 +212,12 @@ class Status extends ImmutablePureComponent {
this.props.onReply(this._properStatus(), this.context.router.history);
};
handleHotkeyFavourite = () => {
this.props.onFavourite(this._properStatus());
handleHotkeyFavorite = () => {
this.props.onFavorite(this._properStatus());
};
handleHotkeyBoost = e => {
this.props.onReblog(this._properStatus(), e);
this.props.onRepost(this._properStatus(), e);
};
handleHotkeyMention = e => {
@@ -424,7 +424,7 @@ class Status extends ImmutablePureComponent {
const handlers = this.props.muted ? {} : {
reply: this.handleHotkeyReply,
favourite: this.handleHotkeyFavourite,
favorite: this.handleHotkeyFavorite,
boost: this.handleHotkeyBoost,
mention: this.handleHotkeyMention,
open: this.handleHotkeyOpen,

View File

@@ -63,8 +63,8 @@ class StatusActionBar extends ImmutablePureComponent {
onOpenUnauthorizedModal: PropTypes.func.isRequired,
onReply: PropTypes.func,
onQuote: PropTypes.func,
onFavourite: PropTypes.func,
onReblog: PropTypes.func,
onFavorite: PropTypes.func,
onRepost: PropTypes.func,
onDelete: PropTypes.func,
onMention: PropTypes.func,
onMute: PropTypes.func,
@@ -101,17 +101,17 @@ class StatusActionBar extends ImmutablePureComponent {
}
}
handleFavouriteClick = () => {
handleFavoriteClick = () => {
if (me) {
this.props.onFavourite(this.props.status)
this.props.onFavorite(this.props.status)
} else {
this.props.onOpenUnauthorizedModal()
}
}
handleReblogClick = e => {
handleRepostClick = e => {
if (me) {
this.props.onReblog(this.props.status, e)
this.props.onRepost(this.props.status, e)
} else {
this.props.onOpenUnauthorizedModal()
}
@@ -129,7 +129,7 @@ class StatusActionBar extends ImmutablePureComponent {
const reblogCount = status.get('reblogs_count')
const reblogTitle = !publicStatus ? formatMessage(messages.cannot_reblog) : formatMessage(messages.reblog)
const favoriteCount = status.get('favourites_count')
const favoriteCount = status.get('favorites_count') // : todo :
const shareButton = ('share' in navigator) && status.get('visibility') === 'public' && (
<IconButton className='status-action-bar-button' title={formatMessage(messages.share)} icon='share-alt' onClick={this.handleShareClick} />
@@ -139,8 +139,8 @@ class StatusActionBar extends ImmutablePureComponent {
{
title: formatMessage(messages.like),
icon: 'like',
active: !!status.get('favourited'),
onClick: this.handleFavouriteClick,
active: !!status.get('favorited'),
onClick: this.handleFavoriteClick,
},
{
title: formatMessage(messages.comment),
@@ -153,13 +153,13 @@ class StatusActionBar extends ImmutablePureComponent {
icon: (status.get('visibility') === 'private') ? 'lock' : 'repost',
disabled: !publicStatus,
active: !!status.get('reblogged'),
onClick: this.handleReblogClick,
onClick: this.handleRepostClick,
},
{
title: formatMessage(messages.share),
icon: 'share',
active: false,
onClick: this.handleFavouriteClick,
onClick: this.handleFavoriteClick,
},
]

View File

@@ -122,7 +122,7 @@ export default class StatusHeader extends ImmutablePureComponent {
menu.push({ text: formatMessage(status.get('pinned') ? messages.unpin : messages.pin), action: this.handlePinClick });
} else {
if (status.get('visibility') === 'private') {
menu.push({ text: formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleReblogClick });
menu.push({ text: formatMessage(status.get('reblogged') ? messages.cancel_reblog_private : messages.reblog_private), action: this.handleRepostClick });
}
}
menu.push({ text: formatMessage(messages.delete), action: this.handleDeleteClick });