diff options
Diffstat (limited to 'app/javascript/flavours/glitch/reducers')
6 files changed, 101 insertions, 81 deletions
diff --git a/app/javascript/flavours/glitch/reducers/accounts.js b/app/javascript/flavours/glitch/reducers/accounts.js index 23fbd999c..86f4970c9 100644 --- a/app/javascript/flavours/glitch/reducers/accounts.js +++ b/app/javascript/flavours/glitch/reducers/accounts.js @@ -57,6 +57,12 @@ import { STORE_HYDRATE } from 'flavours/glitch/actions/store'; import emojify from 'flavours/glitch/util/emoji'; import { Map as ImmutableMap, fromJS } from 'immutable'; import escapeTextContentForBrowser from 'escape-html'; +import { unescapeHTML } from 'flavours/glitch/util/html'; + +const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => { + obj[`:${emoji.shortcode}:`] = emoji; + return obj; +}, {}); const normalizeAccount = (state, account) => { account = { ...account }; @@ -65,15 +71,17 @@ const normalizeAccount = (state, account) => { delete account.following_count; delete account.statuses_count; + const emojiMap = makeEmojiMap(account); const displayName = account.display_name.length === 0 ? account.username : account.display_name; - account.display_name_html = emojify(escapeTextContentForBrowser(displayName)); - account.note_emojified = emojify(account.note); + account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap); + account.note_emojified = emojify(account.note, emojiMap); if (account.fields) { account.fields = account.fields.map(pair => ({ ...pair, name_emojified: emojify(escapeTextContentForBrowser(pair.name)), - value_emojified: emojify(pair.value), + value_emojified: emojify(pair.value, emojiMap), + value_plain: unescapeHTML(pair.value), })); } diff --git a/app/javascript/flavours/glitch/reducers/compose.js b/app/javascript/flavours/glitch/reducers/compose.js index 8973c7713..24a8af86f 100644 --- a/app/javascript/flavours/glitch/reducers/compose.js +++ b/app/javascript/flavours/glitch/reducers/compose.js @@ -5,6 +5,7 @@ import { COMPOSE_CYCLE_ELEFRIEND, COMPOSE_REPLY, COMPOSE_REPLY_CANCEL, + COMPOSE_DIRECT, COMPOSE_MENTION, COMPOSE_SUBMIT_REQUEST, COMPOSE_SUBMIT_SUCCESS, @@ -55,6 +56,7 @@ const initialState = ImmutableMap({ privacy: null, text: '', focusDate: null, + caretPosition: null, preselectDate: null, in_reply_to: null, is_submitting: false, @@ -147,6 +149,7 @@ function continueThread (state, status) { map.update('media_attachments', list => list.clear()); map.set('idempotencyKey', uuid()); map.set('focusDate', new Date()); + map.set('caretPosition', null); map.set('preselectDate', new Date()); }); } @@ -158,7 +161,6 @@ function appendMedia(state, media) { map.update('media_attachments', list => list.push(media)); map.set('is_uploading', false); map.set('resetFileKey', Math.floor((Math.random() * 0x10000))); - map.set('focusDate', new Date()); map.set('idempotencyKey', uuid()); if (prevSize === 0 && (state.get('default_sensitive') || state.get('spoiler'))) { @@ -186,6 +188,7 @@ const insertSuggestion = (state, position, token, completion) => { map.set('suggestion_token', null); map.update('suggestions', ImmutableList(), list => list.clear()); map.set('focusDate', new Date()); + map.set('caretPosition', position + completion.length + 1); map.set('idempotencyKey', uuid()); }); }; @@ -196,6 +199,7 @@ const insertEmoji = (state, position, emojiData) => { return state.withMutations(map => { map.update('text', oldText => `${oldText.slice(0, position)}${emoji}\u200B${oldText.slice(position)}`); map.set('focusDate', new Date()); + map.set('caretPosition', position + emoji.length + 1); map.set('idempotencyKey', uuid()); }); }; @@ -277,6 +281,7 @@ export default function compose(state = initialState, action) { map => map.merge(new ImmutableMap({ do_not_federate: /👁\ufe0f?\u200b?(?:<\/p>)?$/.test(action.status.get('content')) })) ); map.set('focusDate', new Date()); + map.set('caretPosition', null); map.set('preselectDate', new Date()); map.set('idempotencyKey', uuid()); @@ -321,10 +326,20 @@ 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')} `) - .set('focusDate', new Date()) - .set('idempotencyKey', uuid()); + return state.withMutations(map => { + map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); + map.set('focusDate', new Date()); + map.set('caretPosition', null); + map.set('idempotencyKey', uuid()); + }); + case COMPOSE_DIRECT: + return state.withMutations(map => { + map.update('text', text => [text.trim(), `@${action.account.get('acct')} `].filter((str) => str.length !== 0).join(' ')); + map.set('privacy', 'direct'); + map.set('focusDate', new Date()); + map.set('caretPosition', null); + map.set('idempotencyKey', uuid()); + }); case COMPOSE_SUGGESTIONS_CLEAR: return state.update('suggestions', ImmutableList(), list => list.clear()).set('suggestion_token', null); case COMPOSE_SUGGESTIONS_READY: diff --git a/app/javascript/flavours/glitch/reducers/domain_lists.js b/app/javascript/flavours/glitch/reducers/domain_lists.js index a9e3519f3..eff97fbd6 100644 --- a/app/javascript/flavours/glitch/reducers/domain_lists.js +++ b/app/javascript/flavours/glitch/reducers/domain_lists.js @@ -6,7 +6,9 @@ import { import { Map as ImmutableMap, OrderedSet as ImmutableOrderedSet } from 'immutable'; const initialState = ImmutableMap({ - blocks: ImmutableMap(), + blocks: ImmutableMap({ + items: ImmutableOrderedSet(), + }), }); export default function domainLists(state = initialState, action) { diff --git a/app/javascript/flavours/glitch/reducers/notifications.js b/app/javascript/flavours/glitch/reducers/notifications.js index fb2b3f549..dc820b476 100644 --- a/app/javascript/flavours/glitch/reducers/notifications.js +++ b/app/javascript/flavours/glitch/reducers/notifications.js @@ -1,10 +1,7 @@ import { NOTIFICATIONS_UPDATE, - NOTIFICATIONS_REFRESH_SUCCESS, NOTIFICATIONS_EXPAND_SUCCESS, - NOTIFICATIONS_REFRESH_REQUEST, NOTIFICATIONS_EXPAND_REQUEST, - NOTIFICATIONS_REFRESH_FAIL, NOTIFICATIONS_EXPAND_FAIL, NOTIFICATIONS_CLEAR, NOTIFICATIONS_SCROLL_TOP, @@ -19,16 +16,16 @@ import { ACCOUNT_BLOCK_SUCCESS, ACCOUNT_MUTE_SUCCESS, } from 'flavours/glitch/actions/accounts'; -import { TIMELINE_DELETE } from 'flavours/glitch/actions/timelines'; +import { TIMELINE_DELETE, TIMELINE_DISCONNECT } from 'flavours/glitch/actions/timelines'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; +import compareId from 'flavours/glitch/util/compare_id'; const initialState = ImmutableMap({ items: ImmutableList(), - next: null, + hasMore: true, top: true, unread: 0, - loaded: false, - isLoading: true, + isLoading: false, cleaningMode: false, // notification removal mark of new notifs loaded whilst cleaningMode is true. markNewForDelete: false, @@ -58,39 +55,38 @@ const normalizeNotification = (state, notification) => { }); }; -const normalizeNotifications = (state, notifications, next) => { - let items = ImmutableList(); - const loaded = state.get('loaded'); +const expandNormalizedNotifications = (state, notifications, next) => { + let items = ImmutableList(); notifications.forEach((n, i) => { items = items.set(i, notificationToMap(state, n)); }); - if (state.get('next') === null) { - state = state.set('next', next); - } + return state.withMutations(mutable => { + if (!items.isEmpty()) { + mutable.update('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')) + ); - return state - .update('items', list => loaded ? items.concat(list) : list.concat(items)) - .set('loaded', true) - .set('isLoading', false); -}; + const firstIndex = 1 + list.take(lastIndex).findLastIndex( + item => item !== null && compareId(item.get('id'), items.first().get('id')) > 0 + ); -const appendNormalizedNotifications = (state, notifications, next) => { - let items = ImmutableList(); + return list.take(firstIndex).concat(items, list.skip(lastIndex)); + }); + } - notifications.forEach((n, i) => { - items = items.set(i, notificationToMap(state, n)); - }); + if (!next) { + mutable.set('hasMore', true); + } - return state - .update('items', list => list.concat(items)) - .set('next', next) - .set('isLoading', false); + mutable.set('isLoading', false); + }); }; const filterNotifications = (state, relationship) => { - return state.update('items', list => list.filterNot(item => item.get('account') === relationship.id)); + return state.update('items', list => list.filterNot(item => item !== null && item.get('account') === relationship.id)); }; const updateTop = (state, top) => { @@ -102,7 +98,7 @@ const updateTop = (state, top) => { }; const deleteByStatus = (state, statusId) => { - return state.update('items', list => list.filterNot(item => item.get('status') === statusId)); + return state.update('items', list => list.filterNot(item => item !== null && item.get('status') === statusId)); }; const markForDelete = (state, notificationId, yes) => { @@ -137,29 +133,29 @@ export default function notifications(state = initialState, action) { let st; switch(action.type) { - case NOTIFICATIONS_REFRESH_REQUEST: case NOTIFICATIONS_EXPAND_REQUEST: case NOTIFICATIONS_DELETE_MARKED_REQUEST: return state.set('isLoading', true); case NOTIFICATIONS_DELETE_MARKED_FAIL: - case NOTIFICATIONS_REFRESH_FAIL: case NOTIFICATIONS_EXPAND_FAIL: return state.set('isLoading', false); case NOTIFICATIONS_SCROLL_TOP: return updateTop(state, action.top); case NOTIFICATIONS_UPDATE: return normalizeNotification(state, action.notification); - case NOTIFICATIONS_REFRESH_SUCCESS: - return normalizeNotifications(state, action.notifications, action.next); case NOTIFICATIONS_EXPAND_SUCCESS: - return appendNormalizedNotifications(state, action.notifications, action.next); + return expandNormalizedNotifications(state, action.notifications, action.next); case ACCOUNT_BLOCK_SUCCESS: case ACCOUNT_MUTE_SUCCESS: return filterNotifications(state, action.relationship); case NOTIFICATIONS_CLEAR: - return state.set('items', ImmutableList()).set('next', null); + return state.set('items', ImmutableList()).set('hasMore', false); case TIMELINE_DELETE: return deleteByStatus(state, action.id); + case TIMELINE_DISCONNECT: + return action.timeline === 'home' ? + state.update('items', items => items.first() ? items.unshift(null) : items) : + state; case NOTIFICATION_MARK_FOR_DELETE: return markForDelete(state, action.id, action.yes); diff --git a/app/javascript/flavours/glitch/reducers/search.js b/app/javascript/flavours/glitch/reducers/search.js index f9bf92098..dc6be97e2 100644 --- a/app/javascript/flavours/glitch/reducers/search.js +++ b/app/javascript/flavours/glitch/reducers/search.js @@ -4,7 +4,11 @@ import { SEARCH_FETCH_SUCCESS, SEARCH_SHOW, } from 'flavours/glitch/actions/search'; -import { COMPOSE_MENTION, COMPOSE_REPLY } from 'flavours/glitch/actions/compose'; +import { + COMPOSE_MENTION, + COMPOSE_REPLY, + COMPOSE_DIRECT, +} from 'flavours/glitch/actions/compose'; import { Map as ImmutableMap, List as ImmutableList } from 'immutable'; const initialState = ImmutableMap({ @@ -29,6 +33,7 @@ export default function search(state = initialState, action) { return state.set('hidden', false); case COMPOSE_REPLY: case COMPOSE_MENTION: + case COMPOSE_DIRECT: return state.set('hidden', true); case SEARCH_FETCH_SUCCESS: return state.set('results', ImmutableMap({ diff --git a/app/javascript/flavours/glitch/reducers/timelines.js b/app/javascript/flavours/glitch/reducers/timelines.js index c4ae2bc97..19e400b19 100644 --- a/app/javascript/flavours/glitch/reducers/timelines.js +++ b/app/javascript/flavours/glitch/reducers/timelines.js @@ -1,14 +1,10 @@ import { - TIMELINE_REFRESH_REQUEST, - TIMELINE_REFRESH_SUCCESS, - TIMELINE_REFRESH_FAIL, TIMELINE_UPDATE, TIMELINE_DELETE, TIMELINE_EXPAND_SUCCESS, TIMELINE_EXPAND_REQUEST, TIMELINE_EXPAND_FAIL, TIMELINE_SCROLL_TOP, - TIMELINE_CONNECT, TIMELINE_DISCONNECT, } from 'flavours/glitch/actions/timelines'; import { @@ -17,42 +13,39 @@ import { ACCOUNT_UNFOLLOW_SUCCESS, } from 'flavours/glitch/actions/accounts'; import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'; +import compareId from 'flavours/glitch/util/compare_id'; const initialState = ImmutableMap(); const initialTimeline = ImmutableMap({ unread: 0, - online: false, top: true, - loaded: false, isLoading: false, - next: false, + hasMore: true, items: ImmutableList(), }); -const normalizeTimeline = (state, timeline, statuses, next, isPartial) => { - const oldIds = state.getIn([timeline, 'items'], ImmutableList()); - const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); - const wasLoaded = state.getIn([timeline, 'loaded']); - const hadNext = state.getIn([timeline, 'next']); - - return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { - mMap.set('loaded', true); - mMap.set('isLoading', false); - if (!hadNext) mMap.set('next', next); - mMap.set('items', wasLoaded ? ids.concat(oldIds) : oldIds.concat(ids)); - mMap.set('isPartial', isPartial); - })); -}; - -const appendNormalizedTimeline = (state, timeline, statuses, next) => { - const oldIds = state.getIn([timeline, 'items'], ImmutableList()); - const ids = ImmutableList(statuses.map(status => status.get('id'))).filter(newId => !oldIds.includes(newId)); - +const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial) => { return state.update(timeline, initialTimeline, map => map.withMutations(mMap => { mMap.set('isLoading', false); - mMap.set('next', next); - mMap.set('items', oldIds.concat(ids)); + if (!next) mMap.set('hasMore', false); + + if (!statuses.isEmpty()) { + mMap.update('items', ImmutableList(), oldIds => { + const newIds = statuses.map(status => status.get('id')); + const lastIndex = oldIds.findLastIndex(id => id !== null && compareId(id, newIds.last()) >= 0) + 1; + const firstIndex = oldIds.take(lastIndex).findLastIndex(id => id !== null && compareId(id, newIds.first()) > 0); + + if (firstIndex < 0) { + return (isPartial ? newIds.unshift(null) : newIds).concat(oldIds.skip(lastIndex)); + } + + return oldIds.take(firstIndex + 1).concat( + isPartial && oldIds.get(firstIndex) !== null ? newIds.unshift(null) : newIds, + oldIds.skip(lastIndex) + ); + }); + } })); }; @@ -119,16 +112,12 @@ const updateTop = (state, timeline, top) => { export default function timelines(state = initialState, action) { switch(action.type) { - case TIMELINE_REFRESH_REQUEST: case TIMELINE_EXPAND_REQUEST: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', true)); - case TIMELINE_REFRESH_FAIL: case TIMELINE_EXPAND_FAIL: return state.update(action.timeline, initialTimeline, map => map.set('isLoading', false)); - case TIMELINE_REFRESH_SUCCESS: - return normalizeTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial); case TIMELINE_EXPAND_SUCCESS: - return appendNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next); + return expandNormalizedTimeline(state, action.timeline, fromJS(action.statuses), action.next, action.partial); case TIMELINE_UPDATE: return updateTimeline(state, action.timeline, fromJS(action.status), action.references); case TIMELINE_DELETE: @@ -140,10 +129,15 @@ export default function timelines(state = initialState, action) { return filterTimeline('home', state, action.relationship, action.statuses); case TIMELINE_SCROLL_TOP: return updateTop(state, action.timeline, action.top); - case TIMELINE_CONNECT: - return state.update(action.timeline, initialTimeline, map => map.set('online', true)); case TIMELINE_DISCONNECT: - return state.update(action.timeline, initialTimeline, map => map.set('online', false)); + return state.update( + action.timeline, + initialTimeline, + map => map.update( + 'items', + items => items.first() ? items.unshift(null) : items + ) + ); default: return state; } |