diff options
Diffstat (limited to 'app/javascript/flavours/glitch')
-rw-r--r-- | app/javascript/flavours/glitch/actions/notifications.js | 5 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/components/poll.js | 40 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/components/scrollable_list.js | 11 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/features/community_timeline/index.js | 3 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/features/notifications/index.js | 2 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js | 2 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/features/ui/index.js | 7 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/locales/pt-PT.js (renamed from app/javascript/flavours/glitch/locales/pt.js) | 2 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/packs/public.js | 4 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/reducers/notifications.js | 18 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/reducers/timelines.js | 10 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/styles/accounts.scss | 1 | ||||
-rw-r--r-- | app/javascript/flavours/glitch/styles/polls.scss | 3 |
13 files changed, 81 insertions, 27 deletions
diff --git a/app/javascript/flavours/glitch/actions/notifications.js b/app/javascript/flavours/glitch/actions/notifications.js index 0c2331374..be48b1c77 100644 --- a/app/javascript/flavours/glitch/actions/notifications.js +++ b/app/javascript/flavours/glitch/actions/notifications.js @@ -165,7 +165,7 @@ export function expandNotifications({ maxId } = {}, done = noOp) { dispatch(importFetchedAccounts(response.data.map(item => item.account))); dispatch(importFetchedStatuses(response.data.map(item => item.status).filter(status => !!status))); - dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent && preferPendingItems)); + dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems)); fetchRelatedRelationships(dispatch, response.data); done(); }).catch(error => { @@ -182,11 +182,12 @@ export function expandNotificationsRequest(isLoadingMore) { }; }; -export function expandNotificationsSuccess(notifications, next, isLoadingMore, usePendingItems) { +export function expandNotificationsSuccess(notifications, next, isLoadingMore, isLoadingRecent, usePendingItems) { return { type: NOTIFICATIONS_EXPAND_SUCCESS, notifications, next, + isLoadingRecent: isLoadingRecent, usePendingItems, skipLoading: !isLoadingMore, }; diff --git a/app/javascript/flavours/glitch/components/poll.js b/app/javascript/flavours/glitch/components/poll.js index 36c4b236c..905aa54c1 100644 --- a/app/javascript/flavours/glitch/components/poll.js +++ b/app/javascript/flavours/glitch/components/poll.js @@ -32,8 +32,38 @@ class Poll extends ImmutablePureComponent { state = { selected: {}, + expired: null, }; + static getDerivedStateFromProps (props, state) { + const { poll, intl } = props; + const expired = poll.get('expired') || (new Date(poll.get('expires_at'))).getTime() < intl.now(); + return (expired === state.expired) ? null : { expired }; + } + + componentDidMount () { + this._setupTimer(); + } + + componentDidUpdate () { + this._setupTimer(); + } + + componentWillUnmount () { + clearTimeout(this._timer); + } + + _setupTimer () { + const { poll, intl } = this.props; + clearTimeout(this._timer); + if (!this.state.expired) { + const delay = (new Date(poll.get('expires_at'))).getTime() - intl.now(); + this._timer = setTimeout(() => { + this.setState({ expired: true }); + }, delay); + } + } + handleOptionChange = e => { const { target: { value } } = e; @@ -68,12 +98,11 @@ class Poll extends ImmutablePureComponent { this.props.dispatch(fetchPoll(this.props.poll.get('id'))); }; - renderOption (option, optionIndex) { + renderOption (option, optionIndex, showResults) { const { poll, disabled } = this.props; const percent = poll.get('votes_count') === 0 ? 0 : (option.get('votes_count') / poll.get('votes_count')) * 100; const leading = poll.get('options').filterNot(other => other.get('title') === option.get('title')).every(other => option.get('votes_count') > other.get('votes_count')); const active = !!this.state.selected[`${optionIndex}`]; - const showResults = poll.get('voted') || poll.get('expired'); let titleEmojified = option.get('title_emojified'); if (!titleEmojified) { @@ -112,19 +141,20 @@ class Poll extends ImmutablePureComponent { render () { const { poll, intl } = this.props; + const { expired } = this.state; if (!poll) { return null; } - const timeRemaining = poll.get('expired') ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />; - const showResults = poll.get('voted') || poll.get('expired'); + const timeRemaining = expired ? intl.formatMessage(messages.closed) : <RelativeTimestamp timestamp={poll.get('expires_at')} futureDate />; + const showResults = poll.get('voted') || expired; const disabled = this.props.disabled || Object.entries(this.state.selected).every(item => !item); return ( <div className='poll'> <ul> - {poll.get('options').map((option, i) => this.renderOption(option, i))} + {poll.get('options').map((option, i) => this.renderOption(option, i, showResults))} </ul> <div className='poll__footer'> diff --git a/app/javascript/flavours/glitch/components/scrollable_list.js b/app/javascript/flavours/glitch/components/scrollable_list.js index 5f42bdd8b..7c0b6d082 100644 --- a/app/javascript/flavours/glitch/components/scrollable_list.js +++ b/app/javascript/flavours/glitch/components/scrollable_list.js @@ -153,7 +153,9 @@ export default class ScrollableList extends PureComponent { 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); - if (someItemInserted && (this.node.scrollTop > 0 || this.mouseMovedRecently)) { + const pendingChanged = (prevProps.numPending > 0) !== (this.props.numPending > 0); + + if (pendingChanged || someItemInserted && (this.node.scrollTop > 0 || this.mouseMovedRecently)) { return this.node.scrollHeight - this.node.scrollTop; } else { return null; @@ -228,6 +230,13 @@ export default class ScrollableList extends PureComponent { handleLoadPending = e => { e.preventDefault(); this.props.onLoadPending(); + // Prevent the weird scroll-jumping behavior, as we explicitly don't want to + // scroll to top, and we know the scroll height is going to change + this.scrollToTopOnMouseIdle = false; + this.lastScrollWasSynthetic = false; + this.clearMouseIdleTimer(); + this.mouseIdleTimer = setTimeout(this.handleMouseIdle, MOUSE_IDLE_DELAY); + this.mouseMovedRecently = true; } render () { diff --git a/app/javascript/flavours/glitch/features/community_timeline/index.js b/app/javascript/flavours/glitch/features/community_timeline/index.js index cb7d72c53..5585edc9c 100644 --- a/app/javascript/flavours/glitch/features/community_timeline/index.js +++ b/app/javascript/flavours/glitch/features/community_timeline/index.js @@ -18,9 +18,10 @@ const mapStateToProps = (state, { onlyMedia, columnId }) => { const uuid = columnId; const columns = state.getIn(['settings', 'columns']); const index = columns.findIndex(c => c.get('uuid') === uuid); + const timelineState = state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`]); return { - hasUnread: state.getIn(['timelines', `community${onlyMedia ? ':media' : ''}`, 'unread']) > 0, + hasUnread: !!timelineState && (timelineState.get('unread') > 0 || timelineState.get('pendingItems').size > 0), onlyMedia: (columnId && index >= 0) ? columns.get(index).getIn(['params', 'other', 'onlyMedia']) : state.getIn(['settings', 'community', 'other', 'onlyMedia']), }; }; diff --git a/app/javascript/flavours/glitch/features/notifications/index.js b/app/javascript/flavours/glitch/features/notifications/index.js index bd1af97a9..99b2391c7 100644 --- a/app/javascript/flavours/glitch/features/notifications/index.js +++ b/app/javascript/flavours/glitch/features/notifications/index.js @@ -47,7 +47,7 @@ const mapStateToProps = state => ({ notifications: getNotifications(state), localSettings: state.get('local_settings'), isLoading: state.getIn(['notifications', 'isLoading'], true), - isUnread: state.getIn(['notifications', 'unread']) > 0, + isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0, hasMore: state.getIn(['notifications', 'hasMore']), numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size, notifCleaningActive: state.getIn(['notifications', 'cleaningMode']), diff --git a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js index ced7bd238..8bded391a 100644 --- a/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js +++ b/app/javascript/flavours/glitch/features/ui/components/focal_point_modal.js @@ -226,7 +226,7 @@ class FocalPointModal extends ImmutablePureComponent { <CharacterCounter max={1500} text={detecting ? '' : description} /> </div> - <Button disabled={!dirty || detecting || length(description) > 420} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} /> + <Button disabled={!dirty || detecting || length(description) > 1500} text={intl.formatMessage(messages.apply)} onClick={this.handleSubmit} /> </div> <div className='focal-point-modal__content'> diff --git a/app/javascript/flavours/glitch/features/ui/index.js b/app/javascript/flavours/glitch/features/ui/index.js index 7d9aeb02a..6cc1b1cde 100644 --- a/app/javascript/flavours/glitch/features/ui/index.js +++ b/app/javascript/flavours/glitch/features/ui/index.js @@ -64,6 +64,7 @@ const messages = defineMessages({ const mapStateToProps = state => ({ hasComposingText: state.getIn(['compose', 'text']).trim().length !== 0, hasMediaAttachments: state.getIn(['compose', 'media_attachments']).size > 0, + canUploadMore: !state.getIn(['compose', 'media_attachments']).some(x => ['audio', 'video'].includes(x.get('type'))) && state.getIn(['compose', 'media_attachments']).size < 4, layout: state.getIn(['local_settings', 'layout']), isWide: state.getIn(['local_settings', 'stretch']), navbarUnder: state.getIn(['local_settings', 'navbar_under']), @@ -230,6 +231,7 @@ class UI extends React.Component { isComposing: PropTypes.bool, hasComposingText: PropTypes.bool, hasMediaAttachments: PropTypes.bool, + canUploadMore: PropTypes.bool, match: PropTypes.object.isRequired, location: PropTypes.object.isRequired, history: PropTypes.object.isRequired, @@ -272,7 +274,7 @@ class UI extends React.Component { this.dragTargets.push(e.target); } - if (e.dataTransfer && e.dataTransfer.types.includes('Files')) { + if (e.dataTransfer && e.dataTransfer.types.includes('Files') && this.props.canUploadMore) { this.setState({ draggingOver: true }); } } @@ -293,12 +295,13 @@ class UI extends React.Component { handleDrop = (e) => { if (this.dataTransferIsText(e.dataTransfer)) return; + e.preventDefault(); this.setState({ draggingOver: false }); this.dragTargets = []; - if (e.dataTransfer && e.dataTransfer.files.length >= 1) { + if (e.dataTransfer && e.dataTransfer.files.length >= 1 && this.props.canUploadMore) { this.props.dispatch(uploadCompose(e.dataTransfer.files)); } } diff --git a/app/javascript/flavours/glitch/locales/pt.js b/app/javascript/flavours/glitch/locales/pt-PT.js index 0156f55ff..cf7afd17a 100644 --- a/app/javascript/flavours/glitch/locales/pt.js +++ b/app/javascript/flavours/glitch/locales/pt-PT.js @@ -1,4 +1,4 @@ -import inherited from 'mastodon/locales/pt.json'; +import inherited from 'mastodon/locales/pt-PT.json'; const messages = { // No translations available. diff --git a/app/javascript/flavours/glitch/packs/public.js b/app/javascript/flavours/glitch/packs/public.js index 72725d20b..019de2167 100644 --- a/app/javascript/flavours/glitch/packs/public.js +++ b/app/javascript/flavours/glitch/packs/public.js @@ -11,10 +11,10 @@ function main() { const React = require('react'); const ReactDOM = require('react-dom'); const Rellax = require('rellax'); - const createHistory = require('history').createBrowserHistory; + const { createBrowserHistory } = require('history'); const scrollToDetailedStatus = () => { - const history = createHistory(); + const history = createBrowserHistory(); const detailedStatuses = document.querySelectorAll('.public-layout .detailed-status'); const location = history.location; diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index 135995da6..8dc7a4aba 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -50,12 +50,12 @@ const notificationToMap = (state, notification) => ImmutableMap({ }); const normalizeNotification = (state, notification, usePendingItems) => { - if (usePendingItems) { - return state.update('pendingItems', list => list.unshift(notificationToMap(state, notification))); - } - const top = !shouldCountUnreadNotifications(state); + if (usePendingItems || !top || !state.get('pendingItems').isEmpty()) { + return state.update('pendingItems', list => list.unshift(notificationToMap(state, notification))).update('unread', unread => unread + 1); + } + if (top) { state = state.set('lastReadId', notification.id); } else { @@ -71,7 +71,7 @@ const normalizeNotification = (state, notification, usePendingItems) => { }); }; -const expandNormalizedNotifications = (state, notifications, next, usePendingItems) => { +const expandNormalizedNotifications = (state, notifications, next, isLoadingRecent, usePendingItems) => { const top = !(shouldCountUnreadNotifications(state)); const lastReadId = state.get('lastReadId'); let items = ImmutableList(); @@ -82,6 +82,8 @@ const expandNormalizedNotifications = (state, notifications, next, usePendingIte return state.withMutations(mutable => { if (!items.isEmpty()) { + usePendingItems = isLoadingRecent && (usePendingItems || !mutable.get('top') || !mutable.get('pendingItems').isEmpty()); + mutable.update(usePendingItems ? 'pendingItems' : 'items', list => { const lastIndex = 1 + list.findLastIndex( item => item !== null && (compareId(item.get('id'), items.last().get('id')) > 0 || item.get('id') === items.last().get('id')) @@ -117,7 +119,7 @@ const filterNotifications = (state, accountIds) => { }; const clearUnread = (state) => { - state = state.set('unread', 0); + state = state.set('unread', state.get('pendingItems').size); const lastNotification = state.get('items').find(item => item !== null); return state.set('lastReadId', lastNotification ? lastNotification.get('id') : '0'); } @@ -140,6 +142,8 @@ const deleteByStatus = (state, statusId) => { state = state.update('unread', unread => unread - deletedUnread.size); } const helper = list => list.filterNot(item => item !== null && item.get('status') === statusId); + const deletedUnread = state.get('pendingItems').filter(item => item !== null && item.get('status') === statusId && compareId(item.get('id'), lastReadId) > 0); + state = state.update('unread', unread => unread - deletedUnread.size); return state.update('items', helper).update('pendingItems', helper); }; @@ -216,7 +220,7 @@ export default function notifications(state = initialState, action) { case NOTIFICATIONS_UPDATE: return normalizeNotification(state, action.notification, action.usePendingItems); case NOTIFICATIONS_EXPAND_SUCCESS: - return expandNormalizedNotifications(state, action.notifications, action.next, action.usePendingItems); + return expandNormalizedNotifications(state, action.notifications, action.next, action.isLoadingRecent, action.usePendingItems); case ACCOUNT_BLOCK_SUCCESS: return filterNotifications(state, [action.relationship.id]); case ACCOUNT_MUTE_SUCCESS: diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js index 9b016a4c6..e6bef18e9 100644 --- a/app/javascript/flavours/glitch/reducers/timelines.js +++ b/app/javascript/flavours/glitch/reducers/timelines.js @@ -40,6 +40,7 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is if (timeline.endsWith(':pinned')) { mMap.set('items', statuses.map(status => status.get('id'))); } else if (!statuses.isEmpty()) { + usePendingItems = isLoadingRecent && (usePendingItems || !mMap.get('top') || !mMap.get('pendingItems').isEmpty()); mMap.update(usePendingItems ? 'pendingItems' : 'items', ImmutableList(), oldIds => { const newIds = statuses.map(status => status.get('id')); const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1; @@ -59,15 +60,16 @@ const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, is }; const updateTimeline = (state, timeline, status, usePendingItems) => { - if (usePendingItems) { + const top = state.getIn([timeline, 'top']); + + if (usePendingItems || !top || !state.getIn([timeline, 'pendingItems']).isEmpty()) { if (state.getIn([timeline, 'pendingItems'], ImmutableList()).includes(status.get('id')) || state.getIn([timeline, 'items'], ImmutableList()).includes(status.get('id'))) { return state; } - return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id')))); + return state.update(timeline, initialTimeline, map => map.update('pendingItems', list => list.unshift(status.get('id'))).update('unread', unread => unread + 1)); } - const top = state.getIn([timeline, 'top']); const ids = state.getIn([timeline, 'items'], ImmutableList()); const includesId = ids.includes(status.get('id')); const unread = state.getIn([timeline, 'unread'], 0); @@ -127,7 +129,7 @@ const filterTimeline = (timeline, state, relationship, statuses) => { const updateTop = (state, timeline, top) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { - if (top) mMap.set('unread', 0); + if (top) mMap.set('unread', mMap.get('pendingItems').size); mMap.set('top', top); })); }; diff --git a/app/javascript/flavours/glitch/styles/accounts.scss b/app/javascript/flavours/glitch/styles/accounts.scss index 0fae137f0..a827d271a 100644 --- a/app/javascript/flavours/glitch/styles/accounts.scss +++ b/app/javascript/flavours/glitch/styles/accounts.scss @@ -226,6 +226,7 @@ } .account__header__fields { + max-width: 100vw; padding: 0; margin: 15px -15px -15px; border: 0 none; diff --git a/app/javascript/flavours/glitch/styles/polls.scss b/app/javascript/flavours/glitch/styles/polls.scss index 6051ef45d..06f60408d 100644 --- a/app/javascript/flavours/glitch/styles/polls.scss +++ b/app/javascript/flavours/glitch/styles/polls.scss @@ -86,6 +86,9 @@ top: -1px; border-radius: 50%; vertical-align: middle; + margin-top: auto; + margin-bottom: auto; + flex: 0 0 18px; &.checkbox { border-radius: 4px; |