Commiting

This commit is contained in:
mgabdev
2020-11-25 15:22:37 -06:00
parent fb612f60c8
commit b4e370d3d3
136 changed files with 4171 additions and 3231 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -33,20 +33,17 @@ export const fetchBookmarkedStatuses = () => (dispatch, getState) => {
const fetchBookmarkedStatusesRequest = () => ({
type: BOOKMARKED_STATUSES_FETCH_REQUEST,
skipLoading: true,
})
const fetchBookmarkedStatusesSuccess = (statuses, next) => ({
type: BOOKMARKED_STATUSES_FETCH_SUCCESS,
statuses,
next,
skipLoading: true,
})
const fetchBookmarkedStatusesFail = (error) => ({
type: BOOKMARKED_STATUSES_FETCH_FAIL,
error,
skipLoading: true,
})
/**

View File

@@ -0,0 +1,28 @@
import api from '../api'
import { me } from '../initial_state'
export const MESSAGE_INPUT_CHANGE = 'MESSAGE_INPUT_CHANGE'
export const MESSAGE_INPUT_RESET = 'MESSAGE_INPUT_RESET'
/**
*
*/
export const messageInputChange = (text) => (dispatch, getState) => {
if (!me) return
//Ensure has conversation
const conversationId = getState().getIn(['chat_conversations', 'current', 'conversation_id'], null)
if (!conversationId) return
dispatch({
type: MESSAGE_INPUT_CHANGE,
text,
})
}
/**
*
*/
export const messageInputReset = (dispatch) => {
dispatch({ type: MESSAGE_INPUT_RESET })
}

View File

@@ -0,0 +1,189 @@
import api, { getLinks } from '../api'
import { fetchRelationships } from './accounts'
import { importFetchedAccounts } from './importer'
import { me } from '../initial_state'
export const CONVERSATION_BLOCKS_FETCH_REQUEST = 'CONVERSATION_BLOCKS_FETCH_REQUEST'
export const CONVERSATION_BLOCKS_FETCH_SUCCESS = 'CONVERSATION_BLOCKS_FETCH_SUCCESS'
export const CONVERSATION_BLOCKS_FETCH_FAIL = 'CONVERSATION_BLOCKS_FETCH_FAIL'
export const CONVERSATION_BLOCKS_EXPAND_REQUEST = 'CONVERSATION_BLOCKS_EXPAND_REQUEST'
export const CONVERSATION_BLOCKS_EXPAND_SUCCESS = 'CONVERSATION_BLOCKS_EXPAND_SUCCESS'
export const CONVERSATION_BLOCKS_EXPAND_FAIL = 'CONVERSATION_BLOCKS_EXPAND_FAIL'
export const BLOCK_MESSAGER_REQUEST = 'BLOCK_MESSAGER_REQUEST'
export const BLOCK_MESSAGER_SUCCESS = 'BLOCK_MESSAGER_SUCCESS'
export const BLOCK_MESSAGER_FAIL = 'BLOCK_MESSAGER_FAIL'
export const UNBLOCK_MESSAGER_REQUEST = 'UNBLOCK_MESSAGER_REQUEST'
export const UNBLOCK_MESSAGER_SUCCESS = 'UNBLOCK_MESSAGER_SUCCESS'
export const UNBLOCK_MESSAGER_FAIL = 'UNBLOCK_MESSAGER_FAIL'
//
export const CONVERSATION_MUTES_FETCH_REQUEST = 'CONVERSATION_MUTES_FETCH_REQUEST'
export const CONVERSATION_MUTES_FETCH_SUCCESS = 'CONVERSATION_MUTES_FETCH_SUCCESS'
export const CONVERSATION_MUTES_FETCH_FAIL = 'CONVERSATION_MUTES_FETCH_FAIL'
export const CONVERSATION_MUTES_EXPAND_REQUEST = 'CONVERSATION_MUTES_EXPAND_REQUEST'
export const CONVERSATION_MUTES_EXPAND_SUCCESS = 'CONVERSATION_MUTES_EXPAND_SUCCESS'
export const CONVERSATION_MUTES_EXPAND_FAIL = 'CONVERSATION_MUTES_EXPAND_FAIL'
export const MUTE_MESSAGER_REQUEST = 'BLOCK_MESSAGER_REQUEST'
export const MUTE_MESSAGER_SUCCESS = 'BLOCK_MESSAGER_SUCCESS'
export const MUTE_MESSAGER_FAIL = 'BLOCK_MESSAGER_FAIL'
export const UNMUTE_MESSAGER_REQUEST = 'UNMUTE_MESSAGER_REQUEST'
export const UNMUTE_MESSAGER_SUCCESS = 'UNMUTE_MESSAGER_SUCCESS'
export const UNMUTE_MESSAGER_FAIL = 'UNMUTE_MESSAGER_FAIL'
//
export const CONVERSATION_REQUEST_APPROVE_SUCCESS = 'CONVERSATION_REQUEST_APPROVE_SUCCESS'
export const CONVERSATION_REQUEST_APPROVE_FAIL = 'CONVERSATION_REQUEST_APPROVE_FAIL'
export const CONVERSATION_REQUEST_REJECT_SUCCESS = 'CONVERSATION_REQUEST_REJECT_SUCCESS'
export const CONVERSATION_REQUEST_REJECT_FAIL = 'CONVERSATION_REQUEST_REJECT_FAIL'
export const CONVERSATION_DELETE_REQUEST = 'CONVERSATION_DELETE_REQUEST'
export const CONVERSATION_DELETE_SUCCESS = 'CONVERSATION_DELETE_SUCCESS'
export const CONVERSATION_DELETE_FAIL = 'CONVERSATION_DELETE_FAIL'
//
export const CONVERSATIONS_FETCH_REQUEST = 'CONVERSATIONS_FETCH_REQUEST'
export const CONVERSATIONS_FETCH_SUCCESS = 'CONVERSATIONS_FETCH_SUCCESS'
export const CONVERSATIONS_FETCH_FAIL = 'CONVERSATIONS_FETCH_FAIL'
export const CONVERSATIONS_EXPAND_REQUEST = 'CONVERSATIONS_EXPAND_REQUEST'
export const CONVERSATIONS_EXPAND_SUCCESS = 'CONVERSATIONS_EXPAND_SUCCESS'
export const CONVERSATIONS_EXPAND_FAIL = 'CONVERSATIONS_EXPAND_FAIL'
/**
*
*/
export const blockMessenger = (accountId) => (dispatch, getState) => {
if (!accountId) return
dispatch(blockMessengerRequest(accountId))
api(getState).post(`/api/v1/messages/accounts/${accountId}/block`).then((response) => {
dispatch(blockMessengerSuccess(response.data))
}).catch((error) => {
dispatch(blockMessengerFail(accountId, error))
})
}
const blockMessengerRequest = (accountId) => ({
type: BLOCK_MESSAGER_REQUEST,
accountId,
})
const blockMessengerSuccess = (data) => ({
type: BLOCK_MESSAGER_REQUEST,
data,
})
const blockMessengerFail = (accountId, error) => ({
type: BLOCK_MESSAGER_REQUEST,
accountId,
error,
})
/**
*
*/
export const unblockMessenger = (accountId) => (dispatch, getState) => {
if (!accountId) return
dispatch(unblockMessengerRequest(accountId))
api(getState).post(`/api/v1/messages/accounts/${accountId}/unblock`).then((response) => {
dispatch(unblockMessengerSuccess(response.data))
}).catch((error) => {
dispatch(unblockMessengerFail(accountId, error))
})
}
const unblockMessengerRequest = (accountId) => ({
type: UNBLOCK_MESSAGER_REQUEST,
accountId,
})
const blockMessengerSuccess = (data) => ({
type: UNBLOCK_MESSAGER_REQUEST,
data,
})
const blockMessengerFail = (accountId, error) => ({
type: UNBLOCK_MESSAGER_REQUEST,
accountId,
error,
})
/**
*
*/
export const fetchBlocks = () => (dispatch, getState) => {
if (!me) return
dispatch(fetchBlocksRequest())
api(getState).get('/api/v1/blocks').then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(fetchBlocksSuccess(response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch(error => dispatch(fetchBlocksFail(error)))
}
export const fetchBlocksRequest = () => ({
type: BLOCKS_FETCH_REQUEST,
})
export const fetchBlocksSuccess = (accounts, next) => ({
type: BLOCKS_FETCH_SUCCESS,
accounts,
next,
})
export const fetchBlocksFail = (error) => ({
type: BLOCKS_FETCH_FAIL,
error,
})
/**
*
*/
export const expandBlocks = () => (dispatch, getState) => {
if (!me) return
const url = getState().getIn(['user_lists', 'blocks', me, 'next'])
const isLoading = getState().getIn(['user_lists', 'blocks', me, 'isLoading'])
if (url === null || isLoading) return
dispatch(expandBlocksRequest())
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(expandBlocksSuccess(response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch(error => dispatch(expandBlocksFail(error)))
}
export const expandBlocksRequest = () => ({
type: BLOCKS_EXPAND_REQUEST,
})
export const expandBlocksSuccess = (accounts, next) => ({
type: BLOCKS_EXPAND_SUCCESS,
accounts,
next,
})
export const expandBlocksFail = (error) => ({
type: BLOCKS_EXPAND_FAIL,
error,
})

View File

@@ -0,0 +1,86 @@
import api from '../api'
import { me } from '../initial_state'
export const MESSAGE_SEND_REQUEST = 'MESSAGE_SEND_REQUEST'
export const MESSAGE_SEND_SUCCESS = 'MESSAGE_SEND_SUCCESS'
export const MESSAGE_SEND_FAIL = 'MESSAGE_SEND_FAIL'
export const MESSAGE_DELETE_REQUEST = 'MESSAGE_DELETE_REQUEST'
export const MESSAGE_DELETE_SUCCESS = 'MESSAGE_DELETE_SUCCESS'
export const MESSAGE_DELETE_FAIL = 'MESSAGE_DELETE_FAIL'
/**
*
*/
const sendMessage = (text, conversationId) => (dispatch, getState) => {
if (!me) return
// : todo :
// let text = getState().getIn(['chat_messages', 'text'], '')
// let conversationId = getState().getIn(['chat_messags', 'conversation_id'], '')
dispatch(sendMessageRequest())
api(getState).put('/api/v1/messages/chat', {
text,
conversationId,
}, {
headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
},
}).then((response) => {
sendMessageSuccess(response)
}).catch((error) => {
dispatch(sendMessageFail(error))
})
}
const sendMessageRequest = (text, conversationId) => ({
type: MESSAGE_SEND_REQUEST,
text,
conversationId,
})
const sendMessageSuccess = () => ({
type: MESSAGE_SEND_SUCCESS,
})
const sendMessageFail = (error) => ({
type: MESSAGE_SEND_FAIL,
error,
})
/**
*
*/
const deleteMessage = (messageId) => (dispatch, getState) => {
if (!me || !messageId) return
// : todo :
dispatch(sendMessageRequest())
api(getState).delete(`/api/v1/messages/chat/${messageId}`, {}, {
headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
},
}).then((response) => {
sendMessageSuccess(response)
}).catch((error) => {
dispatch(sendMessageFail(error))
})
}
const deleteMessageRequest = (messageId) => ({
type: MESSAGE_DELETE_REQUEST,
messageId,
})
const deleteMessageSuccess = () => ({
type: MESSAGE_DELETE_SUCCESS,
})
const deleteMessageFail = (error) => ({
type: MESSAGE_DELETE_FAIL,
error,
})

File diff suppressed because it is too large Load Diff

View File

@@ -16,17 +16,14 @@ export const fetchCustomEmojis = () => (dispatch, getState) => {
const fetchCustomEmojisRequest = () => ({
type: CUSTOM_EMOJIS_FETCH_REQUEST,
skipLoading: true,
})
const fetchCustomEmojisSuccess = (custom_emojis) => ({
type: CUSTOM_EMOJIS_FETCH_SUCCESS,
custom_emojis,
skipLoading: true,
})
const fetchCustomEmojisFail = (error) => ({
type: CUSTOM_EMOJIS_FETCH_FAIL,
error,
skipLoading: true,
})

View File

@@ -33,20 +33,17 @@ export const fetchFavoritedStatuses = () => (dispatch, getState) => {
const fetchFavoritedStatusesRequest = () => ({
type: FAVORITED_STATUSES_FETCH_REQUEST,
skipLoading: true,
})
const fetchFavoritedStatusesSuccess = (statuses, next) => ({
type: FAVORITED_STATUSES_FETCH_SUCCESS,
statuses,
next,
skipLoading: true,
})
const fetchFavoritedStatusesFail = (error) => ({
type: FAVORITED_STATUSES_FETCH_FAIL,
error,
skipLoading: true,
})
/**

View File

@@ -23,18 +23,15 @@ export const fetchFilters = () => (dispatch, getState) => {
const fetchFiltersRequest = () => ({
type: FILTERS_FETCH_REQUEST,
skipLoading: true,
})
const fetchFiltersSuccess = (filters) => ({
type: FILTERS_FETCH_SUCCESS,
filters,
skipLoading: true,
})
const fetchFiltersFail = (err) => ({
type: FILTERS_FETCH_FAIL,
err,
skipLoading: true,
skipAlert: true,
})

File diff suppressed because it is too large Load Diff

View File

@@ -14,7 +14,10 @@ export const STATUSES_IMPORT = 'STATUSES_IMPORT'
export const POLLS_IMPORT = 'POLLS_IMPORT'
export const ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP = 'ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP'
function pushUnique(array, object) {
/**
*
*/
const pushUnique = (array, object) => {
if (array.every(element => element.id !== object.id)) {
array.push(object);
}

View File

@@ -5,20 +5,26 @@ import { expandSpoilers } from '../../initial_state'
const domParser = new DOMParser()
/**
*
*/
const makeEmojiMap = record => record.emojis.reduce((obj, emoji) => {
obj[`:${emoji.shortcode}:`] = emoji;
return obj;
}, {});
obj[`:${emoji.shortcode}:`] = emoji
return obj
}, {})
export function normalizeAccount(account) {
account = { ...account };
/**
*
*/
export const normalizeAccount = (account) => {
account = { ...account }
const emojiMap = makeEmojiMap(account);
const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name;
const emojiMap = makeEmojiMap(account)
const displayName = account.display_name.trim().length === 0 ? account.username : account.display_name
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap);
account.display_name_plain = emojify(escapeTextContentForBrowser(displayName), emojiMap, true);
account.note_emojified = emojify(account.note, emojiMap);
account.display_name_html = emojify(escapeTextContentForBrowser(displayName), emojiMap)
account.display_name_plain = emojify(escapeTextContentForBrowser(displayName), emojiMap, true)
account.note_emojified = emojify(account.note, emojiMap)
account.note_plain = unescapeHTML(account.note)
if (account.fields) {
@@ -27,67 +33,73 @@ export function normalizeAccount(account) {
name_emojified: emojify(escapeTextContentForBrowser(pair.name)),
value_emojified: emojify(pair.value, emojiMap),
value_plain: unescapeHTML(pair.value),
}));
}))
}
if (account.moved) {
account.moved = account.moved.id;
account.moved = account.moved.id
}
return account;
return account
}
export function normalizeStatus(status, normalOldStatus) {
const normalStatus = { ...status };
normalStatus.account = status.account_id || status.account.id;
/**
*
*/
export const normalizeStatus = (status, normalOldStatus) => {
const normalStatus = { ...status }
normalStatus.account = status.account_id || status.account.id
if (status.reblog && status.reblog.id) {
normalStatus.reblog = status.reblog.id;
normalStatus.reblog = status.reblog.id
}
if (status.quote && status.quote.id) {
normalStatus.quote = status.quote.id;
normalStatus.quote = status.quote.id
}
if (status.poll && status.poll.id) {
normalStatus.poll = status.poll.id;
normalStatus.poll = status.poll.id
}
if (!!status.group || !!status.group_id) {
normalStatus.group = status.group_id || status.group.id;
normalStatus.group = status.group_id || status.group.id
}
// Only calculate these values when status first encountered
// Otherwise keep the ones already in the reducer
if (normalOldStatus && normalOldStatus.get('content') === normalStatus.content && normalOldStatus.get('spoiler_text') === normalStatus.spoiler_text) {
normalStatus.search_index = normalOldStatus.get('search_index');
normalStatus.contentHtml = normalOldStatus.get('contentHtml');
normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml');
normalStatus.hidden = normalOldStatus.get('hidden');
normalStatus.search_index = normalOldStatus.get('search_index')
normalStatus.contentHtml = normalOldStatus.get('contentHtml')
normalStatus.spoilerHtml = normalOldStatus.get('spoilerHtml')
normalStatus.hidden = normalOldStatus.get('hidden')
} else {
const spoilerText = normalStatus.spoiler_text || '';
const searchContent = [spoilerText, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n');
const emojiMap = makeEmojiMap(normalStatus);
const theContent = !!normalStatus.rich_content ? normalStatus.rich_content : normalStatus.content;
const spoilerText = normalStatus.spoiler_text || ''
const searchContent = [spoilerText, status.content].join('\n\n').replace(/<br\s*\/?>/g, '\n').replace(/<\/p><p>/g, '\n\n')
const emojiMap = makeEmojiMap(normalStatus)
const theContent = !!normalStatus.rich_content ? normalStatus.rich_content : normalStatus.content
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent;
normalStatus.contentHtml = emojify(theContent, emojiMap, false, true);
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap);
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive;
normalStatus.search_index = domParser.parseFromString(searchContent, 'text/html').documentElement.textContent
normalStatus.contentHtml = emojify(theContent, emojiMap, false, true)
normalStatus.spoilerHtml = emojify(escapeTextContentForBrowser(spoilerText), emojiMap)
normalStatus.hidden = expandSpoilers ? false : spoilerText.length > 0 || normalStatus.sensitive
}
return normalStatus;
return normalStatus
}
export function normalizePoll(poll) {
const normalPoll = { ...poll };
/**
*
*/
export const normalizePoll = (poll) => {
const normalPoll = { ...poll }
const emojiMap = makeEmojiMap(normalPoll);
const emojiMap = makeEmojiMap(normalPoll)
normalPoll.options = poll.options.map(option => ({
normalPoll.options = poll.options.map((option) => ({
...option,
title_emojified: emojify(escapeTextContentForBrowser(option.title), emojiMap),
}));
}))
return normalPoll;
return normalPoll
}

View File

@@ -1,5 +1,9 @@
import api from '../api'
import { importFetchedAccounts, importFetchedStatus } from './importer'
import api, { getLinks } from '../api'
import {
importFetchedAccounts,
importFetchedStatus,
} from './importer'
import { fetchRelationships } from './accounts'
import { updateStatusStats } from './statuses'
import { me } from '../initial_state'
@@ -23,6 +27,10 @@ export const REPOSTS_FETCH_REQUEST = 'REPOSTS_FETCH_REQUEST'
export const REPOSTS_FETCH_SUCCESS = 'REPOSTS_FETCH_SUCCESS'
export const REPOSTS_FETCH_FAIL = 'REPOSTS_FETCH_FAIL'
export const REPOSTS_EXPAND_REQUEST = 'REPOSTS_EXPAND_REQUEST'
export const REPOSTS_EXPAND_SUCCESS = 'REPOSTS_EXPAND_SUCCESS'
export const REPOSTS_EXPAND_FAIL = 'REPOSTS_EXPAND_FAIL'
export const PIN_REQUEST = 'PIN_REQUEST'
export const PIN_SUCCESS = 'PIN_SUCCESS'
export const PIN_FAIL = 'PIN_FAIL'
@@ -31,6 +39,10 @@ export const UNPIN_REQUEST = 'UNPIN_REQUEST'
export const UNPIN_SUCCESS = 'UNPIN_SUCCESS'
export const UNPIN_FAIL = 'UNPIN_FAIL'
export const IS_PIN_REQUEST = 'IS_PIN_REQUEST'
export const IS_PIN_SUCCESS = 'IS_PIN_SUCCESS'
export const IS_PIN_FAIL = 'IS_PIN_FAIL'
export const BOOKMARK_REQUEST = 'BOOKMARK_REQUEST'
export const BOOKMARK_SUCCESS = 'BOOKMARK_SUCCESS'
export const BOOKMARK_FAIL = 'BOOKMARK_FAIL'
@@ -39,342 +51,512 @@ export const UNBOOKMARK_REQUEST = 'UNBOOKMARK_REQUEST'
export const UNBOOKMARK_SUCCESS = 'UNBOOKMARK_SUCCESS'
export const UNBOOKMARK_FAIL = 'UNBOOKMARK_FAIL'
export const IS_BOOKMARK_REQUEST = 'IS_BOOKMARK_REQUEST'
export const IS_BOOKMARK_SUCCESS = 'IS_BOOKMARK_SUCCESS'
export const IS_BOOKMARK_FAIL = 'IS_BOOKMARK_FAIL'
export const LIKES_FETCH_REQUEST = 'LIKES_FETCH_REQUEST'
export const LIKES_FETCH_SUCCESS = 'LIKES_FETCH_SUCCESS'
export const LIKES_FETCH_FAIL = 'LIKES_FETCH_FAIL'
export const LIKES_EXPAND_REQUEST = 'LIKES_EXPAND_REQUEST'
export const LIKES_EXPAND_SUCCESS = 'LIKES_EXPAND_SUCCESS'
export const LIKES_EXPAND_FAIL = 'LIKES_EXPAND_FAIL'
/**
*
* @description Repost the given status. Set status to status.reblogged:true and
* increment status.reblogs_count by 1 on success.
* @param {ImmutableMap} status
*/
export const repost = (status) => (dispatch, getState) => {
if (!me) return
if (!me || !status) return
dispatch(repostRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then((response) => {
// The reblog API method returns a new status wrapped around the original. In this case we are only
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(response.data.reblog))
dispatch(updateStatusStats(response.data))
dispatch(repostSuccess(status))
}).catch((error) => {
dispatch(repostFail(status, error))
})
}
export const repostRequest = (status) => ({
const repostRequest = (status) => ({
type: REPOST_REQUEST,
status: status,
skipLoading: true,
status,
})
export const repostSuccess = (status) => ({
const repostSuccess = (status) => ({
type: REPOST_SUCCESS,
status: status,
skipLoading: true,
status,
})
export const repostFail = (status, error) => ({
const repostFail = (status, error) => ({
type: REPOST_FAIL,
status: status,
error: error,
skipLoading: true,
status,
error,
})
/**
*
* @description Unrepost the given status. Set status to status.reblogged:false and
* decrement status.reblogs_count by 1 on success.
* @param {ImmutableMap} status
*/
export const unrepost = (status) => (dispatch, getState) => {
if (!me) return
if (!me || !status) return
dispatch(unrepostRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then((response) => {
dispatch(importFetchedStatus(response.data))
dispatch(updateStatusStats(response.data))
dispatch(unrepostSuccess(status))
}).catch((error) => {
dispatch(unrepostFail(status, error))
})
}
export const unrepostRequest = (status) => ({
const unrepostRequest = (status) => ({
type: UNREPOST_REQUEST,
status: status,
skipLoading: true,
status,
})
export const unrepostSuccess = (status) => ({
const unrepostSuccess = (status) => ({
type: UNREPOST_SUCCESS,
status: status,
skipLoading: true,
status,
})
export const unrepostFail = (status, error) => ({
const unrepostFail = (status, error) => ({
type: UNREPOST_FAIL,
status: status,
error: error,
skipLoading: true,
status,
error,
})
/**
*
* @description Favorite the given status. Set status to status.favourited:true and
* increment status.favourites_count by 1 on success.
* @param {ImmutableMap} status
*/
export const favorite = (status) => (dispatch, getState) => {
if (!me) return
if (!me || !status) return
dispatch(favoriteRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then((response) => {
dispatch(updateStatusStats(response.data))
dispatch(favoriteSuccess(status))
dispatch(favoriteSuccess(response.data))
}).catch((error) => {
dispatch(favoriteFail(status, error))
})
}
export const favoriteRequest = (status) => ({
const favoriteRequest = (status) => ({
type: FAVORITE_REQUEST,
status: status,
skipLoading: true,
status,
})
export const favoriteSuccess = (status) => ({
const favoriteSuccess = (data) => ({
type: FAVORITE_SUCCESS,
status: status,
skipLoading: true,
data,
})
export const favoriteFail = (status, error) => ({
const favoriteFail = (status, error) => ({
type: FAVORITE_FAIL,
status: status,
error: error,
skipLoading: true,
status,
error,
})
/**
*
* @description Unfavorite the given status. Set status to status.favourited:false and
* decrement status.favourites_count by 1 on success.
* @param {ImmutableMap} status
*/
export const unfavorite = (status) => (dispatch, getState) => {
if (!me) return
if (!me || !status) return
dispatch(unfavoriteRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then((response) => {
dispatch(importFetchedStatus(response.data))
dispatch(updateStatusStats(response.data))
dispatch(unfavoriteSuccess(status))
}).catch((error) => {
dispatch(unfavoriteFail(status, error))
})
}
export const unfavoriteRequest = (status) => ({
const unfavoriteRequest = (status) => ({
type: UNFAVORITE_REQUEST,
status: status,
skipLoading: true,
status,
})
export const unfavoriteSuccess = (status) => ({
const unfavoriteSuccess = (status) => ({
type: UNFAVORITE_SUCCESS,
status: status,
skipLoading: true,
status,
})
export const unfavoriteFail = (status, error) => ({
const unfavoriteFail = (status, error) => ({
type: UNFAVORITE_FAIL,
status: status,
error: error,
skipLoading: true,
})
/**
*
*/
export const fetchReposts = (id) => (dispatch, getState) => {
if (!me) return
dispatch(fetchRepostsRequest(id))
api(getState).get(`/api/v1/statuses/${id}/reblogged_by`).then((response) => {
dispatch(importFetchedAccounts(response.data))
dispatch(fetchRepostsSuccess(id, response.data))
}).catch((error) => {
dispatch(fetchRepostsFail(id, error))
})
}
export const fetchRepostsRequest = (id) => ({
type: REPOSTS_FETCH_REQUEST,
id,
})
export const fetchRepostsSuccess = (id, accounts) => ({
type: REPOSTS_FETCH_SUCCESS,
id,
accounts,
})
export const fetchRepostsFail = (id, error) => ({
type: REPOSTS_FETCH_FAIL,
status,
error,
})
/**
*
* @description Pin the given status to your profile. Set status to status.pinned:true
* on success.
* @param {ImmutableMap} status
*/
export const pin = (status) => (dispatch, getState) => {
if (!me) return
if (!me || !status) return
dispatch(pinRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then((response) => {
dispatch(importFetchedStatus(response.data))
dispatch(updateStatusStats(response.data))
dispatch(pinSuccess(status))
}).catch((error) => {
dispatch(pinFail(status, error))
})
}
export const pinRequest = (status) => ({
const pinRequest = (status) => ({
type: PIN_REQUEST,
status,
skipLoading: true,
})
export const pinSuccess = (status) => ({
const pinSuccess = (status) => ({
type: PIN_SUCCESS,
status,
skipLoading: true,
})
export const pinFail = (status, error) => ({
const pinFail = (status, error) => ({
type: PIN_FAIL,
status,
error,
skipLoading: true,
})
/**
*
* @description Unpin the given status from your profile. Set status to status.pinned:false
* on success and remove from account pins in timeline reducer.
* @param {ImmutableMap} status
*/
export const unpin = (status) => (dispatch, getState) => {
if (!me) return
if (!me || !status) return
dispatch(unpinRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then((response) => {
dispatch(importFetchedStatus(response.data))
dispatch(unpinSuccess(status))
dispatch(updateStatusStats(response.data))
dispatch(unpinSuccess(status, response.data.account_id))
}).catch((error) => {
dispatch(unpinFail(status, error))
})
}
export const unpinRequest = (status) => ({
const unpinRequest = (status) => ({
type: UNPIN_REQUEST,
status,
skipLoading: true,
})
export const unpinSuccess = (status) => ({
const unpinSuccess = (status, accountId) => ({
type: UNPIN_SUCCESS,
accountId,
status,
skipLoading: true,
})
export const unpinFail = (status, error) => ({
const unpinFail = (status, error) => ({
type: UNPIN_FAIL,
status,
error,
skipLoading: true,
})
/**
*
* @description Check if a status is pinned to the current user account.
* @param {String} statusId
*/
export const fetchLikes = (id) => (dispatch, getState) => {
dispatch(fetchLikesRequest(id))
export const isPin = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then((response) => {
dispatch(importFetchedAccounts(response.data))
dispatch(fetchLikesSuccess(id, response.data))
dispatch(isPinRequest(statusId))
api(getState).get(`/api/v1/statuses/${statusId}/pin`).then((response) => {
dispatch(updateStatusStats(response.data))
dispatch(isPinSuccess(statusId))
}).catch((error) => {
dispatch(fetchLikesFail(id, error))
dispatch(isPinFail(statusId, error))
})
}
export const fetchLikesRequest = (id) => ({
type: LIKES_FETCH_REQUEST,
id,
const isPinRequest = (statusId) => ({
type: IS_PIN_REQUEST,
statusId,
})
export const fetchLikesSuccess = (id, accounts) => ({
type: LIKES_FETCH_SUCCESS,
id,
accounts,
const isPinSuccess = (statusId) => ({
type: IS_PIN_SUCCESS,
statusId,
})
export const fetchLikesFail = (id, error) => ({
type: LIKES_FETCH_FAIL,
const isPinFail = (statusId, error) => ({
type: IS_PIN_FAIL,
statusId,
error,
})
/**
*
* @description Bookmark the given status in your profile if PRO. Set status to
* status.bookmarked:true on success.
* @param {ImmutableMap} status
*/
export const bookmark = (status) => (dispatch, getState) => {
if (!me || !status) return
dispatch(bookmarkRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then((response) => {
dispatch(importFetchedStatus(response.data))
dispatch(bookmarkSuccess(status, response.data))
dispatch(updateStatusStats(response.data))
dispatch(bookmarkSuccess(status))
}).catch((error) => {
dispatch(bookmarkFail(status, error))
})
}
export const bookmarkRequest = (status) => ({
const bookmarkRequest = (status) => ({
type: BOOKMARK_REQUEST,
status: status,
status,
})
export const bookmarkSuccess = (status, response) => ({
const bookmarkSuccess = (status) => ({
type: BOOKMARK_SUCCESS,
status: status,
response: response,
status,
})
export const bookmarkFail = (status, error) => ({
const bookmarkFail = (status, error) => ({
type: BOOKMARK_FAIL,
status: status,
error: error,
status,
error,
})
/**
*
* @description Unbookmark the given status in your profile if PRO. Set status to
* status.bookmarked:false on success.
* @param {ImmutableMap} status
*/
export const unbookmark = (status) => (dispatch, getState) => {
if (!me || !status) return
dispatch(unbookmarkRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then((response) => {
dispatch(importFetchedStatus(response.data))
dispatch(unbookmarkSuccess(status, response.data))
dispatch(updateStatusStats(response.data))
dispatch(unbookmarkSuccess(status))
}).catch((error) => {
dispatch(unbookmarkFail(status, error))
})
}
export const unbookmarkRequest = (status) => ({
const unbookmarkRequest = (status) => ({
type: UNBOOKMARK_REQUEST,
status: status,
status,
})
export const unbookmarkSuccess = (status, response) => ({
const unbookmarkSuccess = (status) => ({
type: UNBOOKMARK_SUCCESS,
status: status,
response: response,
status,
})
export const unbookmarkFail = (status, error) => ({
const unbookmarkFail = (status, error) => ({
type: UNBOOKMARK_FAIL,
status: status,
error: error,
})
status,
error,
})
/**
* @description Check if a status is bookmarked to the current user account.
* @param {String} statusId
*/
export const isBookmark = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
dispatch(isBookmarkRequest(statusId))
api(getState).get(`/api/v1/statuses/${statusId}/bookmark`).then((response) => {
dispatch(updateStatusStats(response.data))
dispatch(isBookmarkSuccess(statusId))
}).catch((error) => {
dispatch(isBookmarkFail(statusId, error))
})
}
const isBookmarkRequest = (statusId) => ({
type: IS_BOOKMARK_REQUEST,
statusId,
})
const isBookmarkSuccess = (statusId) => ({
type: IS_BOOKMARK_SUCCESS,
statusId,
})
const isBookmarkFail = (statusId, error) => ({
type: IS_BOOKMARK_FAIL,
statusId,
error,
})
/**
* @description Fetch reposts for the given statusId and imports paginated accounts
* and sets in user_lists reducer.
* @param {String} statusId
*/
export const fetchReposts = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
dispatch(fetchRepostsRequest(statusId))
api(getState).get(`/api/v1/statuses/${statusId}/reblogged_by`).then((response) => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(fetchRepostsSuccess(statusId, response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch((error) => {
dispatch(fetchRepostsFail(statusId, error))
})
}
const fetchRepostsRequest = (statusId) => ({
type: REPOSTS_FETCH_REQUEST,
statusId,
})
const fetchRepostsSuccess = (statusId, accounts, next) => ({
type: REPOSTS_FETCH_SUCCESS,
statusId,
accounts,
next,
})
const fetchRepostsFail = (statusId, error) => ({
type: REPOSTS_FETCH_FAIL,
statusId,
error,
})
/**
* @description Expand reposts for the given statusId and imports paginated accounts
* and sets in user_lists reducer.
* @param {String} statusId
*/
export const expandReposts = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
const url = getState().getIn(['user_lists', 'reblogged_by', statusId, 'next'])
const isLoading = getState().getIn(['user_lists', 'reblogged_by', statusId, 'isLoading'])
if (url === null || isLoading) return
dispatch(expandRepostsRequest(statusId))
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(expandRepostsSuccess(statusId, response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch(error => dispatch(expandRepostsFail(error)))
}
const expandRepostsRequest = (statusId) => ({
type: REPOSTS_EXPAND_REQUEST,
statusId,
})
const expandRepostsSuccess = (statusId, accounts, next) => ({
type: REPOSTS_EXPAND_SUCCESS,
statusId,
accounts,
next,
})
const expandRepostsFail = (statusId, error) => ({
type: REPOSTS_EXPAND_FAIL,
statusId,
error,
})
/**
* @description Fetch likes for the given statusId and imports paginated accounts
* and sets in user_lists reducer.
* @param {String} statusId
*/
export const fetchLikes = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
dispatch(fetchLikesRequest(statusId))
api(getState).get(`/api/v1/statuses/${statusId}/favourited_by`).then((response) => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(fetchLikesSuccess(statusId, response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch((error) => {
dispatch(fetchLikesFail(statusId, error))
})
}
const fetchLikesRequest = (statusId) => ({
type: LIKES_FETCH_REQUEST,
statusId,
})
const fetchLikesSuccess = (statusId, accounts, next) => ({
type: LIKES_FETCH_SUCCESS,
statusId,
accounts,
next,
})
const fetchLikesFail = (statusId, error) => ({
type: LIKES_FETCH_FAIL,
statusId,
error,
})
/**
* @description Expand likes for the given statusId and imports paginated accounts
* and sets in user_lists reducer.
* @param {String} statusId
*/
export const expandLikes = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
const url = getState().getIn(['user_lists', 'liked_by', statusId, 'next'])
const isLoading = getState().getIn(['user_lists', 'liked_by', statusId, 'isLoading'])
if (url === null || isLoading) return
dispatch(expandLikesRequest(statusId))
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(expandLikesSuccess(statusId, response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch(error => dispatch(expandLikesFail(error)))
}
const expandLikesRequest = (statusId) => ({
type: LIKES_EXPAND_REQUEST,
statusId,
})
const expandLikesSuccess = (statusId, accounts, next) => ({
type: LIKES_EXPAND_SUCCESS,
statusId,
accounts,
next,
})
const expandLikesFail = (statusId, error) => ({
type: LIKES_EXPAND_FAIL,
statusId,
error,
})

View File

@@ -1,5 +1,6 @@
import api, { getLinks } from '../api'
import IntlMessageFormat from 'intl-messageformat'
import noop from 'lodash.noop'
import { fetchRelationships } from './accounts'
import {
importFetchedAccount,
@@ -49,8 +50,6 @@ const excludeTypesFromFilter = filter => {
return allTypes.filterNot(item => item === filter).toJS()
}
const noOp = () => {}
/**
*
*/
@@ -159,7 +158,7 @@ export const dequeueNotifications = () => (dispatch, getState) => {
/**
*
*/
export const expandNotifications = ({ maxId } = {}, done = noOp) => (dispatch, getState) => {
export const expandNotifications = ({ maxId } = {}, done = noop) => (dispatch, getState) => {
if (!me) return
const onlyVerified = getState().getIn(['notifications', 'filter', 'onlyVerified'])
@@ -204,19 +203,19 @@ export const expandNotifications = ({ maxId } = {}, done = noOp) => (dispatch, g
})
}
export const expandNotificationsRequest = (isLoadingMore) => ({
const expandNotificationsRequest = (isLoadingMore) => ({
type: NOTIFICATIONS_EXPAND_REQUEST,
skipLoading: !isLoadingMore,
})
export const expandNotificationsSuccess = (notifications, next, isLoadingMore) => ({
const expandNotificationsSuccess = (notifications, next, isLoadingMore) => ({
type: NOTIFICATIONS_EXPAND_SUCCESS,
notifications,
next,
skipLoading: !isLoadingMore,
})
export const expandNotificationsFail = (error, isLoadingMore) => ({
const expandNotificationsFail = (error, isLoadingMore) => ({
type: NOTIFICATIONS_EXPAND_FAIL,
error,
skipLoading: !isLoadingMore,

View File

@@ -1,15 +1,23 @@
import api from '../api'
export const STATUS_REVISIONS_LOAD = 'STATUS_REVISIONS_LOAD'
export const STATUS_REVISIONS_LOAD_REQUEST = 'STATUS_REVISIONS_LOAD_REQUEST'
export const STATUS_REVISIONS_LOAD_SUCCESS = 'STATUS_REVISIONS_SUCCESS'
export const STATUS_REVISIONS_LOAD_FAIL = 'STATUS_REVISIONS_FAIL'
/**
*
*/
export const loadStatusRevisions = (statusId) => (dispatch, getState) => {
dispatch(loadStatusRevisionsRequest())
api(getState).get(`/api/v1/statuses/${statusId}/revisions`)
.then(res => dispatch(loadStatusRevisionsSuccess(res.data)))
.catch(() => dispatch(loadStatusRevisionsFail()))
}
const loadStatusRevisionsRequest = () => ({
type: STATUS_REVISIONS_LOAD_REQUEST,
})
const loadStatusRevisionsSuccess = (data) => ({
type: STATUS_REVISIONS_LOAD_SUCCESS,
revisions: data,

View File

@@ -1,304 +1,292 @@
import api from '../api';
import openDB from '../storage/db';
import { evictStatus } from '../storage/modifier';
import { deleteFromTimelines } from './timelines';
import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer';
import { openModal } from './modal';
import { me } from '../initial_state';
import api from '../api'
import openDB from '../storage/db'
import { evictStatus } from '../storage/modifier'
import { deleteFromTimelines } from './timelines'
import { importFetchedStatus, importFetchedStatuses, importAccount, importStatus } from './importer'
import { openModal } from './modal'
import { me } from '../initial_state'
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST';
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS';
export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL';
export const STATUS_FETCH_REQUEST = 'STATUS_FETCH_REQUEST'
export const STATUS_FETCH_SUCCESS = 'STATUS_FETCH_SUCCESS'
export const STATUS_FETCH_FAIL = 'STATUS_FETCH_FAIL'
export const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST';
export const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS';
export const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL';
export const STATUS_DELETE_REQUEST = 'STATUS_DELETE_REQUEST'
export const STATUS_DELETE_SUCCESS = 'STATUS_DELETE_SUCCESS'
export const STATUS_DELETE_FAIL = 'STATUS_DELETE_FAIL'
export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST';
export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS';
export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL';
export const CONTEXT_FETCH_REQUEST = 'CONTEXT_FETCH_REQUEST'
export const CONTEXT_FETCH_SUCCESS = 'CONTEXT_FETCH_SUCCESS'
export const CONTEXT_FETCH_FAIL = 'CONTEXT_FETCH_FAIL'
export const COMMENTS_FETCH_REQUEST = 'COMMENTS_FETCH_REQUEST';
export const COMMENTS_FETCH_SUCCESS = 'COMMENTS_FETCH_SUCCESS';
export const COMMENTS_FETCH_FAIL = 'COMMENTS_FETCH_FAIL';
export const COMMENTS_FETCH_REQUEST = 'COMMENTS_FETCH_REQUEST'
export const COMMENTS_FETCH_SUCCESS = 'COMMENTS_FETCH_SUCCESS'
export const COMMENTS_FETCH_FAIL = 'COMMENTS_FETCH_FAIL'
export const STATUS_REVEAL = 'STATUS_REVEAL';
export const STATUS_HIDE = 'STATUS_HIDE';
export const STATUS_REVEAL = 'STATUS_REVEAL'
export const STATUS_HIDE = 'STATUS_HIDE'
export const STATUS_EDIT = 'STATUS_EDIT';
export const STATUS_EDIT = 'STATUS_EDIT'
export const UPDATE_STATUS_STATS = 'UPDATE_STATUS_STATS'
export function fetchStatusRequest(id, skipLoading) {
return {
type: STATUS_FETCH_REQUEST,
id,
skipLoading,
};
};
/**
*
*/
function getFromDB(dispatch, getState, accountIndex, index, id) {
return new Promise((resolve, reject) => {
const request = index.get(id);
const request = index.get(id)
request.onerror = reject;
request.onerror = reject
request.onsuccess = () => {
const promises = [];
const promises = []
if (!request.result) {
reject();
return;
reject()
return
}
dispatch(importStatus(request.result));
dispatch(importStatus(request.result))
if (getState().getIn(['accounts', request.result.account], null) === null) {
promises.push(new Promise((accountResolve, accountReject) => {
const accountRequest = accountIndex.get(request.result.account);
const accountRequest = accountIndex.get(request.result.account)
accountRequest.onerror = accountReject;
accountRequest.onerror = accountReject
accountRequest.onsuccess = () => {
if (!request.result) {
accountReject();
return;
accountReject()
return
}
dispatch(importAccount(accountRequest.result));
accountResolve();
};
}));
dispatch(importAccount(accountRequest.result))
accountResolve()
}
}))
}
if (request.result.reblog && getState().getIn(['statuses', request.result.reblog], null) === null) {
promises.push(getFromDB(dispatch, getState, accountIndex, index, request.result.reblog));
promises.push(getFromDB(dispatch, getState, accountIndex, index, request.result.reblog))
}
resolve(Promise.all(promises));
};
});
resolve(Promise.all(promises))
}
})
}
export function fetchStatus(id) {
return (dispatch, getState) => {
const skipLoading = getState().getIn(['statuses', id], null) !== null;
/**
*
*/
export const fetchStatus = (id) => (dispatch, getState) => {
const skipLoading = getState().getIn(['statuses', id], null) !== null
if (skipLoading) return
if (skipLoading) {
return;
dispatch(fetchStatusRequest(id, skipLoading))
openDB().then((db) => {
const transaction = db.transaction(['accounts', 'statuses'], 'read')
const accountIndex = transaction.objectStore('accounts').index('id')
const index = transaction.objectStore('statuses').index('id')
return getFromDB(dispatch, getState, accountIndex, index, id).then(() => {
db.close()
}, (error) => {
db.close()
throw error
})
}).then(() => {
dispatch(fetchStatusSuccess(skipLoading))
}, () => api(getState).get(`/api/v1/statuses/${id}`).then((response) => {
dispatch(importFetchedStatus(response.data))
dispatch(fetchStatusSuccess(skipLoading))
})).catch((error) => {
dispatch(fetchStatusFail(id, error, skipLoading))
})
}
const fetchStatusRequest = (id, skipLoading) => ({
type: STATUS_FETCH_REQUEST,
id,
skipLoading,
})
const fetchStatusSuccess = (skipLoading) => ({
type: STATUS_FETCH_SUCCESS,
skipLoading,
})
const fetchStatusFail = (id, error, skipLoading) => ({
type: STATUS_FETCH_FAIL,
id,
error,
skipLoading,
skipAlert: true,
})
/**
*
*/
export const editStatus = (status) => (dispatch) => {
dispatch({
type: STATUS_EDIT,
status,
})
dispatch(openModal('COMPOSE'))
}
/**
*
*/
export const deleteStatus = (id, routerHistory) => (dispatch, getState) => {
if (!me) return
let status = getState().getIn(['statuses', id])
if (status.get('poll')) {
status = status.set('poll', getState().getIn(['polls', status.get('poll')]))
}
dispatch(deleteStatusRequest(id))
api(getState).delete(`/api/v1/statuses/${id}`).then((response) => {
evictStatus(id)
dispatch(deleteStatusSuccess(id))
dispatch(deleteFromTimelines(id))
}).catch((error) => {
dispatch(deleteStatusFail(id, error))
})
}
const deleteStatusRequest = (id) => ({
type: STATUS_DELETE_REQUEST,
id: id,
})
const deleteStatusSuccess = (id) => ({
type: STATUS_DELETE_SUCCESS,
id: id,
})
const deleteStatusFail = (id, error) => ({
type: STATUS_DELETE_FAIL,
id: id,
error,
})
/**
*
*/
export const fetchContext = (id, ensureIsReply) => (dispatch, getState) => {
if (ensureIsReply) {
const isReply = !!getState().getIn(['statuses', id, 'in_reply_to_id'], null)
if (!isReply) return
}
dispatch(fetchContextRequest(id))
api(getState).get(`/api/v1/statuses/${id}/context`).then((response) => {
dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants)))
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants))
}).catch((error) => {
if (error.response && error.response.status === 404) {
dispatch(deleteFromTimelines(id))
}
dispatch(fetchContextFail(id, error))
})
}
/**
*
*/
const fetchContextRequest = (id) => ({
type: CONTEXT_FETCH_REQUEST,
id,
})
const fetchContextSuccess = (id, ancestors, descendants) => ({
type: CONTEXT_FETCH_SUCCESS,
id,
ancestors,
descendants,
statuses: ancestors.concat(descendants),
})
const fetchContextFail = (id, error) => ({
type: CONTEXT_FETCH_FAIL,
id,
error,
skipAlert: true,
})
/**
*
*/
export const fetchComments = (id) => (dispatch, getState) => {
dispatch(fetchCommentsRequest(id))
api(getState).get(`/api/v1/statuses/${id}/comments`).then((response) => {
dispatch(importFetchedStatuses(response.data.descendants))
dispatch(fetchCommentsSuccess(id, response.data.descendants))
}).catch((error) => {
if (error.response && error.response.status === 404) {
dispatch(deleteFromTimelines(id))
}
dispatch(fetchStatusRequest(id, skipLoading));
dispatch(fetchCommentsFail(id, error))
})
}
openDB().then(db => {
const transaction = db.transaction(['accounts', 'statuses'], 'read');
const accountIndex = transaction.objectStore('accounts').index('id');
const index = transaction.objectStore('statuses').index('id');
const fetchCommentsRequest = (id) => ({
type: COMMENTS_FETCH_REQUEST,
id,
})
return getFromDB(dispatch, getState, accountIndex, index, id).then(() => {
db.close();
}, error => {
db.close();
throw error;
});
}).then(() => {
dispatch(fetchStatusSuccess(skipLoading));
}, () => api(getState).get(`/api/v1/statuses/${id}`).then(response => {
dispatch(importFetchedStatus(response.data));
dispatch(fetchStatusSuccess(skipLoading));
})).catch(error => {
dispatch(fetchStatusFail(id, error, skipLoading));
});
};
};
const fetchCommentsSuccess = (id, descendants) => ({
type: COMMENTS_FETCH_SUCCESS,
id,
descendants,
})
export function fetchStatusSuccess(skipLoading) {
return {
type: STATUS_FETCH_SUCCESS,
skipLoading,
};
};
const fetchCommentsFail = (id, error) => ({
type: COMMENTS_FETCH_FAIL,
id,
error,
skipAlert: true,
})
export function fetchStatusFail(id, error, skipLoading) {
return {
type: STATUS_FETCH_FAIL,
id,
error,
skipLoading,
skipAlert: true,
};
};
export function editStatus(status) {
return dispatch => {
dispatch({
type: STATUS_EDIT,
status,
});
dispatch(openModal('COMPOSE'));
};
};
export function deleteStatus(id, routerHistory) {
return (dispatch, getState) => {
if (!me) return;
let status = getState().getIn(['statuses', id]);
if (status.get('poll')) {
status = status.set('poll', getState().getIn(['polls', status.get('poll')]));
}
dispatch(deleteStatusRequest(id));
api(getState).delete(`/api/v1/statuses/${id}`).then(response => {
evictStatus(id);
dispatch(deleteStatusSuccess(id));
dispatch(deleteFromTimelines(id));
}).catch(error => {
dispatch(deleteStatusFail(id, error));
});
};
};
export function deleteStatusRequest(id) {
return {
type: STATUS_DELETE_REQUEST,
id: id,
};
};
export function deleteStatusSuccess(id) {
return {
type: STATUS_DELETE_SUCCESS,
id: id,
};
};
export function deleteStatusFail(id, error) {
return {
type: STATUS_DELETE_FAIL,
id: id,
error: error,
};
};
export function fetchContext(id, ensureIsReply) {
return (dispatch, getState) => {
if (ensureIsReply) {
const isReply = !!getState().getIn(['statuses', id, 'in_reply_to_id'], null)
if (!isReply) return;
}
dispatch(fetchContextRequest(id));
api(getState).get(`/api/v1/statuses/${id}/context`).then(response => {
dispatch(importFetchedStatuses(response.data.ancestors.concat(response.data.descendants)));
dispatch(fetchContextSuccess(id, response.data.ancestors, response.data.descendants));
}).catch(error => {
if (error.response && error.response.status === 404) {
dispatch(deleteFromTimelines(id));
}
dispatch(fetchContextFail(id, error));
});
};
};
export function fetchComments(id) {
return (dispatch, getState) => {
dispatch(fetchCommentsRequest(id));
api(getState).get(`/api/v1/statuses/${id}/comments`).then(response => {
dispatch(importFetchedStatuses(response.data.descendants));
dispatch(fetchCommentsSuccess(id, response.data.descendants));
}).catch(error => {
if (error.response && error.response.status === 404) {
dispatch(deleteFromTimelines(id));
}
dispatch(fetchCommentsFail(id, error));
});
};
};
export function fetchContextRequest(id) {
return {
type: CONTEXT_FETCH_REQUEST,
id,
};
};
export function fetchContextSuccess(id, ancestors, descendants) {
return {
type: CONTEXT_FETCH_SUCCESS,
id,
ancestors,
descendants,
statuses: ancestors.concat(descendants),
};
};
export function fetchContextFail(id, error) {
return {
type: CONTEXT_FETCH_FAIL,
id,
error,
skipAlert: true,
};
};
export function fetchCommentsRequest(id) {
return {
type: COMMENTS_FETCH_REQUEST,
id,
};
};
export function fetchCommentsSuccess(id, descendants) {
return {
type: COMMENTS_FETCH_SUCCESS,
id,
descendants,
};
};
export function fetchCommentsFail(id, error) {
return {
type: COMMENTS_FETCH_FAIL,
id,
error,
skipAlert: true,
};
};
export function hideStatus(ids) {
/**
*
*/
export const hideStatus = (ids) => {
if (!Array.isArray(ids)) {
ids = [ids];
ids = [ids]
}
return {
type: STATUS_HIDE,
ids,
};
};
}
}
export function revealStatus(ids) {
/**
*
*/
export const revealStatus = (ids) => {
if (!Array.isArray(ids)) {
ids = [ids];
ids = [ids]
}
return {
type: STATUS_REVEAL,
ids,
};
};
}
}
export function updateStatusStats(data) {
return {
type: UPDATE_STATUS_STATS,
data,
};
};
/**
*
*/
export const updateStatusStats = (data) => ({
type: UPDATE_STATUS_STATS,
data,
})

View File

@@ -71,3 +71,20 @@ export const connectStatusUpdateStream = () => {
*
*/
export const connectUserStream = () => connectTimelineStream('home', 'user')
/**
*
*/
export const connectMessageStream = () => {
return connectStream('chat_messages', null, (dispatch, getState) => {
return {
onConnect() {},
onDisconnect() {},
onReceive (data) {
//
},
}
})
}

View File

@@ -44,20 +44,17 @@ const fetchSuggestions = (suggestionType, dispatch, getState, unlimited = false)
const fetchSuggestionsRequest = (suggestionType) => ({
type: SUGGESTIONS_FETCH_REQUEST,
skipLoading: true,
suggestionType,
})
const fetchSuggestionsSuccess = (accounts, suggestionType) => ({
type: SUGGESTIONS_FETCH_SUCCESS,
skipLoading: true,
accounts,
suggestionType
})
const fetchSuggestionsFail = (error, suggestionType) => ({
type: SUGGESTIONS_FETCH_FAIL,
skipLoading: true,
skipAlert: true,
error,
suggestionType,

View File

@@ -1,133 +1,139 @@
import { Map as ImmutableMap, List as ImmutableList, toJS } from 'immutable';
import { importFetchedStatus, importFetchedStatuses } from './importer';
import api, { getLinks } from '../api';
import { Map as ImmutableMap, List as ImmutableList, toJS } from 'immutable'
import noop from 'lodash.noop'
import { importFetchedStatus, importFetchedStatuses } from './importer'
import api, { getLinks } from '../api'
import { fetchRelationships } from './accounts'
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE';
export const TIMELINE_DELETE = 'TIMELINE_DELETE';
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR';
export const TIMELINE_UPDATE_QUEUE = 'TIMELINE_UPDATE_QUEUE';
export const TIMELINE_DEQUEUE = 'TIMELINE_DEQUEUE';
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP';
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'
export const TIMELINE_DELETE = 'TIMELINE_DELETE'
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR'
export const TIMELINE_UPDATE_QUEUE = 'TIMELINE_UPDATE_QUEUE'
export const TIMELINE_DEQUEUE = 'TIMELINE_DEQUEUE'
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'
export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST';
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS';
export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL';
export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'
export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT';
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT';
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'
export const MAX_QUEUED_ITEMS = 40;
export function updateTimeline(timeline, status, accept) {
return dispatch => {
if (typeof accept === 'function' && !accept(status)) {
return;
}
dispatch(importFetchedStatus(status));
dispatch({
type: TIMELINE_UPDATE,
timeline,
status,
});
};
};
export function updateTimelineQueue(timeline, status, accept) {
return dispatch => {
if (typeof accept === 'function' && !accept(status)) {
return;
}
dispatch({
type: TIMELINE_UPDATE_QUEUE,
timeline,
status,
});
}
};
export function forceDequeueTimeline(timeline) {
return (dispatch) => {
dispatch({
type: TIMELINE_DEQUEUE,
timeline,
})
}
}
export function dequeueTimeline(timeline, expandFunc, optionalExpandArgs) {
return (dispatch, getState) => {
const queuedItems = getState().getIn(['timelines', timeline, 'queuedItems'], ImmutableList());
const totalQueuedItemsCount = getState().getIn(['timelines', timeline, 'totalQueuedItemsCount'], 0);
let shouldDispatchDequeue = true;
if (totalQueuedItemsCount === 0) {
return;
} else if (totalQueuedItemsCount > 0 && totalQueuedItemsCount <= MAX_QUEUED_ITEMS) {
queuedItems.forEach(status => {
dispatch(updateTimeline(timeline, status.toJS(), null));
});
} else {
if (typeof expandFunc === 'function') {
dispatch(clearTimeline(timeline));
expandFunc();
} else {
if (timeline === 'home') {
dispatch(clearTimeline(timeline));
dispatch(expandHomeTimeline(optionalExpandArgs));
} else if (timeline === 'community') {
dispatch(clearTimeline(timeline));
dispatch(expandCommunityTimeline(optionalExpandArgs));
} else {
shouldDispatchDequeue = false;
}
}
}
if (!shouldDispatchDequeue) return;
dispatch({
type: TIMELINE_DEQUEUE,
timeline,
});
}
};
export function deleteFromTimelines(id) {
return (dispatch, getState) => {
const accountId = getState().getIn(['statuses', id, 'account']);
const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')]);
const reblogOf = getState().getIn(['statuses', id, 'reblog'], null);
dispatch({
type: TIMELINE_DELETE,
id,
accountId,
references,
reblogOf,
});
};
};
export function clearTimeline(timeline) {
return (dispatch) => {
dispatch({ type: TIMELINE_CLEAR, timeline });
};
};
const noOp = () => { };
export const MAX_QUEUED_ITEMS = 40
const parseTags = (tags = {}, mode) => {
return (tags[mode] || []).map((tag) => {
return tag.value;
});
};
return (tags[mode] || []).map((tag) => tag.value)
}
export const expandTimeline = (timelineId, path, params = {}, done = noOp) => (dispatch, getState) => {
/**
*
*/
export const updateTimeline = (timeline, status, accept) => (dispatch) => {
if (typeof accept === 'function' && !accept(status)) return
dispatch(importFetchedStatus(status))
dispatch({
type: TIMELINE_UPDATE,
timeline,
status,
})
}
/**
*
*/
export const updateTimelineQueue = (timeline, status, accept) => (dispatch) => {
if (typeof accept === 'function' && !accept(status)) return
dispatch({
type: TIMELINE_UPDATE_QUEUE,
timeline,
status,
})
}
/**
*
*/
export const forceDequeueTimeline = (timeline) => (dispatch) => {
dispatch({
type: TIMELINE_DEQUEUE,
timeline,
})
}
/**
*
*/
export const dequeueTimeline = (timeline, expandFunc, optionalExpandArgs) => (dispatch, getState) => {
const queuedItems = getState().getIn(['timelines', timeline, 'queuedItems'], ImmutableList())
const totalQueuedItemsCount = getState().getIn(['timelines', timeline, 'totalQueuedItemsCount'], 0)
let shouldDispatchDequeue = true
if (totalQueuedItemsCount === 0) return
if (totalQueuedItemsCount > 0 && totalQueuedItemsCount <= MAX_QUEUED_ITEMS) {
queuedItems.forEach((status) => {
dispatch(updateTimeline(timeline, status.toJS(), null))
})
} else {
if (typeof expandFunc === 'function') {
dispatch(clearTimeline(timeline))
expandFunc()
} else {
if (timeline === 'home') {
dispatch(clearTimeline(timeline))
dispatch(expandHomeTimeline(optionalExpandArgs))
} else if (timeline === 'community') {
dispatch(clearTimeline(timeline))
dispatch(expandCommunityTimeline(optionalExpandArgs))
} else {
shouldDispatchDequeue = false
}
}
}
if (!shouldDispatchDequeue) return
dispatch({
type: TIMELINE_DEQUEUE,
timeline,
})
}
/**
*
*/
export const deleteFromTimelines = (id) => (dispatch, getState) => {
const accountId = getState().getIn(['statuses', id, 'account'])
const references = getState().get('statuses').filter(status => status.get('reblog') === id).map(status => [status.get('id'), status.get('account')])
const reblogOf = getState().getIn(['statuses', id, 'reblog'], null)
dispatch({
type: TIMELINE_DELETE,
id,
accountId,
references,
reblogOf,
})
}
/**
*
*/
export const clearTimeline = (timeline) => (dispatch) => {
dispatch({
type: TIMELINE_CLEAR,
timeline
})
}
/**
*
*/
export const expandTimeline = (timelineId, path, params = {}, done = noop) => (dispatch, getState) => {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap())
const isLoadingMore = !!params.max_id
@@ -156,74 +162,179 @@ export const expandTimeline = (timelineId, path, params = {}, done = noOp) => (d
})
}
export const expandHomeTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('home', '/api/v1/timelines/home', { max_id: maxId }, done);
export const expandExploreTimeline = ({ maxId, sortBy } = {}, done = noOp) => expandTimeline('explore', '/api/v1/timelines/explore', { max_id: maxId, sort_by: sortBy }, done);
export const expandProTimeline = ({ maxId } = {}, done = noOp) => expandTimeline('pro', '/api/v1/timelines/pro', { max_id: maxId }, done);
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', { max_id: maxId, only_media: !!onlyMedia }, done);
export const expandAccountTimeline = (accountId, { maxId, withReplies, commentsOnly } = {}) => expandTimeline(`account:${accountId}${withReplies ? ':with_replies' : ''}${commentsOnly ? ':comments_only' : ''}`, `/api/v1/accounts/${accountId}/statuses`, { only_comments: commentsOnly, exclude_replies: (!withReplies && !commentsOnly), max_id: maxId });
export const expandAccountFeaturedTimeline = accountId => expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, { pinned: true });
export const expandAccountMediaTimeline = (accountId, { maxId, limit, mediaType } = {}) => expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, { max_id: maxId, only_media: true, limit: limit || 20, media_type: mediaType });
export const expandListTimeline = (id, { maxId } = {}, done = noOp) => expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, { max_id: maxId }, done);
export const expandGroupTimeline = (id, { sortBy, maxId, onlyMedia } = {}, done = noOp) => expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { sort_by: sortBy, max_id: maxId, only_media: onlyMedia }, done);
export const expandGroupFeaturedTimeline = (groupId, done = noOp) => expandTimeline(`group:${groupId}:pinned`, `/api/v1/timelines/group_pins/${groupId}`, {}, done);
export const expandGroupCollectionTimeline = (collectionType, { sortBy, maxId } = {}, done = noOp) => expandTimeline(`group_collection:${collectionType}`, `/api/v1/timelines/group_collection/${collectionType}`, { sort_by: sortBy, max_id: maxId }, done);
export const expandLinkTimeline = (linkId, { maxId } = {}, done = noOp) => expandTimeline(`link:${linkId}`, `/api/v1/timelines/preview_card/${linkId}`, { max_id: maxId }, done);
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noOp) => {
const expandTimelineRequest = (timeline, isLoadingMore) => ({
type: TIMELINE_EXPAND_REQUEST,
timeline,
skipLoading: !isLoadingMore,
})
const expandTimelineSuccess = (timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) => ({
type: TIMELINE_EXPAND_SUCCESS,
timeline,
statuses,
next,
partial,
isLoadingRecent,
skipLoading: !isLoadingMore,
})
const expandTimelineFail = (timeline, error, isLoadingMore) => ({
type: TIMELINE_EXPAND_FAIL,
timeline,
error,
skipLoading: !isLoadingMore,
})
/**
*
*/
export const scrollTopTimeline = (timeline, top) => ({
type: TIMELINE_SCROLL_TOP,
timeline,
top,
})
/**
*
*/
export const connectTimeline = (timeline) => ({
type: TIMELINE_CONNECT,
timeline,
})
/**
*
*/
export const disconnectTimeline = (timeline) => ({
type: TIMELINE_DISCONNECT,
timeline,
})
/**
*
*/
export const expandHomeTimeline = ({ maxId } = {}, done = noop) => {
return expandTimeline('home', '/api/v1/timelines/home', {
max_id: maxId,
}, done)
}
/**
*
*/
export const expandExploreTimeline = ({ maxId, sortBy } = {}, done = noop) => {
return expandTimeline('explore', '/api/v1/timelines/explore', {
max_id: maxId,
sort_by: sortBy,
}, done)
}
/**
*
*/
export const expandProTimeline = ({ maxId } = {}, done = noop) => {
return expandTimeline('pro', '/api/v1/timelines/pro', {
max_id: maxId,
}, done)
}
/**
*
*/
export const expandCommunityTimeline = ({ maxId, onlyMedia } = {}, done = noop) => {
return expandTimeline(`community${onlyMedia ? ':media' : ''}`, '/api/v1/timelines/public', {
max_id: maxId,
only_media: !!onlyMedia,
}, done)
}
/**
*
*/
export const expandAccountTimeline = (accountId, { maxId, withReplies, commentsOnly } = {}) => {
let key = `account:${accountId}${withReplies ? ':with_replies' : ''}${commentsOnly ? ':comments_only' : ''}`
return expandTimeline(key, `/api/v1/accounts/${accountId}/statuses`, {
only_comments: commentsOnly,
exclude_replies: (!withReplies && !commentsOnly),
max_id: maxId,
})
}
/**
*
*/
export const expandAccountFeaturedTimeline = (accountId) => {
return expandTimeline(`account:${accountId}:pinned`, `/api/v1/accounts/${accountId}/statuses`, {
pinned: true,
})
}
/**
*
*/
export const expandAccountMediaTimeline = (accountId, { maxId, limit, mediaType } = {}) => {
return expandTimeline(`account:${accountId}:media`, `/api/v1/accounts/${accountId}/statuses`, {
max_id: maxId,
only_media: true,
limit: limit || 20,
media_type: mediaType
})
}
/**
*
*/
export const expandListTimeline = (id, { maxId } = {}, done = noop) => {
return expandTimeline(`list:${id}`, `/api/v1/timelines/list/${id}`, {
max_id: maxId,
}, done)
}
/**
*
*/
export const expandGroupTimeline = (id, { sortBy, maxId, onlyMedia } = {}, done = noop) => {
return expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, {
sort_by: sortBy,
max_id: maxId,
only_media: onlyMedia
}, done)
}
/**
*
*/
export const expandGroupFeaturedTimeline = (groupId, done = noop) => {
return expandTimeline(`group:${groupId}:pinned`, `/api/v1/timelines/group_pins/${groupId}`, {}, done)
}
/**
*
*/
export const expandGroupCollectionTimeline = (collectionType, { sortBy, maxId } = {}, done = noop) => {
return expandTimeline(`group_collection:${collectionType}`, `/api/v1/timelines/group_collection/${collectionType}`, {
sort_by: sortBy,
max_id: maxId,
}, done)
}
/**
*
*/
export const expandLinkTimeline = (linkId, { maxId } = {}, done = noop) => {
return expandTimeline(`link:${linkId}`, `/api/v1/timelines/preview_card/${linkId}`, {
max_id: maxId,
}, done)
}
/**
*
*/
export const expandHashtagTimeline = (hashtag, { maxId, tags } = {}, done = noop) => {
return expandTimeline(`hashtag:${hashtag}`, `/api/v1/timelines/tag/${hashtag}`, {
max_id: maxId,
any: parseTags(tags, 'any'),
all: parseTags(tags, 'all'),
none: parseTags(tags, 'none'),
}, done);
};
export function expandTimelineRequest(timeline, isLoadingMore) {
return {
type: TIMELINE_EXPAND_REQUEST,
timeline,
skipLoading: !isLoadingMore,
};
};
export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) {
return {
type: TIMELINE_EXPAND_SUCCESS,
timeline,
statuses,
next,
partial,
isLoadingRecent,
skipLoading: !isLoadingMore,
};
};
export function expandTimelineFail(timeline, error, isLoadingMore) {
return {
type: TIMELINE_EXPAND_FAIL,
timeline,
error,
skipLoading: !isLoadingMore,
};
};
export function connectTimeline(timeline) {
return {
type: TIMELINE_CONNECT,
timeline,
};
};
export function disconnectTimeline(timeline) {
return {
type: TIMELINE_DISCONNECT,
timeline,
};
};
export function scrollTopTimeline(timeline, top) {
return {
type: TIMELINE_SCROLL_TOP,
timeline,
top,
};
};
}, done)
}

View File

@@ -80,7 +80,7 @@ class Account extends ImmutablePureComponent {
</Button>
) : <AccountActionButton account={account} isSmall />
const avatarSize = compact ? 42 : 52
const avatarSize = compact ? 40 : 52
const dismissBtn = !showDismiss ? null : (
<Button
isNarrow

View File

@@ -230,7 +230,7 @@ const mapDispatchToProps = (dispatch) => ({
onOpenStatusOptions(targetRef, status) {
dispatch(openPopover('STATUS_OPTIONS', {
targetRef,
status,
statusId: status.get('id'),
position: 'top',
}))
},

View File

@@ -126,7 +126,7 @@ const messages = defineMessages({
down: { id: 'keyboard_shortcuts.down', defaultMessage: 'move down in the list' },
column: { id: 'keyboard_shortcuts.column', defaultMessage: 'focus a status in one of the columns' },
compose: { id: 'keyboard_shortcuts.compose', defaultMessage: 'focus the compose textarea' },
gab: { id: 'keyboard_shortcuts.toot', defaultMessage: 'start a brand new gab' },
gab: { id: 'keyboard_shortcuts.gab', defaultMessage: 'start a brand new gab' },
back: { id: 'keyboard_shortcuts.back', defaultMessage: 'navigate back' },
search: { id: 'keyboard_shortcuts.search', defaultMessage: 'focus search' },
unfocus: { id: 'keyboard_shortcuts.unfocus', defaultMessage: 'un-focus compose textarea/search' },

View File

@@ -4,7 +4,7 @@ import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { defineMessages, injectIntl } from 'react-intl'
import { fetchGroups } from '../../actions/groups'
import { fetchGroupsByTab } from '../../actions/groups'
import PanelLayout from './panel_layout'
import GroupListItem from '../group_list_item'
import ScrollableList from '../scrollable_list'
@@ -26,13 +26,13 @@ class GroupsPanel extends ImmutablePureComponent {
componentDidUpdate(prevProps, prevState, snapshot) {
if (!prevState.fetched && this.state.fetched) {
this.props.onFetchGroups(this.props.groupType)
this.props.onFetchGroupsByTab(this.props.groupType)
}
}
componentDidMount() {
if (!this.props.isLazy) {
this.props.onFetchGroups(this.props.groupType)
this.props.onFetchGroupsByTab(this.props.groupType)
this.setState({ fetched: true })
}
}
@@ -93,7 +93,7 @@ const mapStateToProps = (state, { groupType }) => ({
})
const mapDispatchToProps = (dispatch) => ({
onFetchGroups: (type) => dispatch(fetchGroups(type))
onFetchGroupsByTab: (type) => dispatch(fetchGroupsByTab(type))
})
GroupsPanel.propTypes = {

View File

@@ -5,13 +5,16 @@ import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl'
import { me, isStaff, boostModal, deleteModal } from '../../initial_state'
import { makeGetStatus } from '../../selectors'
import {
repost,
unrepost,
pin,
unpin,
isPin,
bookmark,
unbookmark,
isBookmark,
} from '../../actions/interactions';
import {
deleteStatus,
@@ -24,6 +27,7 @@ import {
groupRemoveStatus,
pinGroupStatus,
unpinGroupStatus,
isPinnedGroupStatus,
} from '../../actions/groups'
import { initReport } from '../../actions/reports'
import { openModal } from '../../actions/modal'
@@ -51,11 +55,23 @@ class StatusOptionsPopover extends ImmutablePureComponent {
]
componentDidMount() {
const {
status,
statusId,
groupRelationships,
} = this.props
if (status.get('pinnable')) {
this.props.fetchIsPin(statusId)
}
this.props.fetchIsBookmark(statusId)
if (!!status.get('group')) {
this.props.fetchIsPinnedGroupStatus(status.getIn(['group', 'id'], null), statusId)
}
if (!this.props.groupRelationships && this.props.groupId) {
this.props.onFetchGroupRelationships(this.props.groupId)
// : todo :
// check if pin
// check if bookmark
}
}
@@ -131,11 +147,15 @@ class StatusOptionsPopover extends ImmutablePureComponent {
isXS,
} = this.props
if (!status) return <div />
const mutingConversation = status.get('muted')
const publicStatus = ['public', 'unlisted'].includes(status.get('visibility'))
const isReply = !!status.get('in_reply_to_id')
const withGroupAdmin = !!groupRelationships ? (groupRelationships.get('admin') || groupRelationships.get('moderator')) : false
console.log("publicStatus:", status, publicStatus)
let menu = []
if (me) {
@@ -296,13 +316,15 @@ const messages = defineMessages({
share: { id: 'status.share_gab', defaultMessage: 'Share gab' },
})
const mapStateToProps = (state, { status }) => {
const mapStateToProps = (state, { statusId }) => {
if (!me) return null
const status = statusId ? makeGetStatus()(state, { id: statusId }) : undefined
const groupId = status ? status.getIn(['group', 'id']) : undefined
const groupRelationships = state.getIn(['group_relationships', groupId])
return {
status,
groupId,
groupRelationships,
isPro: state.getIn(['accounts', me, 'is_pro']),
@@ -311,6 +333,18 @@ const mapStateToProps = (state, { status }) => {
const mapDispatchToProps = (dispatch) => ({
fetchIsPin(statusId) {
dispatch(isPin(statusId))
},
fetchIsBookmark(statusId) {
dispatch(isBookmark(statusId))
},
fetchIsPinnedGroupStatus(groupId, statusId) {
dispatch(isPinnedGroupStatus(groupId, statusId))
},
onPin(status) {
dispatch(closePopover())
@@ -440,7 +474,8 @@ const mapDispatchToProps = (dispatch) => ({
})
StatusOptionsPopover.propTypes = {
status: ImmutablePropTypes.map.isRequired,
status: ImmutablePropTypes.map,
statusId: PropTypes.string.isRequired,
groupRelationships: ImmutablePropTypes.map,
groupId: PropTypes.string,
onQuote: PropTypes.func.isRequired,
@@ -454,6 +489,9 @@ StatusOptionsPopover.propTypes = {
onFetchGroupRelationships: PropTypes.func.isRequired,
onOpenProUpgradeModal: PropTypes.func.isRequired,
onClosePopover: PropTypes.func.isRequired,
fetchIsPinnedGroupStatus: PropTypes.func.isRequired,
fetchIsBookmark: PropTypes.func.isRequired,
fetchIsPin: PropTypes.func.isRequired,
isXS: PropTypes.bool,
isPro: PropTypes.bool,
}

View File

@@ -11,7 +11,7 @@ import { CX } from '../constants'
class StatusActionBar extends ImmutablePureComponent {
updateOnProps = ['status']
// updateOnProps = ['status']
handleShareClick = () => {
this.props.onShare(this.shareButton, this.props.status)

View File

@@ -4,7 +4,7 @@ import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component';
import { Set as ImmutableSet } from 'immutable';
import noop from 'lodash/noop';
import noop from 'lodash.noop'
import { toggleStatusReport } from '../actions/reports';
import { MediaGallery, Video } from '../features/ui/util/async_components';
import Bundle from '../features/ui/util/bundle';

View File

@@ -216,7 +216,7 @@ const mapDispatchToProps = (dispatch) => ({
onOpenStatusOptionsPopover(targetRef, status) {
dispatch(openPopover('STATUS_OPTIONS', {
targetRef,
status,
statusId: status.get('id'),
position: 'left-start',
}))
},

View File

@@ -3,7 +3,7 @@ import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { fetchGroups } from '../../actions/groups'
import { fetchGroupsByTab } from '../../actions/groups'
import GroupCollectionItem from '../group_collection_item'
import TimelineInjectionLayout from './timeline_injection_layout'
@@ -11,7 +11,7 @@ class FeaturedGroupsInjection extends ImmutablePureComponent {
componentDidMount() {
if (!this.props.isFetched) {
this.props.onFetchGroups('featured')
this.props.onFetchGroupsByTap('featured')
}
}
@@ -59,14 +59,14 @@ const mapStateToProps = (state) => ({
})
const mapDispatchToProps = (dispatch) => ({
onFetchGroups: (tab) => dispatch(fetchGroups(tab)),
onFetchGroupsByTap: (tab) => dispatch(fetchGroupsByTab(tab)),
})
FeaturedGroupsInjection.propTypes = {
groupIds: ImmutablePropTypes.list,
isFetched: PropTypes.bool.isRequired,
isLoading: PropTypes.bool.isRequired,
onFetchGroups: PropTypes.func.isRequired,
onFetchGroupsByTab: PropTypes.func.isRequired,
injectionId: PropTypes.string.isRequired,
}

View File

@@ -134,6 +134,8 @@ export const GROUP_TIMELINE_SORTING_TYPE_NEWEST = 'newest'
export const GROUP_TIMELINE_SORTING_TYPE_RECENT_ACTIVITY = 'recent'
export const GROUP_TIMELINE_SORTING_TYPE_TOP = 'top'
export const ACCEPTED_GROUP_TABS = ['new', 'featured', 'member', 'admin']
export const GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_TODAY = 'today'
export const GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_WEEKLY = 'weekly'
export const GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_MONTHLY = 'monthly'

View File

@@ -11,6 +11,7 @@ import Account from '../components/account'
import Block from '../components/block'
import BlockHeading from '../components/block_heading'
import ScrollableList from '../components/scrollable_list'
import AccountPlaceholder from '../components/placeholder/account_placeholder'
class Blocks extends ImmutablePureComponent {
@@ -40,16 +41,19 @@ class Blocks extends ImmutablePureComponent {
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
isLoading={isLoading}
showLoading={isLoading}
emptyMessage={emptyMessage}
placeholderComponent={AccountPlaceholder}
placeholderCount={3}
>
{
accountIds && accountIds.map((id) =>
accountIds && accountIds.map((id) => (
<Account
key={`blocked-accounts-${id}`}
id={id}
compact
/>
)
))
}
</ScrollableList>
</Block>

View File

@@ -4,7 +4,7 @@ import { connect } from 'react-redux'
import ImmutablePropTypes from 'react-immutable-proptypes'
import ImmutablePureComponent from 'react-immutable-pure-component'
import { injectIntl, defineMessages } from 'react-intl'
import { fetchGroups } from '../actions/groups'
import { fetchGroupsByTab } from '../actions/groups'
import { openPopover } from '../actions/popover'
import { POPOVER_GROUP_LIST_SORT_OPTIONS } from '../constants'
import Block from '../components/block'
@@ -16,12 +16,12 @@ import GroupListItem from '../components/group_list_item'
class GroupsCollection extends ImmutablePureComponent {
componentWillMount() {
this.props.onFetchGroups(this.props.activeTab)
this.props.onFetchGroupsByTab(this.props.activeTab)
}
componentDidUpdate(oldProps) {
if (this.props.activeTab && this.props.activeTab !== oldProps.activeTab) {
this.props.onFetchGroups(this.props.activeTab)
this.props.onFetchGroupsByTab(this.props.activeTab)
}
}
@@ -103,7 +103,7 @@ const mapStateToProps = (state, { activeTab }) => ({
})
const mapDispatchToProps = (dispatch) => ({
onFetchGroups: (tab) => dispatch(fetchGroups(tab)),
onFetchGroupsByTab: (tab) => dispatch(fetchGroupsByTab(tab)),
onOpenSortPopover(tab, targetRef) {
dispatch(openPopover(POPOVER_GROUP_LIST_SORT_OPTIONS, {
targetRef,

View File

@@ -11,7 +11,7 @@ import {
} from '../constants'
import { me } from '../initial_state'
import { saveShownOnboarding } from '../actions/settings'
import { fetchGroups } from '../actions/groups'
import { fetchGroupsByTab } from '../actions/groups'
import { saveUserProfileInformation } from '../actions/user'
import { makeGetAccount } from '../selectors'
import Button from '../components/button'
@@ -414,7 +414,7 @@ const mapStateToProps = (state) => ({
const mapDispatchToProps = (dispatch) => ({
onSaveShownOnboarding: () => dispatch(saveShownOnboarding()),
onFetchFeaturedGroups: () => dispatch(fetchGroups('featured')),
onFetchFeaturedGroups: () => dispatch(fetchGroupsByTab('featured')),
onSaveUserProfileInformation(data) {
dispatch(saveUserProfileInformation(data))
},

View File

@@ -4,45 +4,65 @@ import { connect } from 'react-redux'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { FormattedMessage } from 'react-intl'
import { fetchLikes } from '../actions/interactions'
import debounce from 'lodash.debounce'
import { fetchLikes, expandLikes } from '../actions/interactions'
import { fetchStatus } from '../actions/statuses'
import { makeGetStatus } from '../selectors'
import Account from '../components/account'
import ColumnIndicator from '../components/column_indicator'
import ScrollableList from '../components/scrollable_list'
import AccountPlaceholder from '../components/placeholder/account_placeholder'
class StatusLikes extends ImmutablePureComponent {
componentWillMount () {
this.props.dispatch(fetchLikes(this.props.statusId))
this.props.dispatch(fetchStatus(this.props.statusId))
}
componentWillReceiveProps(nextProps) {
if (nextProps.statusId !== this.props.statusId && nextProps.statusId) {
this.props.dispatch(fetchLikes(nextProps.statusId))
this.props.dispatch(fetchStatus(nextProps.statusId))
}
}
render () {
const { accountIds, status } = this.props
handleLoadMore = debounce(() => {
this.props.dispatch(expandLikes(this.props.statusId))
}, 300, { leading: true })
if (!accountIds) {
return <ColumnIndicator type='loading' />
} else if (!status) {
render () {
const {
accountIds,
isLoading,
hasMore,
list,
statusId,
} = this.props
if (!statusId) {
return <ColumnIndicator type='missing' />
}
const accountIdCount = !!accountIds ? accountIds.count() : 0
return (
<ScrollableList
scrollKey='likes'
emptyMessage={<FormattedMessage id='status.likes.empty' defaultMessage='No one has liked this gab yet. When someone does, they will show up here.' />}
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
isLoading={isLoading && accountIdCount === 0}
showLoading={isLoading && accountIdCount === 0}
placeholderComponent={AccountPlaceholder}
placeholderCount={3}
>
{
accountIds.map(id =>
<Account key={id} id={id} />
)
accountIdCount > 0 && accountIds.map((id) => (
<Account
compact
key={`liked-by-${id}`}
id={id}
/>
))
}
</ScrollableList>
)
@@ -52,24 +72,17 @@ class StatusLikes extends ImmutablePureComponent {
const mapStateToProps = (state, props) => {
const statusId = props.params ? props.params.statusId : props.statusId
const getStatus = makeGetStatus()
const status = getStatus(state, {
id: statusId
})
return {
status,
statusId,
accountIds: state.getIn(['user_lists', 'liked_by', statusId]),
accountIds: state.getIn(['user_lists', 'liked_by', statusId, 'items']),
hasMore: !!state.getIn(['user_lists', 'liked_by', statusId, 'next']),
isLoading: state.getIn(['user_lists', 'liked_by', statusId, 'isLoading']),
}
}
StatusLikes.propTypes = {
accountIds: ImmutablePropTypes.list,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
statusId: PropTypes.string.isRequired,
}
export default connect(mapStateToProps)(StatusLikes)
export default connect(mapStateToProps)(StatusLikes)

View File

@@ -4,45 +4,65 @@ import { connect } from 'react-redux'
import ImmutablePureComponent from 'react-immutable-pure-component'
import ImmutablePropTypes from 'react-immutable-proptypes'
import { FormattedMessage } from 'react-intl'
import { fetchReposts } from '../actions/interactions'
import debounce from 'lodash.debounce'
import { fetchReposts, expandReposts } from '../actions/interactions'
import { fetchStatus } from '../actions/statuses'
import { makeGetStatus } from '../selectors'
import Account from '../components/account'
import ColumnIndicator from '../components/column_indicator'
import ScrollableList from '../components/scrollable_list'
import AccountPlaceholder from '../components/placeholder/account_placeholder'
class StatusReposts extends ImmutablePureComponent {
componentWillMount () {
this.props.dispatch(fetchReposts(this.props.statusId))
this.props.dispatch(fetchStatus(this.props.statusId))
}
componentWillReceiveProps(nextProps) {
if (nextProps.statusId !== this.props.statusId && nextProps.statusId) {
this.props.dispatch(fetchReposts(nextProps.statusId))
this.props.dispatch(fetchStatus(nextProps.statusId))
}
}
render () {
const { accountIds, status } = this.props
handleLoadMore = debounce(() => {
this.props.dispatch(expandReposts(this.props.statusId))
}, 300, { leading: true })
if (!accountIds) {
return <ColumnIndicator type='loading' />
} else if (!status) {
render () {
const {
accountIds,
isLoading,
hasMore,
list,
statusId,
} = this.props
if (!statusId) {
return <ColumnIndicator type='missing' />
}
const accountIdCount = !!accountIds ? accountIds.count() : 0
return (
<ScrollableList
scrollKey='reposts'
emptyMessage={<FormattedMessage id='status.reposts.empty' defaultMessage='No one has reposted this gab yet. When someone does, they will show up here.' />}
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
isLoading={isLoading && accountIdCount === 0}
showLoading={isLoading && accountIdCount === 0}
placeholderComponent={AccountPlaceholder}
placeholderCount={3}
>
{
accountIds.map(id =>
<Account key={id} id={id} />
)
accountIdCount > 0 && accountIds.map((id) => (
<Account
compact
key={`reposted-by-${id}`}
id={id}
/>
))
}
</ScrollableList>
)
@@ -52,25 +72,17 @@ class StatusReposts extends ImmutablePureComponent {
const mapStateToProps = (state, props) => {
const statusId = props.params ? props.params.statusId : props.statusId
const getStatus = makeGetStatus()
const status = getStatus(state, {
id: statusId,
// username: props.params.username,
})
return {
status,
statusId,
accountIds: state.getIn(['user_lists', 'reblogged_by', statusId]),
accountIds: state.getIn(['user_lists', 'reblogged_by', statusId, 'items']),
hasMore: !!state.getIn(['user_lists', 'reblogged_by', statusId, 'next']),
isLoading: state.getIn(['user_lists', 'reblogged_by', statusId, 'isLoading']),
}
}
StatusReposts.propTypes = {
accountIds: ImmutablePropTypes.list,
dispatch: PropTypes.func.isRequired,
status: ImmutablePropTypes.map,
statusId: PropTypes.string.isRequired,
}
export default connect(mapStateToProps)(StatusReposts)
export default connect(mapStateToProps)(StatusReposts)

View File

@@ -55,7 +55,6 @@ import {
AccountTimeline,
Assets,
BlockedAccounts,
BlockedDomains,
BookmarkedStatuses,
CommunityTimeline,
Compose,

View File

@@ -77,6 +77,7 @@ export function ListTimelineSettingsModal() { return import(/* webpackChunkName:
export function MediaGallery() { return import(/* webpackChunkName: "components/media_gallery" */'../../../components/media_gallery') }
export function MediaGalleryPanel() { return import(/* webpackChunkName: "components/media_gallery_panel" */'../../../components/panel/media_gallery_panel') }
export function MediaModal() { return import(/* webpackChunkName: "components/media_modal" */'../../../components/modal/media_modal') }
export function Messages() { return import(/* webpackChunkName: "features/messages" */'../../messages') }
export function Mutes() { return import(/* webpackChunkName: "features/mutes" */'../../mutes') }
export function MuteModal() { return import(/* webpackChunkName: "modals/mute_modal" */'../../../components/modal/mute_modal') }
export function NavSettingsPopover() { return import(/* webpackChunkName: "modals/nav_settings_popover" */'../../../components/popover/nav_settings_popover') }

View File

@@ -1,6 +1,7 @@
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import noop from 'lodash.noop'
import {
fetchBundleRequest,
fetchBundleSuccess,
@@ -8,7 +9,6 @@ import {
} from '../../../actions/bundles'
const emptyComponent = () => null
const noop = () => { }
class Bundle extends React.PureComponent {

View File

@@ -4,6 +4,7 @@ const defaultFailSuffix = 'FAIL';
export default function errorsMiddleware() {
return ({ dispatch }) => next => action => {
// : todo : use skipAlert!
if (action.type && !action.skipAlert) {
const isFail = new RegExp(`${defaultFailSuffix}$`, 'g');

View File

@@ -6,7 +6,7 @@ export default function loadingBarMiddleware(config = {}) {
const promiseTypeSuffixes = config.promiseTypeSuffixes || defaultTypeSuffixes
return ({ dispatch }) => next => (action) => {
if (action.type && !action.skipLoading) {
if (action.type && action.type.indexOf('TIMELINE') > -1) {
const [PENDING, FULFILLED, REJECTED] = promiseTypeSuffixes
const isPending = new RegExp(`${PENDING}$`, 'g')

View File

@@ -18,7 +18,7 @@ class MessagesPage extends React.PureComponent {
title={title}
>
<PageTitle path={title} />
{children}
</MessagesLayout>
)
}

View File

@@ -0,0 +1,43 @@
import {
MESSAGE_INPUT_CHANGE,
MESSAGE_INPUT_RESET,
MESSAGE_SEND_REQUEST,
MESSAGE_SEND_SUCCESS,
MESSAGE_SEND_FAIL,
MESSAGE_DELETE_REQUEST,
MESSAGE_DELETE_SUCCESS,
MESSAGE_DELETE_FAIL,
} from '../actions/lists'
import { Map as ImmutableMap, fromJS } from 'immutable'
const initialState = ImmutableMap({
text: '',
conversationId: null,
idempotencyKey: null,
})
const normalizeList = (state, list) => state.set(list.id, fromJS(list))
const normalizeLists = (state, lists) => {
lists.forEach(list => {
state = normalizeList(state, list)
})
return state
}
export default function lists(state = initialState, action) {
switch(action.type) {
case LIST_FETCH_SUCCESS:
case LIST_CREATE_SUCCESS:
case LIST_UPDATE_SUCCESS:
return normalizeList(state, action.list);
case LISTS_FETCH_SUCCESS:
return normalizeLists(state, action.lists);
case LIST_DELETE_SUCCESS:
case LIST_FETCH_FAIL:
return state.set(action.id, false);
default:
return state;
}
}

View File

@@ -0,0 +1,84 @@
import {
REPOST_REQUEST,
UNREPOST_REQUEST,
REPOST_FAIL,
FAVORITE_REQUEST,
FAVORITE_FAIL,
UNFAVORITE_REQUEST,
} from '../actions/interactions';
import {
STATUS_REVEAL,
STATUS_HIDE,
UPDATE_STATUS_STATS,
} from '../actions/statuses';
import { TIMELINE_DELETE } from '../actions/timelines';
import { STATUS_IMPORT, STATUSES_IMPORT } from '../actions/importer';
import { Map as ImmutableMap, fromJS } from 'immutable';
const importStatus = (state, status) => state.set(status.id, fromJS(status));
const importStatuses = (state, statuses) =>
state.withMutations(mutable => statuses.forEach(status => importStatus(mutable, status)));
const deleteStatus = (state, id, references) => {
references.forEach(ref => {
state = deleteStatus(state, ref[0], []);
});
return state.delete(id);
};
const initialState = ImmutableMap();
export default function statuses(state = initialState, action) {
switch(action.type) {
case STATUS_IMPORT:
return importStatus(state, action.status);
case STATUSES_IMPORT:
return importStatuses(state, action.statuses);
case FAVORITE_REQUEST:
return state.setIn([action.status.get('id'), 'favourited'], true);
case FAVORITE_FAIL:
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'favourited'], false);
case UNFAVORITE_REQUEST:
return state.setIn([action.status.get('id'), 'favourited'], false);
case REPOST_REQUEST:
return state.setIn([action.status.get('id'), 'reblogged'], true);
case UNREPOST_REQUEST:
return state.setIn([action.status.get('id'), 'reblogged'], false);
case REPOST_FAIL:
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false);
case STATUS_REVEAL:
return state.withMutations((map) => {
action.ids.forEach(id => {
if (!(state.get(id) === undefined)) {
map.setIn([id, 'hidden'], false);
}
});
});
case STATUS_HIDE:
return state.withMutations((map) => {
action.ids.forEach(id => {
if (!(state.get(id) === undefined)) {
map.setIn([id, 'hidden'], true);
}
});
});
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references);
case UPDATE_STATUS_STATS:
const { status_id } = action.data
return state.withMutations((map) => {
if (action.data.favourited !== undefined) map.setIn([status_id, 'favourited'], action.data.favourited)
if (action.data.favourites_count !== undefined) map.setIn([status_id, 'favourites_count'], action.data.favourites_count)
if (action.data.reblogged !== undefined) map.setIn([status_id, 'reblogged'], action.data.reblogged)
if (action.data.reblogs_count !== undefined) map.setIn([status_id, 'reblogs_count'], action.data.reblogs_count)
if (action.data.replies_count !== undefined) map.setIn([status_id, 'replies_count'], action.data.replies_count)
if (action.data.pinned !== undefined) map.setIn([status_id, 'pinned'], action.data.pinned)
if (action.data.pinned_by_group !== undefined) map.setIn([status_id, 'pinned_by_group'], action.data.pinned_by_group)
if (action.data.bookmarked !== undefined) map.setIn([status_id, 'bookmarked'], action.data.bookmarked)
})
default:
return state;
}
};

View File

@@ -21,11 +21,10 @@ import {
GROUP_TIMELINE_SORTING_TYPE_TOP,
GROUP_TIMELINE_SORTING_TYPE_NEWEST,
GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_TODAY,
ACCEPTED_GROUP_TABS,
} from '../constants'
import slugify from '../utils/slugify'
const tabs = ['new', 'featured', 'member', 'admin']
const initialState = ImmutableMap({
sortByValue: GROUP_TIMELINE_SORTING_TYPE_NEWEST,
sortByTopValue: '',

View File

@@ -4,11 +4,11 @@ import { Map as ImmutableMap, fromJS } from 'immutable';
const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship));
const normalizeRelationships = (state, relationships) => {
relationships.forEach(relationship => {
state = normalizeRelationship(state, relationship);
});
relationships.forEach(relationship => {
state = normalizeRelationship(state, relationship);
});
return state;
return state
};
const initialState = ImmutableMap();

View File

@@ -30,7 +30,7 @@ export default function groups(state = initialState, action) {
case GROUPS_FETCH_SUCCESS:
return normalizeGroups(state, action.groups)
case GROUP_FETCH_FAIL:
return state.set(action.id, false)
return state.set(action.groupId, false)
default:
return state
}

View File

@@ -2,6 +2,9 @@ import { combineReducers } from 'redux-immutable'
import { loadingBarReducer } from 'react-redux-loading-bar'
import accounts from './accounts'
import accounts_counters from './accounts_counters'
import chat_compose from './chat_compose'
import chat_conversations from './chat_conversations'
import chat_messages from './chat_messages'
import compose from './compose'
import contexts from './contexts'
import custom_emojis from './custom_emojis'
@@ -46,6 +49,8 @@ import user_lists from './user_lists'
const reducers = {
accounts,
accounts_counters,
chat_conversations,
chat_messages,
compose,
contexts,
custom_emojis,

View File

@@ -1,6 +1,6 @@
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
import {
STATUS_REVISIONS_LOAD,
STATUS_REVISIONS_LOAD_REQUEST,
STATUS_REVISIONS_LOAD_SUCCESS,
STATUS_REVISIONS_LOAD_FAIL
} from '../actions/status_revisions'
@@ -13,15 +13,17 @@ const initialState = ImmutableMap({
export default function statusRevisions(state = initialState, action) {
switch (action.type) {
case STATUS_REVISIONS_LOAD:
return initialState
case STATUS_REVISIONS_LOAD_REQUEST:
return state.withMutations((mutable) => {
mutable.set('loading', true)
})
case STATUS_REVISIONS_LOAD_SUCCESS:
return state.withMutations(mutable => {
return state.withMutations((mutable) => {
mutable.set('loading', false)
mutable.set('revisions', fromJS(action.revisions).reverse())
})
case STATUS_REVISIONS_LOAD_FAIL:
return state.withMutations(mutable => {
return state.withMutations((mutable) => {
mutable.set('loading', false)
mutable.set('error', action.error)
})

View File

@@ -1,5 +1,6 @@
import {
REPOST_REQUEST,
UNREPOST_REQUEST,
REPOST_FAIL,
FAVORITE_REQUEST,
FAVORITE_FAIL,
@@ -43,10 +44,12 @@ export default function statuses(state = initialState, action) {
return state.setIn([action.status.get('id'), 'favourited'], false);
case REPOST_REQUEST:
return state.setIn([action.status.get('id'), 'reblogged'], true);
case UNREPOST_REQUEST:
return state.setIn([action.status.get('id'), 'reblogged'], false);
case REPOST_FAIL:
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false);
case STATUS_REVEAL:
return state.withMutations(map => {
return state.withMutations((map) => {
action.ids.forEach(id => {
if (!(state.get(id) === undefined)) {
map.setIn([id, 'hidden'], false);
@@ -54,7 +57,7 @@ export default function statuses(state = initialState, action) {
});
});
case STATUS_HIDE:
return state.withMutations(map => {
return state.withMutations((map) => {
action.ids.forEach(id => {
if (!(state.get(id) === undefined)) {
map.setIn([id, 'hidden'], true);
@@ -64,8 +67,17 @@ export default function statuses(state = initialState, action) {
case TIMELINE_DELETE:
return deleteStatus(state, action.id, action.references);
case UPDATE_STATUS_STATS:
// : todo :
return state;
const { status_id } = action.data
return state.withMutations((map) => {
if (action.data.favourited !== undefined) map.setIn([status_id, 'favourited'], action.data.favourited)
if (action.data.favourites_count !== undefined) map.setIn([status_id, 'favourites_count'], action.data.favourites_count)
if (action.data.reblogged !== undefined) map.setIn([status_id, 'reblogged'], action.data.reblogged)
if (action.data.reblogs_count !== undefined) map.setIn([status_id, 'reblogs_count'], action.data.reblogs_count)
if (action.data.replies_count !== undefined) map.setIn([status_id, 'replies_count'], action.data.replies_count)
if (action.data.pinned !== undefined) map.setIn([status_id, 'pinned'], action.data.pinned)
if (action.data.pinned_by_group !== undefined) map.setIn([status_id, 'pinned_by_group'], action.data.pinned_by_group)
if (action.data.bookmarked !== undefined) map.setIn([status_id, 'bookmarked'], action.data.bookmarked)
})
default:
return state;
}

View File

@@ -1,3 +1,5 @@
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
import compareId from '../utils/compare_id'
import {
TIMELINE_UPDATE,
TIMELINE_DELETE,
@@ -11,17 +13,19 @@ import {
TIMELINE_DEQUEUE,
MAX_QUEUED_ITEMS,
TIMELINE_SCROLL_TOP,
} from '../actions/timelines';
} from '../actions/timelines'
import {
ACCOUNT_BLOCK_SUCCESS,
ACCOUNT_MUTE_SUCCESS,
ACCOUNT_UNFOLLOW_SUCCESS,
} from '../actions/accounts';
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
import compareId from '../utils/compare_id';
import { GROUP_REMOVE_STATUS_SUCCESS } from '../actions/groups';
} from '../actions/accounts'
import {
GROUP_REMOVE_STATUS_SUCCESS,
GROUP_UNPIN_STATUS_SUCCESS,
} from '../actions/groups'
import { UNPIN_SUCCESS } from '../actions/interactions'
const initialState = ImmutableMap();
const initialState = ImmutableMap()
const initialTimeline = ImmutableMap({
unread: 0,
@@ -33,14 +37,14 @@ const initialTimeline = ImmutableMap({
items: ImmutableList(),
queuedItems: ImmutableList(), //max= MAX_QUEUED_ITEMS
totalQueuedItemsCount: 0, //used for queuedItems overflow for MAX_QUEUED_ITEMS+
});
})
const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent) => {
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
mMap.set('isLoading', false);
mMap.set('isPartial', isPartial);
return state.update(timeline, initialTimeline, map => map.withMutations((mMap) => {
mMap.set('isLoading', false)
mMap.set('isPartial', isPartial)
if (!next && !isLoadingRecent) mMap.set('hasMore', false);
if (!next && !isLoadingRecent) mMap.set('hasMore', false)
if (!statuses.isEmpty()) {
mMap.update('items', ImmutableList(), oldIds => {
@@ -160,8 +164,16 @@ const filterTimeline = (timeline, state, relationship, statuses) =>
));
const removeStatusFromGroup = (state, groupId, statusId) => {
return state.updateIn([`group:${groupId}`, 'items'], list => list.filterNot(item => item === statusId));
};
return state.updateIn([`group:${groupId}`, 'items'], list => list.filterNot(item => item === statusId))
}
const removeStatusFromGroupPins = (state, groupId, statusId) => {
return state.updateIn([`group:${groupId}:pinned`, 'items'], list => list.filterNot(item => item === statusId))
}
const removeStatusFromAccountPins = (state, accountId, statusId) => {
return state.updateIn([`account:${accountId}:pinned`, 'items'], list => list.filterNot(item => item === statusId))
}
export default function timelines(state = initialState, action) {
switch(action.type) {
@@ -201,10 +213,14 @@ export default function timelines(state = initialState, action) {
action.timeline,
initialTimeline,
map => map.set('online', false).update('items', items => items.first() ? items.unshift(null) : items)
);
)
case UNPIN_SUCCESS:
return removeStatusFromAccountPins(state, action.accountId, action.status.get('id'))
case GROUP_REMOVE_STATUS_SUCCESS:
return removeStatusFromGroup(state, action.groupId, action.id)
return removeStatusFromGroup(state, action.groupId, action.statusId)
case GROUP_UNPIN_STATUS_SUCCESS:
return removeStatusFromGroupPins(state, action.groupId, action.statusId)
default:
return state;
return state
}
};
}

View File

@@ -23,8 +23,18 @@ import {
FOLLOW_REQUEST_REJECT_SUCCESS,
} from '../actions/accounts'
import {
REPOSTS_FETCH_REQUEST,
REPOSTS_FETCH_SUCCESS,
REPOSTS_FETCH_FAIL,
REPOSTS_EXPAND_REQUEST,
REPOSTS_EXPAND_SUCCESS,
REPOSTS_EXPAND_FAIL,
LIKES_FETCH_REQUEST,
LIKES_FETCH_SUCCESS,
LIKES_FETCH_FAIL,
LIKES_EXPAND_REQUEST,
LIKES_EXPAND_SUCCESS,
LIKES_EXPAND_FAIL,
} from '../actions/interactions'
import {
BLOCKS_FETCH_REQUEST,
@@ -52,6 +62,7 @@ import {
GROUP_JOIN_REQUESTS_EXPAND_SUCCESS,
GROUP_JOIN_REQUESTS_APPROVE_SUCCESS,
GROUP_JOIN_REQUESTS_REJECT_SUCCESS,
GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS,
} from '../actions/groups'
const initialState = ImmutableMap({
@@ -119,12 +130,28 @@ export default function userLists(state = initialState, action) {
case FOLLOWING_EXPAND_FAIL:
return state.setIn(['following', action.id, 'isLoading'], false);
case REPOSTS_FETCH_REQUEST:
case REPOSTS_EXPAND_REQUEST:
return state.setIn(['reblogged_by', action.statusId, 'isLoading'], true)
case REPOSTS_FETCH_SUCCESS:
return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
return normalizeList(state, 'reblogged_by', action.statusId, action.accounts, action.next)
case REPOSTS_EXPAND_SUCCESS:
return appendToList(state, 'reblogged_by', action.statusId, action.accounts, action.next)
case REPOSTS_FETCH_FAIL:
case REPOSTS_EXPAND_FAIL:
return setListFailed(state, 'reblogged_by', action.statusId)
case LIKES_FETCH_REQUEST:
case LIKES_EXPAND_REQUEST:
return state.setIn(['liked_by', action.statusId, 'isLoading'], true)
case LIKES_FETCH_SUCCESS:
return state.setIn(['liked_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
return normalizeList(state, 'liked_by', action.statusId, action.accounts, action.next)
case LIKES_EXPAND_SUCCESS:
return appendToList(state, 'liked_by', action.statusId, action.accounts, action.next)
case LIKES_FETCH_FAIL:
case LIKES_EXPAND_FAIL:
return setListFailed(state, 'liked_by', action.statusId)
case FOLLOW_REQUESTS_FETCH_SUCCESS:
return normalizeList(state, 'follow_requests', me, action.accounts, action.next);
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
@@ -162,21 +189,24 @@ export default function userLists(state = initialState, action) {
return setListFailed(state, 'mutes', me)
case GROUP_MEMBERS_FETCH_SUCCESS:
return normalizeList(state, 'groups', action.id, action.accounts, action.next);
return normalizeList(state, 'groups', action.groupId, action.accounts, action.next);
case GROUP_MEMBERS_EXPAND_SUCCESS:
return appendToList(state, 'groups', action.id, action.accounts, action.next);
return appendToList(state, 'groups', action.groupId, action.accounts, action.next);
case GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS:
return state.updateIn(['groups', action.groupId, 'items'], list => list.filterNot(item => item === action.accountId));
case GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS:
return normalizeList(state, 'group_removed_accounts', action.id, action.accounts, action.next);
return normalizeList(state, 'group_removed_accounts', action.groupId, action.accounts, action.next);
case GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS:
return appendToList(state, 'group_removed_accounts', action.id, action.accounts, action.next);
return appendToList(state, 'group_removed_accounts', action.groupId, action.accounts, action.next);
case GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS:
return state.updateIn(['group_removed_accounts', action.groupId, 'items'], list => list.filterNot(item => item === action.id));
return state.updateIn(['group_removed_accounts', action.groupId, 'items'], list => list.filterNot(item => item === action.accountId));
case GROUP_JOIN_REQUESTS_FETCH_SUCCESS:
return normalizeList(state, 'group_join_requests', action.id, action.accounts, action.next);
return normalizeList(state, 'group_join_requests', action.groupId, action.accounts, action.next);
case GROUP_JOIN_REQUESTS_EXPAND_SUCCESS:
return appendToList(state, 'group_join_requests', action.id, action.accounts, action.next);
return appendToList(state, 'group_join_requests', action.groupId, action.accounts, action.next);
case GROUP_JOIN_REQUESTS_APPROVE_SUCCESS:
case GROUP_JOIN_REQUESTS_REJECT_SUCCESS:
return state.updateIn(['group_join_requests', action.groupId, 'items'], list => list.filterNot(item => item === action.accountId));

View File

@@ -124,7 +124,7 @@ export const makeGetStatus = () => {
}
console.log("group:", group)
// console.log("group:", group)
//Find ancestor status