diff options
Diffstat (limited to 'app/assets/javascripts/components/reducers')
8 files changed, 219 insertions, 96 deletions
diff --git a/app/assets/javascripts/components/reducers/accounts.jsx b/app/assets/javascripts/components/reducers/accounts.jsx index f3938cee1..df9440093 100644 --- a/app/assets/javascripts/components/reducers/accounts.jsx +++ b/app/assets/javascripts/components/reducers/accounts.jsx @@ -33,7 +33,7 @@ import { STATUS_FETCH_SUCCESS, CONTEXT_FETCH_SUCCESS } from '../actions/statuses'; -import { SEARCH_SUGGESTIONS_READY } from '../actions/search'; +import { SEARCH_FETCH_SUCCESS } from '../actions/search'; import { NOTIFICATIONS_UPDATE, NOTIFICATIONS_REFRESH_SUCCESS, @@ -90,7 +90,6 @@ export default function accounts(state = initialState, action) { case REBLOGS_FETCH_SUCCESS: case FAVOURITES_FETCH_SUCCESS: case COMPOSE_SUGGESTIONS_READY: - case SEARCH_SUGGESTIONS_READY: case FOLLOW_REQUESTS_FETCH_SUCCESS: case FOLLOW_REQUESTS_EXPAND_SUCCESS: case BLOCKS_FETCH_SUCCESS: @@ -98,6 +97,7 @@ export default function accounts(state = initialState, action) { return normalizeAccounts(state, action.accounts); case NOTIFICATIONS_REFRESH_SUCCESS: case NOTIFICATIONS_EXPAND_SUCCESS: + case SEARCH_FETCH_SUCCESS: return normalizeAccountsFromStatuses(normalizeAccounts(state, action.accounts), action.statuses); case TIMELINE_REFRESH_SUCCESS: case TIMELINE_EXPAND_SUCCESS: diff --git a/app/assets/javascripts/components/reducers/compose.jsx b/app/assets/javascripts/components/reducers/compose.jsx index 77ec2705f..4470ad643 100644 --- a/app/assets/javascripts/components/reducers/compose.jsx +++ b/app/assets/javascripts/components/reducers/compose.jsx @@ -20,7 +20,8 @@ import { COMPOSE_SPOILERNESS_CHANGE, COMPOSE_SPOILER_TEXT_CHANGE, COMPOSE_VISIBILITY_CHANGE, - COMPOSE_LISTABILITY_CHANGE + COMPOSE_LISTABILITY_CHANGE, + COMPOSE_EMOJI_INSERT } from '../actions/compose'; import { TIMELINE_DELETE } from '../actions/timelines'; import { STORE_HYDRATE } from '../actions/store'; @@ -31,10 +32,10 @@ const initialState = Immutable.Map({ sensitive: false, spoiler: false, spoiler_text: '', - unlisted: false, - private: false, + privacy: null, text: '', - fileDropDate: null, + focusDate: null, + preselectDate: null, in_reply_to: null, is_submitting: false, is_uploading: false, @@ -65,8 +66,7 @@ function clearAll(state) { map.set('spoiler_text', ''); map.set('is_submitting', false); map.set('in_reply_to', null); - map.set('unlisted', state.get('default_privacy') === 'unlisted'); - map.set('private', state.get('default_privacy') === 'private'); + map.set('privacy', state.get('default_privacy')); map.update('media_attachments', list => list.clear()); }); }; @@ -99,9 +99,31 @@ const insertSuggestion = (state, position, token, completion) => { map.update('text', oldText => `${oldText.slice(0, position)}${completion} ${oldText.slice(position + token.length)}`); map.set('suggestion_token', null); map.update('suggestions', Immutable.List(), list => list.clear()); + map.set('focusDate', new Date()); }); }; +const insertEmoji = (state, position, emojiData) => { + const emoji = emojiData.shortname; + + return state.withMutations(map => { + map.update('text', oldText => `${oldText.slice(0, position)}${emoji} ${oldText.slice(position)}`); + map.set('focusDate', new Date()); + }); +}; + +const privacyPreference = (a, b) => { + if (a === 'direct' || b === 'direct') { + return 'direct'; + } else if (a === 'private' || b === 'private') { + return 'private'; + } else if (a === 'unlisted' || b === 'unlisted') { + return 'unlisted'; + } else { + return 'public'; + } +}; + export default function compose(state = initialState, action) { switch(action.type) { case STORE_HYDRATE: @@ -111,30 +133,38 @@ export default function compose(state = initialState, action) { case COMPOSE_UNMOUNT: return state.set('mounted', false); case COMPOSE_SENSITIVITY_CHANGE: - return state.set('sensitive', action.checked); + return state.set('sensitive', !state.get('sensitive')); case COMPOSE_SPOILERNESS_CHANGE: - return (action.checked ? state : state.set('spoiler_text', '')).set('spoiler', action.checked); + return state.withMutations(map => { + map.set('spoiler_text', ''); + map.set('spoiler', !state.get('spoiler')); + }); case COMPOSE_SPOILER_TEXT_CHANGE: return state.set('spoiler_text', action.text); case COMPOSE_VISIBILITY_CHANGE: - return state.set('private', action.checked); - case COMPOSE_LISTABILITY_CHANGE: - return state.set('unlisted', action.checked); + return state.set('privacy', action.value); case COMPOSE_CHANGE: return state.set('text', action.text); case COMPOSE_REPLY: return state.withMutations(map => { map.set('in_reply_to', action.status.get('id')); map.set('text', statusToTextMentions(state, action.status)); - map.set('unlisted', action.status.get('visibility') === 'unlisted'); - map.set('private', action.status.get('visibility') === 'private'); + map.set('privacy', privacyPreference(action.status.get('visibility'), state.get('default_privacy'))); + map.set('focusDate', new Date()); + map.set('preselectDate', new Date()); + + if (action.status.get('spoiler_text').length > 0) { + map.set('spoiler', true); + map.set('spoiler_text', action.status.get('spoiler_text')); + } }); case COMPOSE_REPLY_CANCEL: return state.withMutations(map => { map.set('in_reply_to', null); map.set('text', ''); - map.set('unlisted', state.get('default_privacy') === 'unlisted'); - map.set('private', state.get('default_privacy') === 'private'); + map.set('spoiler', false); + map.set('spoiler_text', ''); + map.set('privacy', state.get('default_privacy')); }); case COMPOSE_SUBMIT_REQUEST: return state.set('is_submitting', true); @@ -145,7 +175,6 @@ export default function compose(state = initialState, action) { case COMPOSE_UPLOAD_REQUEST: return state.withMutations(map => { map.set('is_uploading', true); - map.set('fileDropDate', new Date()); }); case COMPOSE_UPLOAD_SUCCESS: return appendMedia(state, Immutable.fromJS(action.media)); @@ -156,7 +185,7 @@ export default function compose(state = initialState, action) { case COMPOSE_UPLOAD_PROGRESS: return state.set('progress', Math.round((action.loaded / action.total) * 100)); case COMPOSE_MENTION: - return state.update('text', text => `${text}@${action.account.get('acct')} `); + return state.update('text', text => `${text}@${action.account.get('acct')} `).set('focusDate', new Date()); case COMPOSE_SUGGESTIONS_CLEAR: return state.update('suggestions', Immutable.List(), list => list.clear()).set('suggestion_token', null); case COMPOSE_SUGGESTIONS_READY: @@ -169,6 +198,8 @@ export default function compose(state = initialState, action) { } else { return state; } + case COMPOSE_EMOJI_INSERT: + return insertEmoji(state, action.position, action.emoji); default: return state; } diff --git a/app/assets/javascripts/components/reducers/modal.jsx b/app/assets/javascripts/components/reducers/modal.jsx index 07da65771..3566820ef 100644 --- a/app/assets/javascripts/components/reducers/modal.jsx +++ b/app/assets/javascripts/components/reducers/modal.jsx @@ -1,31 +1,17 @@ -import { - MEDIA_OPEN, - MODAL_CLOSE, - MODAL_INDEX_DECREASE, - MODAL_INDEX_INCREASE -} from '../actions/modal'; +import { MODAL_OPEN, MODAL_CLOSE } from '../actions/modal'; import Immutable from 'immutable'; -const initialState = Immutable.Map({ - media: null, - index: 0, - open: false -}); +const initialState = { + modalType: null, + modalProps: {} +}; export default function modal(state = initialState, action) { switch(action.type) { - case MEDIA_OPEN: - return state.withMutations(map => { - map.set('media', action.media); - map.set('index', action.index); - map.set('open', true); - }); + case MODAL_OPEN: + return { modalType: action.modalType, modalProps: action.modalProps }; case MODAL_CLOSE: - return state.set('open', false); - case MODAL_INDEX_DECREASE: - return state.update('index', index => Math.max(index - 1, 0)); - case MODAL_INDEX_INCREASE: - return state.update('index', index => Math.min(index + 1, state.get('media').size - 1)); + return initialState; default: return state; } diff --git a/app/assets/javascripts/components/reducers/notifications.jsx b/app/assets/javascripts/components/reducers/notifications.jsx index 4a7af8856..1406a388a 100644 --- a/app/assets/javascripts/components/reducers/notifications.jsx +++ b/app/assets/javascripts/components/reducers/notifications.jsx @@ -6,7 +6,8 @@ import { NOTIFICATIONS_EXPAND_REQUEST, NOTIFICATIONS_REFRESH_FAIL, NOTIFICATIONS_EXPAND_FAIL, - NOTIFICATIONS_CLEAR + NOTIFICATIONS_CLEAR, + NOTIFICATIONS_SCROLL_TOP } from '../actions/notifications'; import { ACCOUNT_BLOCK_SUCCESS } from '../actions/accounts'; import Immutable from 'immutable'; @@ -14,6 +15,8 @@ import Immutable from 'immutable'; const initialState = Immutable.Map({ items: Immutable.List(), next: null, + top: true, + unread: 0, loaded: false, isLoading: true }); @@ -26,6 +29,10 @@ const notificationToMap = notification => Immutable.Map({ }); const normalizeNotification = (state, notification) => { + if (!state.get('top')) { + state = state.update('unread', unread => unread + 1); + } + return state.update('items', list => list.unshift(notificationToMap(notification))); }; @@ -37,9 +44,12 @@ const normalizeNotifications = (state, notifications, next) => { items = items.set(i, notificationToMap(n)); }); + if (state.get('next') === null) { + state = state.set('next', next); + } + return state .update('items', list => loaded ? list.unshift(...items) : list.push(...items)) - .set('next', next) .set('loaded', true) .set('isLoading', false); }; @@ -61,6 +71,14 @@ const filterNotifications = (state, relationship) => { return state.update('items', list => list.filterNot(item => item.get('account') === relationship.id)); }; +const updateTop = (state, top) => { + if (top) { + state = state.set('unread', 0); + } + + return state.set('top', top); +}; + export default function notifications(state = initialState, action) { switch(action.type) { case NOTIFICATIONS_REFRESH_REQUEST: @@ -68,6 +86,8 @@ export default function notifications(state = initialState, action) { case NOTIFICATIONS_REFRESH_FAIL: case NOTIFICATIONS_EXPAND_FAIL: return state.set('isLoading', true); + case NOTIFICATIONS_SCROLL_TOP: + return updateTop(state, action.top); case NOTIFICATIONS_UPDATE: return normalizeNotification(state, action.notification); case NOTIFICATIONS_REFRESH_SUCCESS: diff --git a/app/assets/javascripts/components/reducers/relationships.jsx b/app/assets/javascripts/components/reducers/relationships.jsx index e4af1f028..c65c48b43 100644 --- a/app/assets/javascripts/components/reducers/relationships.jsx +++ b/app/assets/javascripts/components/reducers/relationships.jsx @@ -3,6 +3,8 @@ import { ACCOUNT_UNFOLLOW_SUCCESS, ACCOUNT_BLOCK_SUCCESS, ACCOUNT_UNBLOCK_SUCCESS, + ACCOUNT_MUTE_SUCCESS, + ACCOUNT_UNMUTE_SUCCESS, RELATIONSHIPS_FETCH_SUCCESS } from '../actions/accounts'; import Immutable from 'immutable'; @@ -21,14 +23,16 @@ const initialState = Immutable.Map(); export default function relationships(state = initialState, action) { switch(action.type) { - case ACCOUNT_FOLLOW_SUCCESS: - case ACCOUNT_UNFOLLOW_SUCCESS: - case ACCOUNT_BLOCK_SUCCESS: - case ACCOUNT_UNBLOCK_SUCCESS: - return normalizeRelationship(state, action.relationship); - case RELATIONSHIPS_FETCH_SUCCESS: - return normalizeRelationships(state, action.relationships); - default: - return state; + case ACCOUNT_FOLLOW_SUCCESS: + case ACCOUNT_UNFOLLOW_SUCCESS: + case ACCOUNT_BLOCK_SUCCESS: + case ACCOUNT_UNBLOCK_SUCCESS: + case ACCOUNT_MUTE_SUCCESS: + case ACCOUNT_UNMUTE_SUCCESS: + return normalizeRelationship(state, action.relationship); + case RELATIONSHIPS_FETCH_SUCCESS: + return normalizeRelationships(state, action.relationships); + default: + return state; } }; diff --git a/app/assets/javascripts/components/reducers/search.jsx b/app/assets/javascripts/components/reducers/search.jsx index d835ef268..b3fe6c7be 100644 --- a/app/assets/javascripts/components/reducers/search.jsx +++ b/app/assets/javascripts/components/reducers/search.jsx @@ -1,38 +1,64 @@ import { SEARCH_CHANGE, - SEARCH_SUGGESTIONS_READY, - SEARCH_RESET + SEARCH_CLEAR, + SEARCH_FETCH_SUCCESS, + SEARCH_SHOW } from '../actions/search'; +import { COMPOSE_MENTION, COMPOSE_REPLY } from '../actions/compose'; import Immutable from 'immutable'; const initialState = Immutable.Map({ value: '', - loaded_value: '', - suggestions: [] + submitted: false, + hidden: false, + results: Immutable.Map() }); -const normalizeSuggestions = (state, value, accounts) => { - let newSuggestions = [ - { +const normalizeSuggestions = (state, value, accounts, hashtags, statuses) => { + let newSuggestions = []; + + if (accounts.length > 0) { + newSuggestions.push({ title: 'account', items: accounts.map(item => ({ type: 'account', id: item.id, value: item.acct })) + }); + } + + if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 || hashtags.length > 0) { + let hashtagItems = hashtags.map(item => ({ + type: 'hashtag', + id: item, + value: `#${item}` + })); + + if (value.indexOf('@') === -1 && value.indexOf(' ') === -1 && !value.startsWith('http://') && !value.startsWith('https://') && hashtags.indexOf(value) === -1) { + hashtagItems.unshift({ + type: 'hashtag', + id: value, + value: `#${value}` + }); } - ]; - if (value.indexOf('@') === -1 && value.indexOf(' ') === -1) { + if (hashtagItems.length > 0) { + newSuggestions.push({ + title: 'hashtag', + items: hashtagItems + }); + } + } + + if (statuses.length > 0) { newSuggestions.push({ - title: 'hashtag', - items: [ - { - type: 'hashtag', - id: value, - value: `#${value}` - } - ] + title: 'status', + items: statuses.map(item => ({ + type: 'status', + id: item.id, + value: item.id + })) }); } @@ -44,17 +70,27 @@ const normalizeSuggestions = (state, value, accounts) => { export default function search(state = initialState, action) { switch(action.type) { - case SEARCH_CHANGE: - return state.set('value', action.value); - case SEARCH_SUGGESTIONS_READY: - return normalizeSuggestions(state, action.value, action.accounts); - case SEARCH_RESET: - return state.withMutations(map => { - map.set('suggestions', []); - map.set('value', ''); - map.set('loaded_value', ''); - }); - default: - return state; + case SEARCH_CHANGE: + return state.set('value', action.value); + case SEARCH_CLEAR: + return state.withMutations(map => { + map.set('value', ''); + map.set('results', Immutable.Map()); + map.set('submitted', false); + map.set('hidden', false); + }); + case SEARCH_SHOW: + return state.set('hidden', false); + case COMPOSE_REPLY: + case COMPOSE_MENTION: + return state.set('hidden', true); + case SEARCH_FETCH_SUCCESS: + return state.set('results', Immutable.Map({ + accounts: Immutable.List(action.results.accounts.map(item => item.id)), + statuses: Immutable.List(action.results.statuses.map(item => item.id)), + hashtags: Immutable.List(action.results.hashtags) + })).set('submitted', true); + default: + return state; } }; diff --git a/app/assets/javascripts/components/reducers/statuses.jsx b/app/assets/javascripts/components/reducers/statuses.jsx index 6323e0fbe..ca8fa7a01 100644 --- a/app/assets/javascripts/components/reducers/statuses.jsx +++ b/app/assets/javascripts/components/reducers/statuses.jsx @@ -32,6 +32,7 @@ import { FAVOURITED_STATUSES_FETCH_SUCCESS, FAVOURITED_STATUSES_EXPAND_SUCCESS } from '../actions/favourites'; +import { SEARCH_FETCH_SUCCESS } from '../actions/search'; import Immutable from 'immutable'; const normalizeStatus = (state, status) => { @@ -39,14 +40,15 @@ const normalizeStatus = (state, status) => { return state; } - status.account = status.account.id; + const normalStatus = { ...status }; + normalStatus.account = status.account.id; if (status.reblog && status.reblog.id) { - state = normalizeStatus(state, status.reblog); - status.reblog = status.reblog.id; + state = normalizeStatus(state, status.reblog); + normalStatus.reblog = status.reblog.id; } - return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(status))); + return state.update(status.id, Immutable.Map(), map => map.mergeDeep(Immutable.fromJS(normalStatus))); }; const normalizeStatuses = (state, statuses) => { @@ -107,6 +109,7 @@ export default function statuses(state = initialState, action) { case NOTIFICATIONS_EXPAND_SUCCESS: case FAVOURITED_STATUSES_FETCH_SUCCESS: case FAVOURITED_STATUSES_EXPAND_SUCCESS: + case SEARCH_FETCH_SUCCESS: return normalizeStatuses(state, action.statuses); case TIMELINE_DELETE: return deleteStatus(state, action.id, action.references); diff --git a/app/assets/javascripts/components/reducers/timelines.jsx b/app/assets/javascripts/components/reducers/timelines.jsx index 6f2d26dcb..675a52759 100644 --- a/app/assets/javascripts/components/reducers/timelines.jsx +++ b/app/assets/javascripts/components/reducers/timelines.jsx @@ -7,7 +7,9 @@ import { TIMELINE_EXPAND_SUCCESS, TIMELINE_EXPAND_REQUEST, TIMELINE_EXPAND_FAIL, - TIMELINE_SCROLL_TOP + TIMELINE_SCROLL_TOP, + TIMELINE_CONNECT, + TIMELINE_DISCONNECT } from '../actions/timelines'; import { REBLOG_SUCCESS, @@ -22,7 +24,8 @@ import { ACCOUNT_TIMELINE_EXPAND_REQUEST, ACCOUNT_TIMELINE_EXPAND_SUCCESS, ACCOUNT_TIMELINE_EXPAND_FAIL, - ACCOUNT_BLOCK_SUCCESS + ACCOUNT_BLOCK_SUCCESS, + ACCOUNT_MUTE_SUCCESS } from '../actions/accounts'; import { CONTEXT_FETCH_SUCCESS @@ -31,31 +34,47 @@ import Immutable from 'immutable'; const initialState = Immutable.Map({ home: Immutable.Map({ + path: () => '/api/v1/timelines/home', + next: null, isLoading: false, + online: false, loaded: false, top: true, + unread: 0, items: Immutable.List() }), - mentions: Immutable.Map({ + public: Immutable.Map({ + path: () => '/api/v1/timelines/public', + next: null, isLoading: false, + online: false, loaded: false, top: true, + unread: 0, items: Immutable.List() }), - public: Immutable.Map({ + community: Immutable.Map({ + path: () => '/api/v1/timelines/public', + next: null, + params: { local: true }, isLoading: false, + online: false, loaded: false, top: true, + unread: 0, items: Immutable.List() }), tag: Immutable.Map({ + path: (id) => `/api/v1/timelines/tag/${id}`, + next: null, isLoading: false, id: null, loaded: false, top: true, + unread: 0, items: Immutable.List() }), @@ -81,7 +100,7 @@ const normalizeStatus = (state, status) => { return state; }; -const normalizeTimeline = (state, timeline, statuses, replace = false) => { +const normalizeTimeline = (state, timeline, statuses, next) => { let ids = Immutable.List(); const loaded = state.getIn([timeline, 'loaded']); @@ -93,10 +112,14 @@ const normalizeTimeline = (state, timeline, statuses, replace = false) => { state = state.setIn([timeline, 'loaded'], true); state = state.setIn([timeline, 'isLoading'], false); + if (state.getIn([timeline, 'next']) === null) { + state = state.setIn([timeline, 'next'], next); + } + return state.updateIn([timeline, 'items'], Immutable.List(), list => (loaded ? list.unshift(...ids) : ids)); }; -const appendNormalizedTimeline = (state, timeline, statuses) => { +const appendNormalizedTimeline = (state, timeline, statuses, next) => { let moreIds = Immutable.List(); statuses.forEach((status, i) => { @@ -105,6 +128,7 @@ const appendNormalizedTimeline = (state, timeline, statuses) => { }); state = state.setIn([timeline, 'isLoading'], false); + state = state.setIn([timeline, 'next'], next); return state.updateIn([timeline, 'items'], Immutable.List(), list => list.push(...moreIds)); }; @@ -141,6 +165,10 @@ const updateTimeline = (state, timeline, status, references) => { state = normalizeStatus(state, status); + if (!top) { + state = state.updateIn([timeline, 'unread'], unread => unread + 1); + } + state = state.updateIn([timeline, 'items'], Immutable.List(), list => { if (top && list.size > 40) { list = list.take(20); @@ -169,7 +197,7 @@ const deleteStatus = (state, id, accountId, references, reblogOf) => { } // Remove references from timelines - ['home', 'mentions', 'public', 'tag'].forEach(function (timeline) { + ['home', 'public', 'community', 'tag'].forEach(function (timeline) { state = state.updateIn([timeline, 'items'], list => list.filterNot(item => item === id)); }); @@ -221,11 +249,13 @@ const normalizeContext = (state, id, ancestors, descendants) => { }; const resetTimeline = (state, timeline, id) => { - if (timeline === 'tag' && state.getIn([timeline, 'id']) !== id) { + if (timeline === 'tag' && typeof id !== 'undefined' && state.getIn([timeline, 'id']) !== id) { state = state.update(timeline, map => map .set('id', id) .set('isLoading', true) .set('loaded', false) + .set('next', null) + .set('top', true) .update('items', list => list.clear())); } else { state = state.setIn([timeline, 'isLoading'], true); @@ -234,6 +264,14 @@ const resetTimeline = (state, timeline, id) => { return state; }; +const updateTop = (state, timeline, top) => { + if (top) { + state = state.setIn([timeline, 'unread'], 0); + } + + return state.setIn([timeline, 'top'], top); +}; + export default function timelines(state = initialState, action) { switch(action.type) { case TIMELINE_REFRESH_REQUEST: @@ -243,9 +281,9 @@ export default function timelines(state = initialState, action) { case TIMELINE_EXPAND_FAIL: return state.setIn([action.timeline, 'isLoading'], false); case TIMELINE_REFRESH_SUCCESS: - return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses)); + return normalizeTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next); case TIMELINE_EXPAND_SUCCESS: - return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses)); + return appendNormalizedTimeline(state, action.timeline, Immutable.fromJS(action.statuses), action.next); case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, Immutable.fromJS(action.status), action.references); case TIMELINE_DELETE: @@ -263,9 +301,14 @@ export default function timelines(state = initialState, action) { case ACCOUNT_TIMELINE_EXPAND_SUCCESS: return appendNormalizedAccountTimeline(state, action.id, Immutable.fromJS(action.statuses)); case ACCOUNT_BLOCK_SUCCESS: + case ACCOUNT_MUTE_SUCCESS: return filterTimelines(state, action.relationship, action.statuses); case TIMELINE_SCROLL_TOP: - return state.setIn([action.timeline, 'top'], action.top); + return updateTop(state, action.timeline, action.top); + case TIMELINE_CONNECT: + return state.setIn([action.timeline, 'online'], true); + case TIMELINE_DISCONNECT: + return state.setIn([action.timeline, 'online'], false); default: return state; } |