diff options
author | Reverite <github@reverite.sh> | 2019-02-24 22:18:10 -0800 |
---|---|---|
committer | Reverite <github@reverite.sh> | 2019-02-24 22:18:10 -0800 |
commit | 54e480ca0939ba737f5abdf4ee861cd63c025865 (patch) | |
tree | 760cdd75fd0922266b60e784c06db99902ef5692 /app/javascript/flavours/glitch/components | |
parent | ff9a09a9a7f73b558c53f334573b94198eb8d08a (diff) | |
parent | d82de360c13894746d3974d11c9505c8937ebdee (diff) |
Merge branch 'glitch' into production
Diffstat (limited to 'app/javascript/flavours/glitch/components')
11 files changed, 110 insertions, 43 deletions
diff --git a/app/javascript/flavours/glitch/components/account.js b/app/javascript/flavours/glitch/components/account.js index 072c601e0..4fcafc509 100644 --- a/app/javascript/flavours/glitch/components/account.js +++ b/app/javascript/flavours/glitch/components/account.js @@ -85,7 +85,7 @@ export default class Account extends ImmutablePureComponent { if (requested) { buttons = <IconButton disabled icon='hourglass' title={intl.formatMessage(messages.requested)} />; } else if (blocking) { - buttons = <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />; + buttons = <IconButton active icon='unlock' title={intl.formatMessage(messages.unblock, { name: account.get('username') })} onClick={this.handleBlock} />; } else if (muting) { let hidingNotificationsButton; if (account.getIn(['relationship', 'muting_notifications'])) { diff --git a/app/javascript/flavours/glitch/components/domain.js b/app/javascript/flavours/glitch/components/domain.js index f657cb8d2..74174f83d 100644 --- a/app/javascript/flavours/glitch/components/domain.js +++ b/app/javascript/flavours/glitch/components/domain.js @@ -32,7 +32,7 @@ export default class Account extends ImmutablePureComponent { </span> <div className='domain__buttons'> - <IconButton active icon='unlock-alt' title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} /> + <IconButton active icon='unlock' title={intl.formatMessage(messages.unblockDomain, { domain })} onClick={this.handleDomainUnblock} /> </div> </div> </div> diff --git a/app/javascript/flavours/glitch/components/intersection_observer_article.js b/app/javascript/flavours/glitch/components/intersection_observer_article.js index 6eeca5598..900c98638 100644 --- a/app/javascript/flavours/glitch/components/intersection_observer_article.js +++ b/app/javascript/flavours/glitch/components/intersection_observer_article.js @@ -63,7 +63,7 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent } updateStateAfterIntersection = (prevState) => { - if (prevState.isIntersecting && !this.entry.isIntersecting) { + if (prevState.isIntersecting !== false && !this.entry.isIntersecting) { scheduleIdleTask(this.hideIfNotIntersecting); } return { @@ -103,24 +103,23 @@ export default class IntersectionObserverArticle extends ImmutablePureComponent const { children, id, index, listLength, cachedHeight } = this.props; const { isIntersecting, isHidden } = this.state; + const style = {}; + if (!isIntersecting && (isHidden || cachedHeight)) { - return ( - <article - ref={this.handleRef} - aria-posinset={index + 1} - aria-setsize={listLength} - style={{ height: `${this.height || cachedHeight}px`, opacity: 0, overflow: 'hidden' }} - data-id={id} - tabIndex='0' - > - {children && React.cloneElement(children, { hidden: true })} - </article> - ); + style.height = `${this.height || cachedHeight || 150}px`; + style.opacity = 0; + style.overflow = 'hidden'; } return ( - <article ref={this.handleRef} aria-posinset={index + 1} aria-setsize={listLength} data-id={id} tabIndex='0'> - {children && React.cloneElement(children, { hidden: false })} + <article + ref={this.handleRef} + aria-posinset={index + 1} + aria-setsize={listLength} + data-id={id} + tabIndex='0' + style={style}> + {children && React.cloneElement(children, { hidden: !isIntersecting && (isHidden || cachedHeight) })} </article> ); } diff --git a/app/javascript/flavours/glitch/components/media_gallery.js b/app/javascript/flavours/glitch/components/media_gallery.js index d0226bbbb..1fa25ee4e 100644 --- a/app/javascript/flavours/glitch/components/media_gallery.js +++ b/app/javascript/flavours/glitch/components/media_gallery.js @@ -224,6 +224,8 @@ export default class MediaGallery extends React.PureComponent { size: PropTypes.object, onOpenMedia: PropTypes.func.isRequired, intl: PropTypes.object.isRequired, + defaultWidth: PropTypes.number, + cacheWidth: PropTypes.func, }; static defaultProps = { @@ -232,6 +234,7 @@ export default class MediaGallery extends React.PureComponent { state = { visible: this.props.revealed === undefined ? (displayMedia !== 'hide_all' && !this.props.sensitive || displayMedia === 'show_all') : this.props.revealed, + width: this.props.defaultWidth, }; componentWillReceiveProps (nextProps) { @@ -259,6 +262,7 @@ export default class MediaGallery extends React.PureComponent { handleRef = (node) => { this.node = node; if (node && node.offsetWidth && node.offsetWidth != this.state.width) { + if (this.props.cacheWidth) this.props.cacheWidth(node.offsetWidth); this.setState({ width: node.offsetWidth, }); @@ -271,10 +275,12 @@ export default class MediaGallery extends React.PureComponent { } render () { - const { media, intl, sensitive, letterbox, fullwidth } = this.props; - const { width, visible } = this.state; + const { media, intl, sensitive, letterbox, fullwidth, defaultWidth } = this.props; + const { visible } = this.state; const size = media.take(4).size; + const width = this.state.width || defaultWidth; + let children; const style = {}; diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js index 7cd0774a9..462185bbc 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.js +++ b/app/javascript/flavours/glitch/components/scrollable_list.js @@ -40,6 +40,7 @@ export default class ScrollableList extends PureComponent { state = { fullscreen: null, + cachedMediaWidth: 300, }; intersectionObserverWrapper = new IntersectionObserverWrapper(); @@ -128,7 +129,7 @@ export default class ScrollableList extends PureComponent { } getScrollPosition = () => { - if (this.node && this.node.scrollTop > 0) { + if (this.node && (this.node.scrollTop > 0 || this.mouseMovedRecently)) { return {height: this.node.scrollHeight, top: this.node.scrollTop}; } else { return null; @@ -141,6 +142,10 @@ export default class ScrollableList extends PureComponent { this.setScrollTop(newScrollTop); } + cacheMediaWidth = (width) => { + if (width && this.state.cachedMediaWidth != width) this.setState({ cachedMediaWidth: width }); + } + getSnapshotBeforeUpdate (prevProps, prevState) { const someItemInserted = React.Children.count(prevProps.children) > 0 && React.Children.count(prevProps.children) < React.Children.count(this.props.children) && @@ -252,7 +257,12 @@ export default class ScrollableList extends PureComponent { intersectionObserverWrapper={this.intersectionObserverWrapper} saveHeightKey={trackScroll ? `${this.context.router.route.location.key}:${scrollKey}` : null} > - {React.cloneElement(child, {getScrollPosition: this.getScrollPosition, updateScrollBottom: this.updateScrollBottom})} + {React.cloneElement(child, { + getScrollPosition: this.getScrollPosition, + updateScrollBottom: this.updateScrollBottom, + cachedMediaWidth: this.state.cachedMediaWidth, + cacheMediaWidth: this.cacheMediaWidth, + })} </IntersectionObserverArticleContainer> ))} diff --git a/app/javascript/flavours/glitch/components/status.js b/app/javascript/flavours/glitch/components/status.js index 9ff53485e..349f9c6cc 100644 --- a/app/javascript/flavours/glitch/components/status.js +++ b/app/javascript/flavours/glitch/components/status.js @@ -72,6 +72,8 @@ export default class Status extends ImmutablePureComponent { updateScrollBottom: PropTypes.func, expanded: PropTypes.bool, intl: PropTypes.object.isRequired, + cacheMediaWidth: PropTypes.func, + cachedMediaWidth: PropTypes.number, }; state = { @@ -214,6 +216,8 @@ export default class Status extends ImmutablePureComponent { // Hack to fix timeline jumps on second rendering when auto-collapsing this.setState({ autoCollapsed: true }); } + + this.didShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card') && this.props.settings.get('inline_preview_cards'); } getSnapshotBeforeUpdate (prevProps, prevState) { @@ -226,8 +230,10 @@ export default class Status extends ImmutablePureComponent { // Hack to fix timeline jumps on second rendering when auto-collapsing componentDidUpdate (prevProps, prevState, snapshot) { - if (this.state.autoCollapsed) { - this.setState({ autoCollapsed: false }); + const doShowCard = !this.props.muted && !this.props.hidden && this.props.status && this.props.status.get('card') && this.props.settings.get('inline_preview_cards'); + if (this.state.autoCollapsed || (doShowCard && !this.didShowCard)) { + if (doShowCard) this.didShowCard = true; + if (this.state.autoCollapsed) this.setState({ autoCollapsed: false }); if (snapshot !== null && this.props.updateScrollBottom) { if (this.node.offsetTop < snapshot.top) { this.props.updateScrollBottom(snapshot.height - snapshot.top); @@ -236,6 +242,15 @@ export default class Status extends ImmutablePureComponent { } } + componentWillUnmount() { + if (this.node && this.props.getScrollPosition) { + const position = this.props.getScrollPosition(); + if (position !== null && this.node.offsetTop < position.top) { + requestAnimationFrame(() => { this.props.updateScrollBottom(position.height - position.top); }); + } + } + } + // `setCollapsed()` sets the value of `isCollapsed` in our state, that is, // whether the toot is collapsed or not. @@ -384,15 +399,7 @@ export default class Status extends ImmutablePureComponent { if (hidden) { return ( - <div - ref={this.handleRef} - data-id={status.get('id')} - style={{ - height: `${this.height}px`, - opacity: 0, - overflow: 'hidden', - }} - > + <div ref={this.handleRef}> {status.getIn(['account', 'display_name']) || status.getIn(['account', 'username'])} {' '} {status.get('content')} @@ -408,7 +415,7 @@ export default class Status extends ImmutablePureComponent { return ( <HotKeys handlers={minHandlers}> - <div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0'> + <div className='status__wrapper status__wrapper--filtered focusable' tabIndex='0' ref={this.handleRef}> <FormattedMessage id='status.filtered' defaultMessage='Filtered' /> </div> </HotKeys> @@ -453,6 +460,8 @@ export default class Status extends ImmutablePureComponent { fullwidth={settings.getIn(['media', 'fullwidth'])} preventPlayback={isCollapsed || !isExpanded} onOpenVideo={this.handleOpenVideo} + width={this.props.cachedMediaWidth} + cacheWidth={this.props.cacheMediaWidth} />)} </Bundle> ); @@ -468,6 +477,8 @@ export default class Status extends ImmutablePureComponent { fullwidth={settings.getIn(['media', 'fullwidth'])} hidden={isCollapsed || !isExpanded} onOpenMedia={this.props.onOpenMedia} + cacheWidth={this.props.cacheMediaWidth} + defaultWidth={this.props.cachedMediaWidth} /> )} </Bundle> @@ -484,6 +495,8 @@ export default class Status extends ImmutablePureComponent { onOpenMedia={this.props.onOpenMedia} card={status.get('card')} compact + cacheWidth={this.props.cacheMediaWidth} + defaultWidth={this.props.cachedMediaWidth} /> ); mediaIcon = 'link'; diff --git a/app/javascript/flavours/glitch/components/status_action_bar.js b/app/javascript/flavours/glitch/components/status_action_bar.js index 16abcab4e..1d3130604 100644 --- a/app/javascript/flavours/glitch/components/status_action_bar.js +++ b/app/javascript/flavours/glitch/components/status_action_bar.js @@ -34,6 +34,7 @@ const messages = defineMessages({ embed: { id: 'status.embed', defaultMessage: 'Embed' }, admin_account: { id: 'status.admin_account', defaultMessage: 'Open moderation interface for @{name}' }, admin_status: { id: 'status.admin_status', defaultMessage: 'Open this status in the moderation interface' }, + copy: { id: 'status.copy', defaultMessage: 'Copy link to status' }, }); const obfuscatedCount = count => { @@ -82,7 +83,11 @@ export default class StatusActionBar extends ImmutablePureComponent { ] handleReplyClick = () => { - this.props.onReply(this.props.status, this.context.router.history); + if (me) { + this.props.onReply(this.props.status, this.context.router.history); + } else { + this._openInteractionDialog('reply'); + } } handleShareClick = () => { @@ -93,17 +98,29 @@ export default class StatusActionBar extends ImmutablePureComponent { } handleFavouriteClick = (e) => { - this.props.onFavourite(this.props.status, e); + if (me) { + this.props.onFavourite(this.props.status, e); + } else { + this._openInteractionDialog('favourite'); + } } handleBookmarkClick = (e) => { this.props.onBookmark(this.props.status, e); } - handleReblogClick = (e) => { - this.props.onReblog(this.props.status, e); + handleReblogClick = e => { + if (me) { + this.props.onReblog(this.props.status, e); + } else { + this._openInteractionDialog('reblog'); + } } + _openInteractionDialog = type => { + window.open(`/interact/${this.props.status.get('id')}?type=${type}`, 'mastodon-intent', 'width=445,height=600,resizable=no,menubar=no,status=no,scrollbars=yes'); + } + handleDeleteClick = () => { this.props.onDelete(this.props.status, this.context.router.history); } @@ -148,13 +165,32 @@ export default class StatusActionBar extends ImmutablePureComponent { this.props.onMuteConversation(this.props.status); } + handleCopy = () => { + const url = this.props.status.get('url'); + const textarea = document.createElement('textarea'); + + textarea.textContent = url; + textarea.style.position = 'fixed'; + + document.body.appendChild(textarea); + + try { + textarea.select(); + document.execCommand('copy'); + } catch (e) { + + } finally { + document.body.removeChild(textarea); + } + } + render () { const { status, intl, withDismiss, showReplyCount } = this.props; const mutingConversation = status.get('muted'); const anonymousAccess = !me; const publicStatus = ['public', 'unlisted'].includes(status.get('visibility')); - const reblogDisabled = anonymousAccess || (status.get('visibility') === 'direct' || (status.get('visibility') === 'private' && me !== status.getIn(['account', 'id']))); + const reblogDisabled = status.get('visibility') === 'direct' || (status.get('visibility') === 'private' && me !== status.getIn(['account', 'id'])); const reblogMessage = status.get('visibility') === 'private' ? messages.reblog_private : messages.reblog; let menu = []; @@ -165,6 +201,7 @@ export default class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.open), action: this.handleOpen }); if (publicStatus) { + menu.push({ text: intl.formatMessage(messages.copy), action: this.handleCopy }); menu.push({ text: intl.formatMessage(messages.embed), action: this.handleEmbed }); } @@ -189,6 +226,7 @@ export default class StatusActionBar extends ImmutablePureComponent { menu.push({ text: intl.formatMessage(messages.mute, { name: status.getIn(['account', 'username']) }), action: this.handleMuteClick }); menu.push({ text: intl.formatMessage(messages.block, { name: status.getIn(['account', 'username']) }), action: this.handleBlockClick }); menu.push({ text: intl.formatMessage(messages.report, { name: status.getIn(['account', 'username']) }), action: this.handleReport }); + if (isStaff && (accountAdminLink || statusAdminLink)) { menu.push(null); if (accountAdminLink !== undefined) { @@ -221,7 +259,6 @@ export default class StatusActionBar extends ImmutablePureComponent { let replyButton = ( <IconButton className='status__action-bar-button' - disabled={anonymousAccess} title={replyTitle} icon={replyIcon} onClick={this.handleReplyClick} @@ -240,7 +277,7 @@ export default class StatusActionBar extends ImmutablePureComponent { <div className='status__action-bar'> {replyButton} <IconButton className='status__action-bar-button' disabled={reblogDisabled} active={status.get('reblogged')} pressed={status.get('reblogged')} title={reblogDisabled ? intl.formatMessage(messages.cannot_reblog) : intl.formatMessage(reblogMessage)} icon={reblogIcon} onClick={this.handleReblogClick} /> - <IconButton className='status__action-bar-button star-icon' disabled={anonymousAccess} animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /> + <IconButton className='status__action-bar-button star-icon' animate active={status.get('favourited')} pressed={status.get('favourited')} title={intl.formatMessage(messages.favourite)} icon='star' onClick={this.handleFavouriteClick} /> {shareButton} <IconButton className='status__action-bar-button bookmark-icon' disabled={anonymousAccess} active={status.get('bookmarked')} pressed={status.get('bookmarked')} title={intl.formatMessage(messages.bookmark)} icon='bookmark' onClick={this.handleBookmarkClick} /> diff --git a/app/javascript/flavours/glitch/components/status_content.js b/app/javascript/flavours/glitch/components/status_content.js index 6542df65b..c60d63f9a 100644 --- a/app/javascript/flavours/glitch/components/status_content.js +++ b/app/javascript/flavours/glitch/components/status_content.js @@ -17,6 +17,7 @@ export default class StatusContent extends React.PureComponent { mediaIcon: PropTypes.string, parseClick: PropTypes.func, disabled: PropTypes.bool, + onUpdate: PropTypes.func, }; state = { @@ -62,6 +63,7 @@ export default class StatusContent extends React.PureComponent { componentDidUpdate () { this._updateStatusLinks(); + if (this.props.onUpdate) this.props.onUpdate(); } onLinkClick = (e) => { diff --git a/app/javascript/flavours/glitch/components/status_header.js b/app/javascript/flavours/glitch/components/status_header.js index 65458e3f0..f9321904c 100644 --- a/app/javascript/flavours/glitch/components/status_header.js +++ b/app/javascript/flavours/glitch/components/status_header.js @@ -19,7 +19,7 @@ export default class StatusHeader extends React.PureComponent { // Handles clicks on account name/image handleAccountClick = (e) => { const { status, parseClick } = this.props; - parseClick(e, `/accounts/${+status.getIn(['account', 'id'])}`); + parseClick(e, `/accounts/${status.getIn(['account', 'id'])}`); } // Rendering. diff --git a/app/javascript/flavours/glitch/components/status_prepend.js b/app/javascript/flavours/glitch/components/status_prepend.js index f4ef83135..4e329f546 100644 --- a/app/javascript/flavours/glitch/components/status_prepend.js +++ b/app/javascript/flavours/glitch/components/status_prepend.js @@ -15,7 +15,7 @@ export default class StatusPrepend extends React.PureComponent { handleClick = (e) => { const { account, parseClick } = this.props; - parseClick(e, `/accounts/${+account.get('id')}`); + parseClick(e, `/accounts/${account.get('id')}`); } Message = () => { diff --git a/app/javascript/flavours/glitch/components/status_visibility_icon.js b/app/javascript/flavours/glitch/components/status_visibility_icon.js index 017b69cbb..5e7b8ed00 100644 --- a/app/javascript/flavours/glitch/components/status_visibility_icon.js +++ b/app/javascript/flavours/glitch/components/status_visibility_icon.js @@ -25,7 +25,7 @@ export default class VisibilityIcon extends ImmutablePureComponent { const visibilityClass = { public: 'globe', - unlisted: 'unlock-alt', + unlisted: 'unlock', private: 'lock', direct: 'envelope', }[visibility]; |