diff --git a/app/controllers/api/base_controller.rb b/app/controllers/api/base_controller.rb index 260909cc..b65ba69f 100644 --- a/app/controllers/api/base_controller.rb +++ b/app/controllers/api/base_controller.rb @@ -1,8 +1,8 @@ # frozen_string_literal: true class Api::BaseController < ApplicationController - DEFAULT_STATUSES_LIMIT = 18 - DEFAULT_ACCOUNTS_LIMIT = 40 + DEFAULT_STATUSES_LIMIT = 20 + DEFAULT_ACCOUNTS_LIMIT = 20 include RateLimitHeaders @@ -82,8 +82,8 @@ class Api::BaseController < ApplicationController end end - def render_empty - render json: {}, status: 200 + def render_empty_success(message = nil) + render json: { success: true, error: false, message: message }, status: 200 end def authorize_if_got_token!(*scopes) diff --git a/app/controllers/api/v1/filters_controller.rb b/app/controllers/api/v1/filters_controller.rb index b0ace3af..e1806af3 100644 --- a/app/controllers/api/v1/filters_controller.rb +++ b/app/controllers/api/v1/filters_controller.rb @@ -27,7 +27,7 @@ class Api::V1::FiltersController < Api::BaseController def destroy @filter.destroy! - render_empty + render_empty_success end private diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index e6888154..665a1427 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -14,12 +14,12 @@ class Api::V1::FollowRequestsController < Api::BaseController def authorize AuthorizeFollowService.new.call(account, current_account) NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account)) - render_empty + render_empty_success end def reject RejectFollowService.new.call(account, current_account) - render_empty + render_empty_success end private diff --git a/app/controllers/api/v1/groups/accounts_controller.rb b/app/controllers/api/v1/groups/accounts_controller.rb index 0ca8795d..ba8a5611 100644 --- a/app/controllers/api/v1/groups/accounts_controller.rb +++ b/app/controllers/api/v1/groups/accounts_controller.rb @@ -19,21 +19,18 @@ class Api::V1::Groups::AccountsController < Api::BaseController def create authorize @group, :join? - if !@group.password.nil? + if @group.has_password? + # use the groups/password_controller to join group with password render json: { error: true, message: 'Unable to join group. Incorrect password.' }, status: 422 - end - - if @group.is_private - @group.join_requests << current_account else - @group.accounts << current_account - - if current_user.allows_group_in_home_feed? - current_user.force_regeneration! + if @group.is_private + @group.join_requests << current_account + else + @group.accounts << current_account end - end - render json: @group, serializer: REST::GroupRelationshipSerializer, relationships: relationships + render json: @group, serializer: REST::GroupRelationshipSerializer, relationships: relationships + end end def update @@ -41,7 +38,7 @@ class Api::V1::Groups::AccountsController < Api::BaseController @account = @group.accounts.find(params[:account_id]) GroupAccount.where(group: @group, account: @account).update(group_account_params) - render_empty + render_empty_success end def destroy @@ -51,9 +48,6 @@ class Api::V1::Groups::AccountsController < Api::BaseController else authorize @group, :leave? GroupAccount.where(group: @group, account_id: current_account.id).destroy_all - if current_user.allows_group_in_home_feed? - current_user.force_regeneration! - end end render json: @group, serializer: REST::GroupRelationshipSerializer, relationships: relationships diff --git a/app/controllers/api/v1/groups/pins_controller.rb b/app/controllers/api/v1/groups/pins_controller.rb index 051897f3..349453c1 100644 --- a/app/controllers/api/v1/groups/pins_controller.rb +++ b/app/controllers/api/v1/groups/pins_controller.rb @@ -10,25 +10,29 @@ class Api::V1::Groups::PinsController < Api::BaseController def create authorize @group, :update? - - GroupPinnedStatus.create!(group: @group, status: @status) - render json: @status, serializer: REST::StatusSerializer + + pin = GroupPinnedStatus.find_by(group: @group, status: @status) + if pin.nil? + GroupPinnedStatus.create!(group: @group, status: @status) + render json: @status, serializer: REST::StatusGroupPinnedSerializer, group_id: @group.id + else + return render json: { error: 'Status is already pinned to group' }, status: 500 + end end def show - # is status pinned by user of group? + render json: @status, serializer: REST::StatusGroupPinnedSerializer, group_id: @group.id end def destroy authorize @group, :update? pin = GroupPinnedStatus.find_by(group: @group, status: @status) - if pin pin.destroy! end - render json: @status, serializer: REST::StatusSerializer + render json: @status, serializer: REST::StatusGroupPinnedSerializer, group_id: @group.id end private diff --git a/app/controllers/api/v1/groups/removed_accounts_controller.rb b/app/controllers/api/v1/groups/removed_accounts_controller.rb index d6cba234..be26db5e 100644 --- a/app/controllers/api/v1/groups/removed_accounts_controller.rb +++ b/app/controllers/api/v1/groups/removed_accounts_controller.rb @@ -23,7 +23,7 @@ class Api::V1::Groups::RemovedAccountsController < Api::BaseController @account = @group.accounts.find(params[:account_id]) @group.removed_accounts << @account GroupAccount.where(group: @group, account: @account).destroy_all - render_empty + render_empty_success end def destroy @@ -31,7 +31,7 @@ class Api::V1::Groups::RemovedAccountsController < Api::BaseController @account = @group.removed_accounts.find(params[:account_id]) GroupRemovedAccount.where(group: @group, account: @account).destroy_all - render_empty + render_empty_success end private diff --git a/app/controllers/api/v1/groups_controller.rb b/app/controllers/api/v1/groups_controller.rb index bd8261bb..c56de41a 100644 --- a/app/controllers/api/v1/groups_controller.rb +++ b/app/controllers/api/v1/groups_controller.rb @@ -16,12 +16,12 @@ class Api::V1::GroupsController < Api::BaseController @groups = Group.where(id: @groupIds).limit(150).all when 'new' if !current_user - render json: { error: 'This method requires an authenticated user' }, status: 422 + return render json: { error: 'This method requires an authenticated user' }, status: 422 end @groups = Group.where(is_archived: false).limit(24).order('created_at DESC').all when 'member' if !current_user - render json: { error: 'This method requires an authenticated user' }, status: 422 + return render json: { error: 'This method requires an authenticated user' }, status: 422 end @groups = Group.joins(:group_accounts).where(is_archived: false, group_accounts: { account: current_account }).order('group_accounts.id DESC').all when 'admin' @@ -36,7 +36,7 @@ class Api::V1::GroupsController < Api::BaseController def by_category if !current_user - render json: { error: 'This method requires an authenticated user' }, status: 422 + return render json: { error: 'This method requires an authenticated user' }, status: 422 end @groupCategory = nil @@ -54,7 +54,7 @@ class Api::V1::GroupsController < Api::BaseController def by_tag if !current_user - render json: { error: 'This method requires an authenticated user' }, status: 422 + return render json: { error: 'This method requires an authenticated user' }, status: 422 end @groups = [] @@ -94,7 +94,7 @@ class Api::V1::GroupsController < Api::BaseController @group.is_archived = true @group.save! - render_empty + render_empty_success end def destroy_status @@ -102,7 +102,7 @@ class Api::V1::GroupsController < Api::BaseController status = Status.find(params[:status_id]) GroupUnlinkStatusService.new.call(current_account, @group, status) - render_empty + render_empty_success end def approve_status @@ -110,7 +110,7 @@ class Api::V1::GroupsController < Api::BaseController status = Status.find(params[:status_id]) GroupApproveStatusService.new.call(current_account, @group, status) - render_empty + render_empty_success end private diff --git a/app/controllers/api/v1/lists/accounts_controller.rb b/app/controllers/api/v1/lists/accounts_controller.rb index 23078263..d6a282ff 100644 --- a/app/controllers/api/v1/lists/accounts_controller.rb +++ b/app/controllers/api/v1/lists/accounts_controller.rb @@ -21,12 +21,12 @@ class Api::V1::Lists::AccountsController < Api::BaseController end end - render_empty + render_empty_success end def destroy ListAccount.where(list: @list, account_id: account_ids).destroy_all - render_empty + render_empty_success end private diff --git a/app/controllers/api/v1/lists_controller.rb b/app/controllers/api/v1/lists_controller.rb index 054172be..1a7ad326 100644 --- a/app/controllers/api/v1/lists_controller.rb +++ b/app/controllers/api/v1/lists_controller.rb @@ -28,7 +28,7 @@ class Api::V1::ListsController < Api::BaseController def destroy @list.destroy! - render_empty + render_empty_success end private diff --git a/app/controllers/api/v1/messages/chats_controller.rb b/app/controllers/api/v1/messages/chats_controller.rb new file mode 100644 index 00000000..e69de29b diff --git a/app/controllers/api/v1/messages/converations_controller.rb b/app/controllers/api/v1/messages/converations_controller.rb new file mode 100644 index 00000000..e69de29b diff --git a/app/controllers/api/v1/messages_controller.rb b/app/controllers/api/v1/messages_controller.rb new file mode 100644 index 00000000..207a991d --- /dev/null +++ b/app/controllers/api/v1/messages_controller.rb @@ -0,0 +1,59 @@ +# frozen_string_literal: true + +class Api::V1::AccountsController < Api::BaseController + before_action -> { authorize_if_got_token! :read, :'read:accounts' }, except: [:create, :follow, :unfollow, :block, :unblock, :mute, :unmute] + before_action -> { doorkeeper_authorize! :follow, :'write:follows' }, only: [:follow, :unfollow] + before_action -> { doorkeeper_authorize! :follow, :'write:mutes' }, only: [:mute, :unmute] + before_action -> { doorkeeper_authorize! :follow, :'write:blocks' }, only: [:block, :unblock] + before_action -> { doorkeeper_authorize! :write, :'write:accounts' }, only: [:create] + + before_action :require_user!, except: [:show, :create] + before_action :set_account, except: [:create] + before_action :check_account_suspension, only: [:show] + + def show + # + end + + def create + # + end + + def block + BlockMessengerService.new.call(current_user.account, @account) + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships + end + + def mute + MuteMessengerService.new.call(current_user.account, @account, notifications: truthy_param?(:notifications)) + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships + end + + def unblock + UnblockMessengerService.new.call(current_user.account, @account) + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships + end + + def unmute + UnmuteMessegerService.new.call(current_user.account, @account) + render json: @account, serializer: REST::RelationshipSerializer, relationships: relationships + end + + private + + def set_account + @account = Account.find(params[:id]) + end + + def relationships(**options) + AccountRelationshipsPresenter.new([@account.id], current_user.account_id, options) + end + + def check_account_suspension + gone if @account.suspended? + end + + def account_params + params.permit(:username, :email, :password, :agreement, :locale) + end +end diff --git a/app/controllers/api/v1/notifications_controller.rb b/app/controllers/api/v1/notifications_controller.rb index 727f693b..f2e01cf5 100644 --- a/app/controllers/api/v1/notifications_controller.rb +++ b/app/controllers/api/v1/notifications_controller.rb @@ -21,12 +21,12 @@ class Api::V1::NotificationsController < Api::BaseController def clear current_account.notifications.delete_all - render_empty + render_empty_success end def mark_read current_account.notifications.find(params[:id]).mark_read! - render_empty + render_empty_success end private diff --git a/app/controllers/api/v1/scheduled_statuses_controller.rb b/app/controllers/api/v1/scheduled_statuses_controller.rb index 9950296f..3c102848 100644 --- a/app/controllers/api/v1/scheduled_statuses_controller.rb +++ b/app/controllers/api/v1/scheduled_statuses_controller.rb @@ -26,7 +26,7 @@ class Api::V1::ScheduledStatusesController < Api::BaseController def destroy @status.destroy! - render_empty + render_empty_success end private diff --git a/app/controllers/api/v1/statuses/bookmarks_controller.rb b/app/controllers/api/v1/statuses/bookmarks_controller.rb index 8a5625ed..7a3bcadc 100644 --- a/app/controllers/api/v1/statuses/bookmarks_controller.rb +++ b/app/controllers/api/v1/statuses/bookmarks_controller.rb @@ -9,14 +9,15 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController def create if current_user.account.is_pro @status = bookmarked_status - render json: @status, serializer: REST::StatusSerializer + render json: @status, serializer: REST::StatusBookmarkedSerializer else render json: { error: 'You need to be a GabPRO member to access this' }, status: 422 end end def show - # is status bookmarked by user? + @status = requested_status + render json: @status, serializer: REST::StatusBookmarkedSerializer end def destroy @@ -27,7 +28,7 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController bookmark = StatusBookmark.find_by!(account: current_user.account, status: @status) bookmark.destroy! - render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, bookmarks_map: @bookmarks_map) + render json: @status, serializer: REST::StatusBookmarkedSerializer else render json: { error: 'You need to be a GabPRO member to access this' }, status: 422 end diff --git a/app/controllers/api/v1/statuses/favourites_controller.rb b/app/controllers/api/v1/statuses/favourites_controller.rb index 4f52942f..96582d6d 100644 --- a/app/controllers/api/v1/statuses/favourites_controller.rb +++ b/app/controllers/api/v1/statuses/favourites_controller.rb @@ -8,7 +8,6 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController def create @status = favourited_status - puts "tilly -- status: " + @status.inspect render json: @status, serializer: REST::StatusStatSerializer end @@ -18,7 +17,10 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController UnfavouriteWorker.perform_async(current_user.account_id, @status.id) - render json: @status, serializer: REST::StatusStatSerializer, unfavourite: true, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, favourites_map: @favourites_map) + render json: @status, + serializer: REST::StatusStatSerializer, + unfavourite: true, + relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, favourites_map: @favourites_map) end private diff --git a/app/controllers/api/v1/statuses/pins_controller.rb b/app/controllers/api/v1/statuses/pins_controller.rb index fba194da..c3c4784a 100644 --- a/app/controllers/api/v1/statuses/pins_controller.rb +++ b/app/controllers/api/v1/statuses/pins_controller.rb @@ -8,12 +8,17 @@ class Api::V1::Statuses::PinsController < Api::BaseController before_action :set_status def create - StatusPin.create!(account: current_account, status: @status) - render json: @status, serializer: REST::StatusSerializer + pin = StatusPin.find_by(account: current_account, status: @status) + if pin.nil? + StatusPin.create!(account: current_account, status: @status) + render json: @status, serializer: REST::StatusPinnedSerializer + else + return render json: { error: 'Status is already pinned' }, status: 500 + end end def show - # is status pinned by user? + render json: @status, serializer: REST::StatusPinnedSerializer end def destroy @@ -23,7 +28,7 @@ class Api::V1::Statuses::PinsController < Api::BaseController pin.destroy! end - render json: @status, serializer: REST::StatusSerializer + render json: @status, serializer: REST::StatusPinnedSerializer end private diff --git a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb index f897650d..c69839ae 100644 --- a/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb +++ b/app/controllers/api/v1/statuses/reblogged_by_accounts_controller.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class Api::V1::Statuses::RepostedByAccountsController < Api::BaseController +class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController include Authorization before_action -> { authorize_if_got_token! :read, :'read:accounts' } @@ -36,13 +36,13 @@ class Api::V1::Statuses::RepostedByAccountsController < Api::BaseController def next_path if records_continue? - api_v1_status_reposted_by_index_url pagination_params(max_id: pagination_max_id) + api_v1_status_reblogged_by_index_url pagination_params(max_id: pagination_max_id) end end def prev_path unless @accounts.empty? - api_v1_status_reposted_by_index_url pagination_params(since_id: pagination_since_id) + api_v1_status_reblogged_by_index_url pagination_params(since_id: pagination_since_id) end end diff --git a/app/controllers/api/v1/statuses/reblogs_controller.rb b/app/controllers/api/v1/statuses/reblogs_controller.rb index fe492fd0..9e17dfd9 100644 --- a/app/controllers/api/v1/statuses/reblogs_controller.rb +++ b/app/controllers/api/v1/statuses/reblogs_controller.rb @@ -7,22 +7,25 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController before_action :require_user! def create - if !current_user.account.local? || !status_for_reblog.local - return render json: { error: 'Invalid action' }, status: 422 - end - - @status = ReblogService.new.call(current_user.account, status_for_reblog, reblog_params) - render json: @status, serializer: REST::StatusSerializer + @relog = status_for_reblog + ReblogService.new.call(current_user.account, @relog, reblog_params) + render json: @relog, serializer: REST::StatusStatSerializer end def destroy - @status = status_for_destroy.reblog - @reblogs_map = { @status.id => false } + @my_relog = status_for_destroy + @original_status = @my_relog.reblog - authorize status_for_destroy, :unreblog? - RemovalWorker.perform_async(status_for_destroy.id) + authorize @my_relog, :unreblog? - render json: @status, serializer: REST::StatusSerializer, relationships: StatusRelationshipsPresenter.new([@status], current_user&.account_id, reblogs_map: @reblogs_map) + RemovalWorker.perform_async(@my_relog.id) + + @reblogs_map = { @original_status.id => false } + + render json: @original_status, + serializer: REST::StatusStatSerializer, + unreblog: true, + relationships: StatusRelationshipsPresenter.new([@original_status], current_user&.account_id, reblogs_map: @reblogs_map) end private diff --git a/app/controllers/api/v1/suggestions_controller.rb b/app/controllers/api/v1/suggestions_controller.rb index 6ebec322..61b534be 100644 --- a/app/controllers/api/v1/suggestions_controller.rb +++ b/app/controllers/api/v1/suggestions_controller.rb @@ -23,7 +23,7 @@ class Api::V1::SuggestionsController < Api::BaseController def destroy PotentialFriendshipTracker.remove(current_account.id, params[:id]) - render_empty + render_empty_success end end diff --git a/app/controllers/api/web/settings_controller.rb b/app/controllers/api/web/settings_controller.rb index 3d65e46e..21a28273 100644 --- a/app/controllers/api/web/settings_controller.rb +++ b/app/controllers/api/web/settings_controller.rb @@ -7,7 +7,7 @@ class Api::Web::SettingsController < Api::Web::BaseController setting.data = params[:data] setting.save! - render_empty + render_empty_success end private diff --git a/app/controllers/concerns/signature_verification.rb b/app/controllers/concerns/signature_verification.rb index fb2166ff..65265b15 100644 --- a/app/controllers/concerns/signature_verification.rb +++ b/app/controllers/concerns/signature_verification.rb @@ -66,6 +66,7 @@ module SignatureVerification return account unless verify_signature(account, signature, compare_signed_string).nil? + # : todo : @signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}" @signed_request_account = nil end diff --git a/app/javascript/gabsocial/actions/accounts.js b/app/javascript/gabsocial/actions/accounts.js index 38c1d563..dd752a25 100644 --- a/app/javascript/gabsocial/actions/accounts.js +++ b/app/javascript/gabsocial/actions/accounts.js @@ -1,766 +1,674 @@ -import api, { getLinks } from '../api'; -import openDB from '../storage/db'; +import api, { getLinks } from '../api' +import openDB from '../storage/db' import { importAccount, importFetchedAccount, importFetchedAccounts, importErrorWhileFetchingAccountByUsername, -} from './importer'; -import { me } from '../initial_state'; +} from './importer' +import { me } from '../initial_state' -export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST'; -export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS'; -export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL'; +export const ACCOUNT_FETCH_REQUEST = 'ACCOUNT_FETCH_REQUEST' +export const ACCOUNT_FETCH_SUCCESS = 'ACCOUNT_FETCH_SUCCESS' +export const ACCOUNT_FETCH_FAIL = 'ACCOUNT_FETCH_FAIL' -export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST'; -export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS'; -export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL'; +export const ACCOUNT_FOLLOW_REQUEST = 'ACCOUNT_FOLLOW_REQUEST' +export const ACCOUNT_FOLLOW_SUCCESS = 'ACCOUNT_FOLLOW_SUCCESS' +export const ACCOUNT_FOLLOW_FAIL = 'ACCOUNT_FOLLOW_FAIL' -export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST'; -export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS'; -export const ACCOUNT_UNFOLLOW_FAIL = 'ACCOUNT_UNFOLLOW_FAIL'; +export const ACCOUNT_UNFOLLOW_REQUEST = 'ACCOUNT_UNFOLLOW_REQUEST' +export const ACCOUNT_UNFOLLOW_SUCCESS = 'ACCOUNT_UNFOLLOW_SUCCESS' +export const ACCOUNT_UNFOLLOW_FAIL = 'ACCOUNT_UNFOLLOW_FAIL' -export const ACCOUNT_BLOCK_REQUEST = 'ACCOUNT_BLOCK_REQUEST'; -export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS'; -export const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL'; +export const ACCOUNT_BLOCK_SUCCESS = 'ACCOUNT_BLOCK_SUCCESS' +export const ACCOUNT_BLOCK_FAIL = 'ACCOUNT_BLOCK_FAIL' -export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST'; -export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS'; -export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL'; +export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST' +export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS' +export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL' -export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST'; -export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS'; -export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL'; +export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST' +export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS' +export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL' -export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST'; -export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS'; -export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL'; +export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST' +export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS' +export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL' -export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST'; -export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS'; -export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL'; +export const FOLLOWERS_FETCH_REQUEST = 'FOLLOWERS_FETCH_REQUEST' +export const FOLLOWERS_FETCH_SUCCESS = 'FOLLOWERS_FETCH_SUCCESS' +export const FOLLOWERS_FETCH_FAIL = 'FOLLOWERS_FETCH_FAIL' -export const FOLLOWERS_EXPAND_REQUEST = 'FOLLOWERS_EXPAND_REQUEST'; -export const FOLLOWERS_EXPAND_SUCCESS = 'FOLLOWERS_EXPAND_SUCCESS'; -export const FOLLOWERS_EXPAND_FAIL = 'FOLLOWERS_EXPAND_FAIL'; +export const FOLLOWERS_EXPAND_REQUEST = 'FOLLOWERS_EXPAND_REQUEST' +export const FOLLOWERS_EXPAND_SUCCESS = 'FOLLOWERS_EXPAND_SUCCESS' +export const FOLLOWERS_EXPAND_FAIL = 'FOLLOWERS_EXPAND_FAIL' -export const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST'; -export const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS'; -export const FOLLOWING_FETCH_FAIL = 'FOLLOWING_FETCH_FAIL'; +export const FOLLOWING_FETCH_REQUEST = 'FOLLOWING_FETCH_REQUEST' +export const FOLLOWING_FETCH_SUCCESS = 'FOLLOWING_FETCH_SUCCESS' +export const FOLLOWING_FETCH_FAIL = 'FOLLOWING_FETCH_FAIL' -export const FOLLOWING_EXPAND_REQUEST = 'FOLLOWING_EXPAND_REQUEST'; -export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS'; -export const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL'; +export const FOLLOWING_EXPAND_REQUEST = 'FOLLOWING_EXPAND_REQUEST' +export const FOLLOWING_EXPAND_SUCCESS = 'FOLLOWING_EXPAND_SUCCESS' +export const FOLLOWING_EXPAND_FAIL = 'FOLLOWING_EXPAND_FAIL' -export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST'; -export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS'; -export const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL'; +export const RELATIONSHIPS_FETCH_REQUEST = 'RELATIONSHIPS_FETCH_REQUEST' +export const RELATIONSHIPS_FETCH_SUCCESS = 'RELATIONSHIPS_FETCH_SUCCESS' +export const RELATIONSHIPS_FETCH_FAIL = 'RELATIONSHIPS_FETCH_FAIL' -export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST'; -export const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS'; -export const FOLLOW_REQUESTS_FETCH_FAIL = 'FOLLOW_REQUESTS_FETCH_FAIL'; +export const FOLLOW_REQUESTS_FETCH_REQUEST = 'FOLLOW_REQUESTS_FETCH_REQUEST' +export const FOLLOW_REQUESTS_FETCH_SUCCESS = 'FOLLOW_REQUESTS_FETCH_SUCCESS' +export const FOLLOW_REQUESTS_FETCH_FAIL = 'FOLLOW_REQUESTS_FETCH_FAIL' -export const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST'; -export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS'; -export const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL'; +export const FOLLOW_REQUESTS_EXPAND_REQUEST = 'FOLLOW_REQUESTS_EXPAND_REQUEST' +export const FOLLOW_REQUESTS_EXPAND_SUCCESS = 'FOLLOW_REQUESTS_EXPAND_SUCCESS' +export const FOLLOW_REQUESTS_EXPAND_FAIL = 'FOLLOW_REQUESTS_EXPAND_FAIL' -export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST'; -export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS'; -export const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL'; +export const FOLLOW_REQUEST_AUTHORIZE_REQUEST = 'FOLLOW_REQUEST_AUTHORIZE_REQUEST' +export const FOLLOW_REQUEST_AUTHORIZE_SUCCESS = 'FOLLOW_REQUEST_AUTHORIZE_SUCCESS' +export const FOLLOW_REQUEST_AUTHORIZE_FAIL = 'FOLLOW_REQUEST_AUTHORIZE_FAIL' -export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST'; -export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS'; -export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL'; +export const FOLLOW_REQUEST_REJECT_REQUEST = 'FOLLOW_REQUEST_REJECT_REQUEST' +export const FOLLOW_REQUEST_REJECT_SUCCESS = 'FOLLOW_REQUEST_REJECT_SUCCESS' +export const FOLLOW_REQUEST_REJECT_FAIL = 'FOLLOW_REQUEST_REJECT_FAIL' +/** + * + */ function getFromDB(dispatch, getState, 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 = () => { if (!request.result) { - reject(); - return; + reject() + return } - dispatch(importAccount(request.result)); - resolve(request.result.moved && getFromDB(dispatch, getState, index, request.result.moved)); - }; - }); + dispatch(importAccount(request.result)) + resolve(request.result.moved && getFromDB(dispatch, getState, index, request.result.moved)) + } + }) } -export function fetchAccount(id) { - return (dispatch, getState) => { - if (id === -1 || getState().getIn(['accounts', id], null) !== null) { - return; - } - - dispatch(fetchRelationships([id])); - dispatch(fetchAccountRequest(id)); - - openDB().then(db => getFromDB( - dispatch, - getState, - db.transaction('accounts', 'read').objectStore('accounts').index('id'), - id - ).then(() => db.close(), error => { - db.close(); - throw error; - })).catch(() => api(getState).get(`/api/v1/accounts/${id}`).then(response => { - dispatch(importFetchedAccount(response.data)); - })).then(() => { - dispatch(fetchAccountSuccess()); - }).catch(error => { - dispatch(fetchAccountFail(id, error)); - }); - }; -}; - -export function fetchAccountByUsername(username) { - return (dispatch, getState) => { - if (!username) { - return; - } - - api(getState).get(`/api/v1/account_by_username/${username}`).then(response => { - dispatch(importFetchedAccount(response.data)) - dispatch(fetchRelationships([response.data.id])) - }).then(() => { - dispatch(fetchAccountSuccess()); - }).catch(error => { - dispatch(fetchAccountFail(null, error)); - dispatch(importErrorWhileFetchingAccountByUsername(username)); - }); - }; -}; - -export function fetchAccountRequest(id) { - return { - type: ACCOUNT_FETCH_REQUEST, - id, - }; -}; - -export function fetchAccountSuccess() { - return { - type: ACCOUNT_FETCH_SUCCESS, - }; -}; - -export function fetchAccountFail(id, error) { - return { - type: ACCOUNT_FETCH_FAIL, - id, - error, - skipAlert: true, - }; -}; - -export function followAccount(id, reblogs = true) { - return (dispatch, getState) => { - if (!me) return; - - const alreadyFollowing = getState().getIn(['relationships', id, 'following']); - const locked = getState().getIn(['accounts', id, 'locked'], false); - - dispatch(followAccountRequest(id, locked)); - - api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then(response => { - dispatch(followAccountSuccess(response.data, alreadyFollowing)); - }).catch(error => { - dispatch(followAccountFail(error, locked)); - }); - }; -}; - -export function unfollowAccount(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(unfollowAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/unfollow`).then(response => { - dispatch(unfollowAccountSuccess(response.data, getState().get('statuses'))); - }).catch(error => { - dispatch(unfollowAccountFail(error)); - }); - }; -}; - -export function followAccountRequest(id, locked) { - return { - type: ACCOUNT_FOLLOW_REQUEST, - id, - locked, - skipLoading: true, - }; -}; - -export function followAccountSuccess(relationship, alreadyFollowing) { - return { - type: ACCOUNT_FOLLOW_SUCCESS, - relationship, - alreadyFollowing, - skipLoading: true, - }; -}; - -export function followAccountFail(error, locked) { - return { - type: ACCOUNT_FOLLOW_FAIL, - error, - locked, - skipLoading: true, - }; -}; - -export function unfollowAccountRequest(id) { - return { - type: ACCOUNT_UNFOLLOW_REQUEST, - id, - skipLoading: true, - }; -}; - -export function unfollowAccountSuccess(relationship, statuses) { - return { - type: ACCOUNT_UNFOLLOW_SUCCESS, - relationship, - statuses, - skipLoading: true, - }; -}; - -export function unfollowAccountFail(error) { - return { - type: ACCOUNT_UNFOLLOW_FAIL, - error, - skipLoading: true, - }; -}; - -export function blockAccount(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(blockAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/block`).then(response => { - // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - dispatch(blockAccountSuccess(response.data, getState().get('statuses'))); - }).catch(error => { - dispatch(blockAccountFail(id, error)); - }); - }; -}; - -export function unblockAccount(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(unblockAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/unblock`).then(response => { - dispatch(unblockAccountSuccess(response.data)); - }).catch(error => { - dispatch(unblockAccountFail(id, error)); - }); - }; -}; - -export function blockAccountRequest(id) { - return { - type: ACCOUNT_BLOCK_REQUEST, - id, - }; -}; - -export function blockAccountSuccess(relationship, statuses) { - return { - type: ACCOUNT_BLOCK_SUCCESS, - relationship, - statuses, - }; -}; - -export function blockAccountFail(error) { - return { - type: ACCOUNT_BLOCK_FAIL, - error, - }; -}; - -export function unblockAccountRequest(id) { - return { - type: ACCOUNT_UNBLOCK_REQUEST, - id, - }; -}; - -export function unblockAccountSuccess(relationship) { - return { - type: ACCOUNT_UNBLOCK_SUCCESS, - relationship, - }; -}; - -export function unblockAccountFail(error) { - return { - type: ACCOUNT_UNBLOCK_FAIL, - error, - }; -}; - - -export function muteAccount(id, notifications) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(muteAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications }).then(response => { - // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers - dispatch(muteAccountSuccess(response.data, getState().get('statuses'))); - }).catch(error => { - dispatch(muteAccountFail(id, error)); - }); - }; -}; - -export function unmuteAccount(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(unmuteAccountRequest(id)); - - api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => { - dispatch(unmuteAccountSuccess(response.data)); - }).catch(error => { - dispatch(unmuteAccountFail(id, error)); - }); - }; -}; - -export function muteAccountRequest(id) { - return { - type: ACCOUNT_MUTE_REQUEST, - id, - }; -}; - -export function muteAccountSuccess(relationship, statuses) { - return { - type: ACCOUNT_MUTE_SUCCESS, - relationship, - statuses, - }; -}; - -export function muteAccountFail(error) { - return { - type: ACCOUNT_MUTE_FAIL, - error, - }; -}; - -export function unmuteAccountRequest(id) { - return { - type: ACCOUNT_UNMUTE_REQUEST, - id, - }; -}; - -export function unmuteAccountSuccess(relationship) { - return { - type: ACCOUNT_UNMUTE_SUCCESS, - relationship, - }; -}; - -export function unmuteAccountFail(error) { - return { - type: ACCOUNT_UNMUTE_FAIL, - error, - }; -}; - - -export function fetchFollowers(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(fetchFollowersRequest(id)); - - api(getState).get(`/api/v1/accounts/${id}/followers`).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(importFetchedAccounts(response.data)); - dispatch(fetchFollowersSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(fetchFollowersFail(id, error)); - }); - }; -}; - -export function fetchFollowersRequest(id) { - return { - type: FOLLOWERS_FETCH_REQUEST, - id, - }; -}; - -export function fetchFollowersSuccess(id, accounts, next) { - return { - type: FOLLOWERS_FETCH_SUCCESS, - id, - accounts, - next, - }; -}; - -export function fetchFollowersFail(id, error) { - return { - type: FOLLOWERS_FETCH_FAIL, - id, - error, - }; -}; - -export function expandFollowers(id) { - return (dispatch, getState) => { - if (!me) return; - - const url = getState().getIn(['user_lists', 'followers', id, 'next']); - const isLoading = getState().getIn(['user_lists', 'followers', id, 'isLoading']); - - if (url === null || isLoading) return - - dispatch(expandFollowersRequest(id)); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(importFetchedAccounts(response.data)); - dispatch(expandFollowersSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch((error) => { - dispatch(expandFollowersFail(id, error)); - }); - }; -}; - -export function expandFollowersRequest(id) { - return { - type: FOLLOWERS_EXPAND_REQUEST, - id, - }; -}; - -export function expandFollowersSuccess(id, accounts, next) { - return { - type: FOLLOWERS_EXPAND_SUCCESS, - id, - accounts, - next, - }; -}; - -export function expandFollowersFail(id, error) { - return { - type: FOLLOWERS_EXPAND_FAIL, - id, - error, - }; -}; - -export function fetchFollowing(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(fetchFollowingRequest(id)); - - api(getState).get(`/api/v1/accounts/${id}/following`).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(importFetchedAccounts(response.data)); - dispatch(fetchFollowingSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(fetchFollowingFail(id, error)); - }); - }; -}; - -export function fetchFollowingRequest(id) { - return { - type: FOLLOWING_FETCH_REQUEST, - id, - }; -}; - -export function fetchFollowingSuccess(id, accounts, next) { - return { - type: FOLLOWING_FETCH_SUCCESS, - id, - accounts, - next, - }; -}; - -export function fetchFollowingFail(id, error) { - return { - type: FOLLOWING_FETCH_FAIL, - id, - error, - }; -}; - -export function expandFollowing(id) { - return (dispatch, getState) => { - if (!me) return; - - const url = getState().getIn(['user_lists', 'following', id, 'next']); - const isLoading = getState().getIn(['user_lists', 'following', id, 'isLoading']); - - if (url === null || isLoading) return - - dispatch(expandFollowingRequest(id)); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(importFetchedAccounts(response.data)); - dispatch(expandFollowingSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(expandFollowingFail(id, error)); - }); - }; -}; - -export function expandFollowingRequest(id) { - return { - type: FOLLOWING_EXPAND_REQUEST, - id, - }; -}; - -export function expandFollowingSuccess(id, accounts, next) { - return { - type: FOLLOWING_EXPAND_SUCCESS, - id, - accounts, - next, - }; -}; - -export function expandFollowingFail(id, error) { - return { - type: FOLLOWING_EXPAND_FAIL, - id, - error, - }; -}; - -export function fetchRelationships(accountIds) { - return (dispatch, getState) => { - if (!me) return; - - const loadedRelationships = getState().get('relationships'); - let newAccountIds = accountIds.filter((id) => { - if (id === me) return false - return loadedRelationships.get(id, null) === null - }) - - if (newAccountIds.length === 0) { - return; - } else if (newAccountIds.length == 1) { - const firstId = newAccountIds[0] - if (me === firstId) return; - } - - // Unique - newAccountIds = Array.from(new Set(newAccountIds)) - - dispatch(fetchRelationshipsRequest(newAccountIds)); - - api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then(response => { - dispatch(fetchRelationshipsSuccess(response.data)); - }).catch(error => { - dispatch(fetchRelationshipsFail(error)); - }); - }; -}; - -export function fetchRelationshipsRequest(ids) { - return { - type: RELATIONSHIPS_FETCH_REQUEST, - ids, - skipLoading: true, - }; -}; - -export function fetchRelationshipsSuccess(relationships) { - return { - type: RELATIONSHIPS_FETCH_SUCCESS, - relationships, - skipLoading: true, - }; -}; - -export function fetchRelationshipsFail(error) { - return { - type: RELATIONSHIPS_FETCH_FAIL, - error, - skipLoading: true, - }; -}; - -export function fetchFollowRequests() { - return (dispatch, getState) => { - if (!me) return; - - dispatch(fetchFollowRequestsRequest()); - - api(getState).get('/api/v1/follow_requests').then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(importFetchedAccounts(response.data)); - dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)); - }).catch(error => dispatch(fetchFollowRequestsFail(error))); - }; -}; - -export function fetchFollowRequestsRequest() { - return { - type: FOLLOW_REQUESTS_FETCH_REQUEST, - }; -}; - -export function fetchFollowRequestsSuccess(accounts, next) { - return { - type: FOLLOW_REQUESTS_FETCH_SUCCESS, - accounts, - next, - }; -}; - -export function fetchFollowRequestsFail(error) { - return { - type: FOLLOW_REQUESTS_FETCH_FAIL, - error, - }; -}; - -export function expandFollowRequests() { - return (dispatch, getState) => { - if (!me) return; - - const url = getState().getIn(['user_lists', 'follow_requests', me, 'next']); - const isLoading = getState().getIn(['user_lists', 'follow_requests', me, 'isLoading']); - - if (url === null || isLoading) return - - dispatch(expandFollowRequestsRequest()); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - dispatch(importFetchedAccounts(response.data)); - dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)); - }).catch(error => dispatch(expandFollowRequestsFail(error))); - }; -}; - -export function expandFollowRequestsRequest() { - return { - type: FOLLOW_REQUESTS_EXPAND_REQUEST, - }; -}; - -export function expandFollowRequestsSuccess(accounts, next) { - return { - type: FOLLOW_REQUESTS_EXPAND_SUCCESS, - accounts, - next, - }; -}; - -export function expandFollowRequestsFail(error) { - return { - type: FOLLOW_REQUESTS_EXPAND_FAIL, - error, - }; -}; - -export function authorizeFollowRequest(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(authorizeFollowRequestRequest(id)); - - api(getState) - .post(`/api/v1/follow_requests/${id}/authorize`) - .then(() => dispatch(authorizeFollowRequestSuccess(id))) - .catch(error => dispatch(authorizeFollowRequestFail(id, error))); - }; -}; - -export function authorizeFollowRequestRequest(id) { - return { - type: FOLLOW_REQUEST_AUTHORIZE_REQUEST, - id, - }; -}; - -export function authorizeFollowRequestSuccess(id) { - return { - type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, - id, - }; -}; - -export function authorizeFollowRequestFail(id, error) { - return { - type: FOLLOW_REQUEST_AUTHORIZE_FAIL, - id, - error, - }; -}; - - -export function rejectFollowRequest(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(rejectFollowRequestRequest(id)); - - api(getState) - .post(`/api/v1/follow_requests/${id}/reject`) - .then(() => dispatch(rejectFollowRequestSuccess(id))) - .catch(error => dispatch(rejectFollowRequestFail(id, error))); - }; -}; - -export function rejectFollowRequestRequest(id) { - return { - type: FOLLOW_REQUEST_REJECT_REQUEST, - id, - }; -}; - -export function rejectFollowRequestSuccess(id) { - return { - type: FOLLOW_REQUEST_REJECT_SUCCESS, - id, - }; -}; - -export function rejectFollowRequestFail(id, error) { - return { - type: FOLLOW_REQUEST_REJECT_FAIL, - id, - error, - }; -}; +/** + * + */ +export const fetchAccount = (id) => (dispatch, getState) => { + if (id === -1 || getState().getIn(['accounts', id], null) !== null) return + + dispatch(fetchRelationships([id])) + dispatch(fetchAccountRequest(id)) + + openDB().then((db) => getFromDB( + dispatch, + getState, + db.transaction('accounts', 'read').objectStore('accounts').index('id'), + id + ).then(() => db.close(), (error) => { + db.close() + throw error + })).catch(() => api(getState).get(`/api/v1/accounts/${id}`).then((response) => { + dispatch(importFetchedAccount(response.data)) + })).then(() => { + dispatch(fetchAccountSuccess()) + }).catch((error) => { + dispatch(fetchAccountFail(id, error)) + }) +} + +/** + * + */ +export const fetchAccountByUsername = (username) => (dispatch, getState) => { + if (!username) return + + api(getState).get(`/api/v1/account_by_username/${username}`).then((response) => { + dispatch(importFetchedAccount(response.data)) + dispatch(fetchRelationships([response.data.id])) + }).then(() => { + dispatch(fetchAccountSuccess()) + }).catch((error) => { + dispatch(fetchAccountFail(null, error)) + dispatch(importErrorWhileFetchingAccountByUsername(username)) + }) +} + +const fetchAccountRequest = (id) => ({ + type: ACCOUNT_FETCH_REQUEST, + id, +}) + +const fetchAccountSuccess = () => ({ + type: ACCOUNT_FETCH_SUCCESS, +}) + +const fetchAccountFail = (id, error) => ({ + type: ACCOUNT_FETCH_FAIL, + id, + error, + skipAlert: true, +}) + +/** + * + */ +export const followAccount = (id, reblogs = true) => (dispatch, getState) => { + if (!me) return + + const alreadyFollowing = getState().getIn(['relationships', id, 'following']) + const locked = getState().getIn(['accounts', id, 'locked'], false) + + dispatch(followAccountRequest(id, locked)) + + api(getState).post(`/api/v1/accounts/${id}/follow`, { reblogs }).then((response) => { + dispatch(followAccountSuccess(response.data, alreadyFollowing)) + }).catch((error) => { + dispatch(followAccountFail(error, locked)) + }) +} + +const followAccountRequest = (id, locked) => ({ + type: ACCOUNT_FOLLOW_REQUEST, + id, + locked, +}) + +const followAccountSuccess = (relationship, alreadyFollowing) => ({ + type: ACCOUNT_FOLLOW_SUCCESS, + relationship, + alreadyFollowing, +}) + +const followAccountFail = (error, locked) => ({ + type: ACCOUNT_FOLLOW_FAIL, + error, + locked, +}) + +/** + * + */ +export const unfollowAccount = (id) => (dispatch, getState) => { + if (!me) return + + dispatch(unfollowAccountRequest(id)) + + api(getState).post(`/api/v1/accounts/${id}/unfollow`).then((response) => { + dispatch(unfollowAccountSuccess(response.data, getState().get('statuses'))) + }).catch((error) => { + dispatch(unfollowAccountFail(error)) + }) +} + +const unfollowAccountRequest = (id) => ({ + type: ACCOUNT_UNFOLLOW_REQUEST, + id, +}) + +const unfollowAccountSuccess = (relationship, statuses) => ({ + type: ACCOUNT_UNFOLLOW_SUCCESS, + relationship, + statuses, +}) + +const unfollowAccountFail = (error) => ({ + type: ACCOUNT_UNFOLLOW_FAIL, + error, +}) + +/** + * + */ +export const blockAccount = (id) => (dispatch, getState) => { + if (!me) return + + dispatch(blockAccountRequest(id)) + + api(getState).post(`/api/v1/accounts/${id}/block`).then((response) => { + // : todo : remove gay stuff like passing entire status below: + // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers + dispatch(blockAccountSuccess(response.data, getState().get('statuses'))) + }).catch((error) => { + dispatch(blockAccountFail(id, error)) + }) +} + +const blockAccountRequest = (id) => ({ + type: ACCOUNT_BLOCK_REQUEST, + id, +}) + +const blockAccountSuccess = (relationship, statuses) => ({ + type: ACCOUNT_BLOCK_SUCCESS, + relationship, + statuses, +}) + +const blockAccountFail = (error) => ({ + type: ACCOUNT_BLOCK_FAIL, + error, +}) + +/** + * + */ +export const unblockAccount = (id) => (dispatch, getState) => { + if (!me) return + + dispatch(unblockAccountRequest(id)) + + api(getState).post(`/api/v1/accounts/${id}/unblock`).then((response) => { + dispatch(unblockAccountSuccess(response.data)) + }).catch((error) => { + dispatch(unblockAccountFail(id, error)) + }) +} + +const unblockAccountRequest = (id) => ({ + type: ACCOUNT_UNBLOCK_REQUEST, + id, +}) + +const unblockAccountSuccess = (relationship) => ({ + type: ACCOUNT_UNBLOCK_SUCCESS, + relationship, +}) + +const unblockAccountFail = (error) => ({ + type: ACCOUNT_UNBLOCK_FAIL, + error, +}) + +/** + * + */ +export const muteAccount = (id, notifications) => (dispatch, getState) => { + if (!me) return + + dispatch(muteAccountRequest(id)) + + api(getState).post(`/api/v1/accounts/${id}/mute`, { notifications }).then((response) => { + // Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers + dispatch(muteAccountSuccess(response.data, getState().get('statuses'))) + }).catch((error) => { + dispatch(muteAccountFail(id, error)) + }) +} + +const muteAccountRequest = (id) => ({ + type: ACCOUNT_MUTE_REQUEST, + id, +}) + +const muteAccountSuccess = (relationship, statuses) => ({ + type: ACCOUNT_MUTE_SUCCESS, + relationship, + statuses, +}) + +const muteAccountFail = (error) => ({ + type: ACCOUNT_MUTE_FAIL, + error, +}) + +/** + * + */ +export const unmuteAccount = (id) => (dispatch, getState) => { + if (!me) return + + dispatch(unmuteAccountRequest(id)) + + api(getState).post(`/api/v1/accounts/${id}/unmute`).then((response) => { + dispatch(unmuteAccountSuccess(response.data)) + }).catch((error) => { + dispatch(unmuteAccountFail(id, error)) + }) +} + +const unmuteAccountRequest = (id) => ({ + type: ACCOUNT_UNMUTE_REQUEST, + id, +}) + +const unmuteAccountSuccess = (relationship) => ({ + type: ACCOUNT_UNMUTE_SUCCESS, + relationship, +}) + +const unmuteAccountFail = (error) => ({ + type: ACCOUNT_UNMUTE_FAIL, + error, +}) + +/** + * + */ +export const fetchFollowers = (id) => (dispatch, getState) => { + if (!me) return + + dispatch(fetchFollowersRequest(id)) + + api(getState).get(`/api/v1/accounts/${id}/followers`).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(fetchFollowersSuccess(id, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(fetchFollowersFail(id, error)) + }) +} + +const fetchFollowersRequest = (id) => ({ + type: FOLLOWERS_FETCH_REQUEST, + id, +}) + +const fetchFollowersSuccess = (id, accounts, next) => ({ + type: FOLLOWERS_FETCH_SUCCESS, + id, + accounts, + next, +}) + +const fetchFollowersFail = (id, error) => ({ + type: FOLLOWERS_FETCH_FAIL, + id, + error, +}) + +/** + * + */ +export const expandFollowers = (id) => (dispatch, getState) => { + if (!me) return + + const url = getState().getIn(['user_lists', 'followers', id, 'next']) + const isLoading = getState().getIn(['user_lists', 'followers', id, 'isLoading']) + + if (url === null || isLoading) return + + dispatch(expandFollowersRequest(id)) + + api(getState).get(url).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(expandFollowersSuccess(id, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(expandFollowersFail(id, error)) + }) +} + +const expandFollowersRequest = (id) => ({ + type: FOLLOWERS_EXPAND_REQUEST, + id, +}) + +const expandFollowersSuccess = (id, accounts, next) => ({ + type: FOLLOWERS_EXPAND_SUCCESS, + id, + accounts, + next, +}) + +const expandFollowersFail = (id, error) => ({ + type: FOLLOWERS_EXPAND_FAIL, + id, + error, +}) + +/** + * + */ +export const fetchFollowing = (id) => (dispatch, getState) => { + if (!me) return + + dispatch(fetchFollowingRequest(id)) + + api(getState).get(`/api/v1/accounts/${id}/following`).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(fetchFollowingSuccess(id, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(fetchFollowingFail(id, error)) + }) +} + +const fetchFollowingRequest = (id) => ({ + type: FOLLOWING_FETCH_REQUEST, + id, +}) + +const fetchFollowingSuccess = (id, accounts, next) => ({ + type: FOLLOWING_FETCH_SUCCESS, + id, + accounts, + next, +}) + +const fetchFollowingFail = (id, error) => ({ + type: FOLLOWING_FETCH_FAIL, + id, + error, +}) + +/** + * + */ +export const expandFollowing = (id) => (dispatch, getState) => { + if (!me) return + + const url = getState().getIn(['user_lists', 'following', id, 'next']) + const isLoading = getState().getIn(['user_lists', 'following', id, 'isLoading']) + + if (url === null || isLoading) return + + dispatch(expandFollowingRequest(id)) + + api(getState).get(url).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(expandFollowingSuccess(id, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(expandFollowingFail(id, error)) + }) +} + +const expandFollowingRequest = (id) => ({ + type: FOLLOWING_EXPAND_REQUEST, + id, +}) + +const expandFollowingSuccess = (id, accounts, next) => ({ + type: FOLLOWING_EXPAND_SUCCESS, + id, + accounts, + next, +}) + +const expandFollowingFail = (id, error) => ({ + type: FOLLOWING_EXPAND_FAIL, + id, + error, +}) + +/** + * + */ +export const fetchRelationships = (accountIds) => (dispatch, getState) => { + if (!me) return + + const loadedRelationships = getState().get('relationships') + let newAccountIds = accountIds.filter((id) => { + if (id === me) return false + return loadedRelationships.get(id, null) === null + }) + + if (newAccountIds.length === 0) return + + if (newAccountIds.length == 1) { + const firstId = newAccountIds[0] + if (me === firstId) return + } + + // Unique + newAccountIds = Array.from(new Set(newAccountIds)) + + dispatch(fetchRelationshipsRequest(newAccountIds)) + + api(getState).get(`/api/v1/accounts/relationships?${newAccountIds.map(id => `id[]=${id}`).join('&')}`).then((response) => { + dispatch(fetchRelationshipsSuccess(response.data)) + }).catch((error) => { + dispatch(fetchRelationshipsFail(error)) + }) +} + +const fetchRelationshipsRequest = (ids) => ({ + type: RELATIONSHIPS_FETCH_REQUEST, + ids, +}) + +const fetchRelationshipsSuccess = (relationships) => ({ + type: RELATIONSHIPS_FETCH_SUCCESS, + relationships, +}) + +const fetchRelationshipsFail = (error) => ({ + type: RELATIONSHIPS_FETCH_FAIL, + error, +}) + +/** + * + */ +export const fetchFollowRequests = () => (dispatch, getState) => { + if (!me) return + + dispatch(fetchFollowRequestsRequest()) + + api(getState).get('/api/v1/follow_requests').then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + dispatch(importFetchedAccounts(response.data)) + dispatch(fetchFollowRequestsSuccess(response.data, next ? next.uri : null)) + }).catch((error) => dispatch(fetchFollowRequestsFail(error))) +} + +const fetchFollowRequestsRequest = () => ({ + type: FOLLOW_REQUESTS_FETCH_REQUEST, +}) + +const fetchFollowRequestsSuccess = (accounts, next) => ({ + type: FOLLOW_REQUESTS_FETCH_SUCCESS, + accounts, + next, +}) + +const fetchFollowRequestsFail = (error) => ({ + type: FOLLOW_REQUESTS_FETCH_FAIL, + error, +}) + +/** + * + */ +export const expandFollowRequests = () => (dispatch, getState) => { + if (!me) return + + const url = getState().getIn(['user_lists', 'follow_requests', me, 'next']) + const isLoading = getState().getIn(['user_lists', 'follow_requests', me, 'isLoading']) + + if (url === null || isLoading) return + + dispatch(expandFollowRequestsRequest()) + + api(getState).get(url).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + dispatch(importFetchedAccounts(response.data)) + dispatch(expandFollowRequestsSuccess(response.data, next ? next.uri : null)) + }).catch((error) => dispatch(expandFollowRequestsFail(error))) +} + +const expandFollowRequestsRequest = () => ({ + type: FOLLOW_REQUESTS_EXPAND_REQUEST, +}) + +const expandFollowRequestsSuccess = (accounts, next) => ({ + type: FOLLOW_REQUESTS_EXPAND_SUCCESS, + accounts, + next, +}) + +const expandFollowRequestsFail = (error) => ({ + type: FOLLOW_REQUESTS_EXPAND_FAIL, + error, +}) + +/** + * + */ +export const authorizeFollowRequest = (id) => (dispatch, getState) => { + if (!me) return + + dispatch(authorizeFollowRequestRequest(id)) + + api(getState) + .post(`/api/v1/follow_requests/${id}/authorize`) + .then(() => dispatch(authorizeFollowRequestSuccess(id))) + .catch((error) => dispatch(authorizeFollowRequestFail(id, error))) +} + +const authorizeFollowRequestRequest = (id) => ({ + type: FOLLOW_REQUEST_AUTHORIZE_REQUEST, + id, +}) + +const authorizeFollowRequestSuccess = (id) => ({ + type: FOLLOW_REQUEST_AUTHORIZE_SUCCESS, + id, +}) + +const authorizeFollowRequestFail = (id, error) => ({ + type: FOLLOW_REQUEST_AUTHORIZE_FAIL, + id, + error, +}) + +/** + * + */ +export const rejectFollowRequest = (id) => (dispatch, getState) => { + if (!me) return + + dispatch(rejectFollowRequestRequest(id)) + + api(getState) + .post(`/api/v1/follow_requests/${id}/reject`) + .then(() => dispatch(rejectFollowRequestSuccess(id))) + .catch((error) => dispatch(rejectFollowRequestFail(id, error))) +} + +const rejectFollowRequestRequest = (id) => ({ + type: FOLLOW_REQUEST_REJECT_REQUEST, + id, +}) + +const rejectFollowRequestSuccess = (id) => ({ + type: FOLLOW_REQUEST_REJECT_SUCCESS, + id, +}) + +const rejectFollowRequestFail = (id, error) => ({ + type: FOLLOW_REQUEST_REJECT_FAIL, + id, + error, +}) diff --git a/app/javascript/gabsocial/actions/bookmarks.js b/app/javascript/gabsocial/actions/bookmarks.js index 3c41d2ff..3af513a9 100644 --- a/app/javascript/gabsocial/actions/bookmarks.js +++ b/app/javascript/gabsocial/actions/bookmarks.js @@ -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, }) /** diff --git a/app/javascript/gabsocial/actions/chat_compose.js b/app/javascript/gabsocial/actions/chat_compose.js new file mode 100644 index 00000000..ac85f788 --- /dev/null +++ b/app/javascript/gabsocial/actions/chat_compose.js @@ -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 }) +} diff --git a/app/javascript/gabsocial/actions/chat_conversations.js b/app/javascript/gabsocial/actions/chat_conversations.js new file mode 100644 index 00000000..6263209f --- /dev/null +++ b/app/javascript/gabsocial/actions/chat_conversations.js @@ -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, +}) diff --git a/app/javascript/gabsocial/actions/chat_messages.js b/app/javascript/gabsocial/actions/chat_messages.js new file mode 100644 index 00000000..e65912c9 --- /dev/null +++ b/app/javascript/gabsocial/actions/chat_messages.js @@ -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, +}) \ No newline at end of file diff --git a/app/javascript/gabsocial/actions/compose.js b/app/javascript/gabsocial/actions/compose.js index fe9e4d80..91eb47bb 100644 --- a/app/javascript/gabsocial/actions/compose.js +++ b/app/javascript/gabsocial/actions/compose.js @@ -19,6 +19,7 @@ import { import { defineMessages } from 'react-intl' import { openModal, closeModal } from './modal' import { + MODAL_COMPOSE, STATUS_EXPIRATION_OPTION_5_MINUTES, STATUS_EXPIRATION_OPTION_60_MINUTES, STATUS_EXPIRATION_OPTION_6_HOURS, @@ -57,18 +58,18 @@ export const COMPOSE_TAG_HISTORY_UPDATE = 'COMPOSE_TAG_HISTORY_UPDATE' export const COMPOSE_MOUNT = 'COMPOSE_MOUNT' export const COMPOSE_UNMOUNT = 'COMPOSE_UNMOUNT' -export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE' -export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE' +export const COMPOSE_SENSITIVITY_CHANGE = 'COMPOSE_SENSITIVITY_CHANGE' +export const COMPOSE_SPOILERNESS_CHANGE = 'COMPOSE_SPOILERNESS_CHANGE' export const COMPOSE_SPOILER_TEXT_CHANGE = 'COMPOSE_SPOILER_TEXT_CHANGE' -export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE' -export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE' -export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE' +export const COMPOSE_VISIBILITY_CHANGE = 'COMPOSE_VISIBILITY_CHANGE' +export const COMPOSE_LISTABILITY_CHANGE = 'COMPOSE_LISTABILITY_CHANGE' +export const COMPOSE_COMPOSING_CHANGE = 'COMPOSE_COMPOSING_CHANGE' export const COMPOSE_EMOJI_INSERT = 'COMPOSE_EMOJI_INSERT' -export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST' -export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS' -export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL' +export const COMPOSE_UPLOAD_CHANGE_REQUEST = 'COMPOSE_UPLOAD_UPDATE_REQUEST' +export const COMPOSE_UPLOAD_CHANGE_SUCCESS = 'COMPOSE_UPLOAD_UPDATE_SUCCESS' +export const COMPOSE_UPLOAD_CHANGE_FAIL = 'COMPOSE_UPLOAD_UPDATE_FAIL' export const COMPOSE_POLL_ADD = 'COMPOSE_POLL_ADD' export const COMPOSE_POLL_REMOVE = 'COMPOSE_POLL_REMOVE' @@ -90,157 +91,188 @@ const messages = defineMessages({ uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, }) -const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1) +/** + * + */ +export const hydrateCompose = () => (dispatch, getState) => { + const me = getState().getIn(['meta', 'me']) + const history = tagHistory.get(me) -export const ensureComposeIsVisible = (getState, routerHistory) => { - if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { - routerHistory.push('/posts/new') + if (history !== null) { + dispatch(updateTagHistory(history)) } } -export function changeCompose(text, markdown, replyId, isStandalone, caretPosition) { - return function (dispatch, getState) { - const reduxReplyToId = getState().getIn(['compose', 'in_reply_to']) - const existingText = getState().getIn(['compose', 'text']).trim() - const isModalOpen = getState().getIn(['modal', 'modalType']) === 'COMPOSE' || isStandalone +/** + * + */ +const insertIntoTagHistory = (recognizedTags, text) => (dispatch, getState) => { + const state = getState() + const oldHistory = state.getIn(['compose', 'tagHistory']) + const me = state.getIn(['meta', 'me']) + const names = recognizedTags.map(tag => text.match(new RegExp(`#${tag.name}`, 'i'))[0].slice(1)) + const intersectedOldHistory = oldHistory.filter(name => names.findIndex(newName => newName.toLowerCase() === name.toLowerCase()) === -1) - let status - if (!!replyId) { - status = getState().getIn(['statuses', replyId]) - status = makeGetStatus()(getState(), { - id: status.get('id') + names.push(...intersectedOldHistory.toJS()) + + const newHistory = names.slice(0, 1000) + + tagHistory.set(me, newHistory) + dispatch(updateTagHistory(newHistory)) +} + +/** + * + */ +export const changeCompose = (text, markdown, replyId, isStandalone, caretPosition) => (dispatch, getState) => { + const reduxReplyToId = getState().getIn(['compose', 'in_reply_to']) + const existingText = getState().getIn(['compose', 'text']).trim() + const isModalOpen = getState().getIn(['modal', 'modalType']) === MODAL_COMPOSE || isStandalone + + let status + if (!!replyId) { + status = getState().getIn(['statuses', replyId]) + status = makeGetStatus()(getState(), { + id: status.get('id') + }) + } + + if (!!replyId && replyId !== reduxReplyToId && !isModalOpen) { + if (existingText.length === 0 && text.trim().length > 0) { + dispatch({ + type: COMPOSE_REPLY, + status, + text: text, + }) + } else if (existingText.length > 0 && text.trim().length > 0) { + dispatch(openModal('CONFIRM', { + message: , + confirm: , + onConfirm: () => { + dispatch({ + type: COMPOSE_REPLY, + status, + }) + dispatch({ + type: COMPOSE_CHANGE, + text: text, + markdown: markdown, + caretPosition: caretPosition, + }) + } + })) + } else { + dispatch({ + type: COMPOSE_REPLY_CANCEL, }) } - - if (!!replyId && replyId !== reduxReplyToId && !isModalOpen) { - if (existingText.length === 0 && text.trim().length > 0) { - dispatch({ - type: COMPOSE_REPLY, - status: status, - text: text, - }) - } else if (existingText.length > 0 && text.trim().length > 0) { - dispatch(openModal('CONFIRM', { - message: , - confirm: , - onConfirm: () => { - dispatch({ - type: COMPOSE_REPLY, - status: status, - }) - dispatch({ - type: COMPOSE_CHANGE, - text: text, - markdown: markdown, - caretPosition: caretPosition, - }) - } - })) - } else { - dispatch({ - type: COMPOSE_REPLY_CANCEL, - }) - } - } else if (!replyId && !!reduxReplyToId && !isModalOpen) { - if (existingText.length === 0 && text.trim().length > 0) { - dispatch({ - type: COMPOSE_REPLY_CANCEL, - }) - dispatch({ - type: COMPOSE_CHANGE, - text: text, - markdown: markdown, - caretPosition: caretPosition, - }) - } else if (existingText.length > 0 && text.trim().length > 0) { - dispatch(openModal('CONFIRM', { - message: , - confirm: , - onConfirm: () => { - dispatch({ - type: COMPOSE_REPLY_CANCEL, - }) - dispatch({ - type: COMPOSE_CHANGE, - text: text, - markdown: markdown, - caretPosition: caretPosition, - }) - }, - })) - } else { - // - } - } else { + } else if (!replyId && !!reduxReplyToId && !isModalOpen) { + if (existingText.length === 0 && text.trim().length > 0) { + dispatch({ + type: COMPOSE_REPLY_CANCEL, + }) dispatch({ type: COMPOSE_CHANGE, text: text, markdown: markdown, caretPosition: caretPosition, }) + } else if (existingText.length > 0 && text.trim().length > 0) { + dispatch(openModal('CONFIRM', { + message: , + confirm: , + onConfirm: () => { + dispatch({ + type: COMPOSE_REPLY_CANCEL, + }) + dispatch({ + type: COMPOSE_CHANGE, + text: text, + markdown: markdown, + caretPosition: caretPosition, + }) + }, + })) + } else { + // + } + } else { + dispatch({ + type: COMPOSE_CHANGE, + text: text, + markdown: markdown, + caretPosition: caretPosition, + }) + } +} + +/** + * + */ +export const replyCompose = (status, router, showModal) => (dispatch) => { + dispatch({ + type: COMPOSE_REPLY, + status, + }) + + if (isMobile(window.innerWidth)) { + router.history.push('/compose') + } else { + if (showModal) { + dispatch(openModal(MODAL_COMPOSE)) } } } -export function replyCompose(status, router, showModal) { - return (dispatch) => { - dispatch({ - type: COMPOSE_REPLY, - status: status, - }); +/** + * + */ +export const quoteCompose = (status, router) => (dispatch) => { + dispatch({ + type: COMPOSE_QUOTE, + status, + }) - if (isMobile(window.innerWidth)) { - router.history.push('/compose') - } else { - if (showModal) { - dispatch(openModal('COMPOSE')); - } - } - }; -}; + if (isMobile(window.innerWidth)) { + router.history.push('/compose') + } else { + dispatch(openModal(MODAL_COMPOSE)) + } +} -export function quoteCompose(status, router) { - return (dispatch) => { - dispatch({ - type: COMPOSE_QUOTE, - status: status, - }); +/** + * + */ +export const cancelReplyCompose = () => ({ + type: COMPOSE_REPLY_CANCEL, +}) - if (isMobile(window.innerWidth)) { - router.history.push('/compose') - } else { - dispatch(openModal('COMPOSE')); - } - }; -}; +/** + * + */ +export const resetCompose = () => ({ + type: COMPOSE_RESET, +}) -export function cancelReplyCompose() { - return { - type: COMPOSE_REPLY_CANCEL, - }; -}; +/** + * + */ +export const mentionCompose = (account) => (dispatch) => { + dispatch({ + type: COMPOSE_MENTION, + account: account, + }) -export function resetCompose() { - return { - type: COMPOSE_RESET, - }; -}; + dispatch(openModal(MODAL_COMPOSE)) +} -export function mentionCompose(account) { - return (dispatch) => { - dispatch({ - type: COMPOSE_MENTION, - account: account, - }); +/** + * + */ +export const handleComposeSubmit = (dispatch, getState, response, status) => { + if (!dispatch || !getState) return - dispatch(openModal('COMPOSE')); - }; -}; - -export function handleComposeSubmit(dispatch, getState, response, status) { - if (!dispatch || !getState) return; - - const isScheduledStatus = response.data.scheduled_at !== undefined; + const isScheduledStatus = response.data.scheduled_at !== undefined if (isScheduledStatus) { // dispatch(showAlertForError({ // response: { @@ -248,486 +280,458 @@ export function handleComposeSubmit(dispatch, getState, response, status) { // status: 200, // statusText: 'Successfully scheduled status', // } - // })); - dispatch(submitComposeSuccess({ ...response.data })); - return; + // })) + dispatch(submitComposeSuccess({ ...response.data })) + return } - dispatch(insertIntoTagHistory(response.data.tags, status)); - dispatch(submitComposeSuccess({ ...response.data })); + dispatch(insertIntoTagHistory(response.data.tags, status)) + dispatch(submitComposeSuccess({ ...response.data })) // To make the app more responsive, immediately push the status into the timeline // : todo : push into comment, reload parent status, etc. - const insertIfOnline = timelineId => { - const timeline = getState().getIn(['timelines', timelineId]); + const insertIfOnline = (timelineId) => { + const timeline = getState().getIn(['timelines', timelineId]) if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) { - dispatch(updateTimeline(timelineId, { ...response.data })); + dispatch(updateTimeline(timelineId, { ...response.data })) } - }; + } if (response.data.visibility === 'public') { - insertIfOnline('home'); + insertIfOnline('home') } } -export function submitCompose(groupId, replyToId = null, router, isStandalone, autoJoinGroup) { - return function (dispatch, getState) { - if (!me) return; +/** + * + */ +export const submitCompose = (groupId, replyToId = null, router, isStandalone, autoJoinGroup) => (dispatch, getState) => { + if (!me) return - if (autoJoinGroup) dispatch(joinGroup(groupId)) + if (autoJoinGroup) dispatch(joinGroup(groupId)) - let status = getState().getIn(['compose', 'text'], ''); - let markdown = getState().getIn(['compose', 'markdown'], ''); - const media = getState().getIn(['compose', 'media_attachments']); - const isPrivateGroup = !!groupId ? getState().getIn(['groups', groupId, 'is_private'], false) : false + let status = getState().getIn(['compose', 'text'], '') + let markdown = getState().getIn(['compose', 'markdown'], '') + const media = getState().getIn(['compose', 'media_attachments']) + const isPrivateGroup = !!groupId ? getState().getIn(['groups', groupId, 'is_private'], false) : false - const replacer = (match) => { - const hasProtocol = match.startsWith('https://') || match.startsWith('http://') - //Make sure not a remote mention like @someone@somewhere.com - if (!hasProtocol) { - if (status.indexOf(`@${match}`) > -1) return match - } - return hasProtocol ? match : `http://${match}` + const replacer = (match) => { + const hasProtocol = match.startsWith('https://') || match.startsWith('http://') + //Make sure not a remote mention like @someone@somewhere.com + if (!hasProtocol) { + if (status.indexOf(`@${match}`) > -1) return match } + return hasProtocol ? match : `http://${match}` + } - // : hack : - //Prepend http:// to urls in status that don't have protocol - status = `${status}`.replace(urlRegex, replacer) - markdown = !!markdown ? `${markdown}`.replace(urlRegex, replacer) : undefined + // : hack : + //Prepend http:// to urls in status that don't have protocol + status = `${status}`.replace(urlRegex, replacer) + markdown = !!markdown ? `${markdown}`.replace(urlRegex, replacer) : undefined - const inReplyToId = getState().getIn(['compose', 'in_reply_to'], null) || replyToId + const inReplyToId = getState().getIn(['compose', 'in_reply_to'], null) || replyToId - dispatch(submitComposeRequest()); - dispatch(closeModal()); + dispatch(submitComposeRequest()) + dispatch(closeModal()) - const id = getState().getIn(['compose', 'id']); - const endpoint = id === null - ? '/api/v1/statuses' - : `/api/v1/statuses/${id}`; - const method = id === null ? 'post' : 'put'; + const id = getState().getIn(['compose', 'id']) + const endpoint = id === null + ? '/api/v1/statuses' + : `/api/v1/statuses/${id}` + const method = id === null ? 'post' : 'put' - let scheduled_at = getState().getIn(['compose', 'scheduled_at'], null); - if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate(); + let scheduled_at = getState().getIn(['compose', 'scheduled_at'], null) + if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate() - let expires_at = getState().getIn(['compose', 'expires_at'], null); + let expires_at = getState().getIn(['compose', 'expires_at'], null) - if (expires_at) { - if (expires_at === STATUS_EXPIRATION_OPTION_5_MINUTES) { - expires_at = moment.utc().add('5', 'minute').toDate() - } else if (expires_at === STATUS_EXPIRATION_OPTION_60_MINUTES) { - expires_at = moment.utc().add('60', 'minute').toDate() - } else if (expires_at === STATUS_EXPIRATION_OPTION_6_HOURS) { - expires_at = moment.utc().add('6', 'hour').toDate() - } else if (expires_at === STATUS_EXPIRATION_OPTION_24_HOURS) { - expires_at = moment.utc().add('24', 'hour').toDate() - } else if (expires_at === STATUS_EXPIRATION_OPTION_3_DAYS) { - expires_at = moment.utc().add('3', 'day').toDate() - } else if (expires_at === STATUS_EXPIRATION_OPTION_7_DAYS) { - expires_at = moment.utc().add('7', 'day').toDate() - } + if (expires_at) { + if (expires_at === STATUS_EXPIRATION_OPTION_5_MINUTES) { + expires_at = moment.utc().add('5', 'minute').toDate() + } else if (expires_at === STATUS_EXPIRATION_OPTION_60_MINUTES) { + expires_at = moment.utc().add('60', 'minute').toDate() + } else if (expires_at === STATUS_EXPIRATION_OPTION_6_HOURS) { + expires_at = moment.utc().add('6', 'hour').toDate() + } else if (expires_at === STATUS_EXPIRATION_OPTION_24_HOURS) { + expires_at = moment.utc().add('24', 'hour').toDate() + } else if (expires_at === STATUS_EXPIRATION_OPTION_3_DAYS) { + expires_at = moment.utc().add('3', 'day').toDate() + } else if (expires_at === STATUS_EXPIRATION_OPTION_7_DAYS) { + expires_at = moment.utc().add('7', 'day').toDate() } + } - if (isMobile(window.innerWidth) && router && isStandalone) { - router.history.goBack() - } + if (isMobile(window.innerWidth) && router && isStandalone) { + router.history.goBack() + } - api(getState)[method](endpoint, { - status, - markdown, - expires_at, - scheduled_at, - autoJoinGroup, - isPrivateGroup, - in_reply_to_id: inReplyToId, - quote_of_id: getState().getIn(['compose', 'quote_of_id'], null), - media_ids: media.map(item => item.get('id')), - sensitive: getState().getIn(['compose', 'sensitive']), - spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), - visibility: getState().getIn(['compose', 'privacy']), - poll: getState().getIn(['compose', 'poll'], null), - group_id: groupId || null, - }, { - headers: { - 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), - }, - }).then(function (response) { - handleComposeSubmit(dispatch, getState, response, status); - }).catch(function (error) { - dispatch(submitComposeFail(error)); - }); - }; -}; + api(getState)[method](endpoint, { + status, + markdown, + expires_at, + scheduled_at, + autoJoinGroup, + isPrivateGroup, + in_reply_to_id: inReplyToId, + quote_of_id: getState().getIn(['compose', 'quote_of_id'], null), + media_ids: media.map(item => item.get('id')), + sensitive: getState().getIn(['compose', 'sensitive']), + spoiler_text: getState().getIn(['compose', 'spoiler_text'], ''), + visibility: getState().getIn(['compose', 'privacy']), + poll: getState().getIn(['compose', 'poll'], null), + group_id: groupId || null, + }, { + headers: { + 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), + }, + }).then((response) => { + handleComposeSubmit(dispatch, getState, response, status) + }).catch((error) => { + dispatch(submitComposeFail(error)) + }) +} -export function submitComposeRequest() { - return { - type: COMPOSE_SUBMIT_REQUEST, - }; -}; +const submitComposeRequest = () => ({ + type: COMPOSE_SUBMIT_REQUEST, +}) -export function submitComposeSuccess(status) { - return { - type: COMPOSE_SUBMIT_SUCCESS, - status: status, - }; -}; +const submitComposeSuccess = (status) => ({ + type: COMPOSE_SUBMIT_SUCCESS, + status, +}) -export function clearCompose() { - return { - type: COMPOSE_CLEAR, - }; -}; +const submitComposeFail = (error) => ({ + type: COMPOSE_SUBMIT_FAIL, + error, +}) -export function submitComposeFail(error) { - return { - type: COMPOSE_SUBMIT_FAIL, - error: error, +/** + * + */ +export const uploadCompose = (files) => (dispatch, getState) => { + if (!me) return + + const uploadLimit = 4 + const media = getState().getIn(['compose', 'media_attachments']) + const pending = getState().getIn(['compose', 'pending_media_attachments']) + const progress = new Array(files.length).fill(0) + let total = Array.from(files).reduce((a, v) => a + v.size, 0) + + if (files.length + media.size + pending > uploadLimit) { + // dispatch(showAlert(undefined, messages.uploadErrorLimit)) + return + } + + if (getState().getIn(['compose', 'poll'])) { + // dispatch(showAlert(undefined, messages.uploadErrorPoll)) + return + } + + dispatch(uploadComposeRequest()) + + for (const [i, f] of Array.from(files).entries()) { + if (media.size + i > 3) break + + resizeImage(f).then((file) => { + const data = new FormData() + data.append('file', file) + // Account for disparity in size of original image and resized data + total += file.size - f.size + + return api(getState).post('/api/v1/media', data, { + onUploadProgress: ({ loaded }) => { + progress[i] = loaded + dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)) + }, + }).then(({ data }) => dispatch(uploadComposeSuccess(data))) + }).catch((error) => dispatch(uploadComposeFail(error, true))) } } -export function uploadCompose(files) { - return function (dispatch, getState) { - if (!me) return +const uploadComposeRequest = () => ({ + type: COMPOSE_UPLOAD_REQUEST, +}) - const uploadLimit = 4 - const media = getState().getIn(['compose', 'media_attachments']) - const pending = getState().getIn(['compose', 'pending_media_attachments']) - const progress = new Array(files.length).fill(0); - let total = Array.from(files).reduce((a, v) => a + v.size, 0); +const uploadComposeProgress = (loaded, total) => ({ + type: COMPOSE_UPLOAD_PROGRESS, + loaded: loaded, + total: total, +}) - if (files.length + media.size + pending > uploadLimit) { - // dispatch(showAlert(undefined, messages.uploadErrorLimit)); - return; - } +const uploadComposeSuccess = (media) => ({ + type: COMPOSE_UPLOAD_SUCCESS, + media: media, +}) - if (getState().getIn(['compose', 'poll'])) { - // dispatch(showAlert(undefined, messages.uploadErrorPoll)); - return; - } +const uploadComposeFail = (error) => ({ + type: COMPOSE_UPLOAD_FAIL, + error, +}) - dispatch(uploadComposeRequest()); +/** + * + */ +export const changeUploadCompose = (id, params) => (dispatch, getState) => { + if (!me) return - for (const [i, f] of Array.from(files).entries()) { - if (media.size + i > 3) break; + dispatch(changeUploadComposeRequest()) - resizeImage(f).then((file) => { - const data = new FormData(); - data.append('file', file); - // Account for disparity in size of original image and resized data - total += file.size - f.size; + api(getState).put(`/api/v1/media/${id}`, params).then((response) => { + dispatch(changeUploadComposeSuccess(response.data)) + }).catch((error) => { + dispatch(changeUploadComposeFail(id, error)) + }) +} - return api(getState).post('/api/v1/media', data, { - onUploadProgress: function({ loaded }){ - progress[i] = loaded; - dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); - }, - }).then(({ data }) => dispatch(uploadComposeSuccess(data))); - }).catch(error => dispatch(uploadComposeFail(error, true))); - }; - }; -}; +const changeUploadComposeRequest = () => ({ + type: COMPOSE_UPLOAD_CHANGE_REQUEST, +}) -export function changeUploadCompose(id, params) { - return (dispatch, getState) => { - if (!me) return; +const changeUploadComposeSuccess = (media) => ({ + type: COMPOSE_UPLOAD_CHANGE_SUCCESS, + media: media, +}) - dispatch(changeUploadComposeRequest()); +const changeUploadComposeFail = (error, decrement = false) => ({ + type: COMPOSE_UPLOAD_CHANGE_FAIL, + error, + decrement: decrement, +}) - api(getState).put(`/api/v1/media/${id}`, params).then(response => { - dispatch(changeUploadComposeSuccess(response.data)); - }).catch(error => { - dispatch(changeUploadComposeFail(id, error)); - }); - }; -}; +/** + * + */ +export const undoUploadCompose = (media_id) => ({ + type: COMPOSE_UPLOAD_UNDO, + media_id: media_id, +}) -export function changeUploadComposeRequest() { - return { - type: COMPOSE_UPLOAD_CHANGE_REQUEST, - skipLoading: true, - }; -}; -export function changeUploadComposeSuccess(media) { - return { - type: COMPOSE_UPLOAD_CHANGE_SUCCESS, - media: media, - skipLoading: true, - }; -}; - -export function changeUploadComposeFail(error, decrement = false) { - return { - type: COMPOSE_UPLOAD_CHANGE_FAIL, - error: error, - decrement: decrement, - skipLoading: true, - }; -}; - -export function uploadComposeRequest() { - return { - type: COMPOSE_UPLOAD_REQUEST, - skipLoading: true, - }; -}; - -export function uploadComposeProgress(loaded, total) { - return { - type: COMPOSE_UPLOAD_PROGRESS, - loaded: loaded, - total: total, - }; -}; - -export function uploadComposeSuccess(media) { - return { - type: COMPOSE_UPLOAD_SUCCESS, - media: media, - skipLoading: true, - }; -}; - -export function uploadComposeFail(error) { - return { - type: COMPOSE_UPLOAD_FAIL, - error: error, - skipLoading: true, - }; -}; - -export function undoUploadCompose(media_id) { - return { - type: COMPOSE_UPLOAD_UNDO, - media_id: media_id, - }; -}; - -export function clearComposeSuggestions() { +/** + * + */ +export const clearComposeSuggestions = () => { if (cancelFetchComposeSuggestionsAccounts) { - cancelFetchComposeSuggestionsAccounts(); + cancelFetchComposeSuggestionsAccounts() } + return { type: COMPOSE_SUGGESTIONS_CLEAR, - }; -}; + } +} + +/** + * + */ +export const fetchComposeSuggestions = (token) => (dispatch, getState) => { + switch (token[0]) { + case ':': + fetchComposeSuggestionsEmojis(dispatch, getState, token) + break + case '#': + fetchComposeSuggestionsTags(dispatch, getState, token) + break + default: + fetchComposeSuggestionsAccounts(dispatch, getState, token) + break + } +} const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => { if (cancelFetchComposeSuggestionsAccounts) { - cancelFetchComposeSuggestionsAccounts(); + cancelFetchComposeSuggestionsAccounts() } + api(getState).get('/api/v1/accounts/search', { cancelToken: new CancelToken(cancel => { - cancelFetchComposeSuggestionsAccounts = cancel; + cancelFetchComposeSuggestionsAccounts = cancel }), params: { q: token.slice(1), resolve: false, limit: 4, }, - }).then(response => { - dispatch(importFetchedAccounts(response.data)); - dispatch(readyComposeSuggestionsAccounts(token, response.data)); - }).catch(error => { + }).then((response) => { + dispatch(importFetchedAccounts(response.data)) + dispatch(readyComposeSuggestionsAccounts(token, response.data)) + }).catch((error) => { if (!isCancel(error)) { - // dispatch(showAlertForError(error)); + // dispatch(showAlertForError(error)) } - }); -}, 200, { leading: true, trailing: true }); + }) +}, 200, { leading: true, trailing: true }) const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => { - const results = emojiSearch(token.replace(':', ''), { maxResults: 5 }); - dispatch(readyComposeSuggestionsEmojis(token, results)); -}; + const results = emojiSearch(token.replace(':', ''), { maxResults: 5 }) + dispatch(readyComposeSuggestionsEmojis(token, results)) +} const fetchComposeSuggestionsTags = (dispatch, getState, token) => { - dispatch(updateSuggestionTags(token)); -}; + dispatch(updateSuggestionTags(token)) +} -export function fetchComposeSuggestions(token) { - return (dispatch, getState) => { - switch (token[0]) { - case ':': - fetchComposeSuggestionsEmojis(dispatch, getState, token); - break; - case '#': - fetchComposeSuggestionsTags(dispatch, getState, token); - break; - default: - fetchComposeSuggestionsAccounts(dispatch, getState, token); - break; - } - }; -}; +const readyComposeSuggestionsEmojis = (token, emojis) => ({ + type: COMPOSE_SUGGESTIONS_READY, + token, + emojis, +}) -export function readyComposeSuggestionsEmojis(token, emojis) { - return { - type: COMPOSE_SUGGESTIONS_READY, - token, - emojis, - }; -}; - -export const readyComposeSuggestionsAccounts = (token, accounts) => ({ +const readyComposeSuggestionsAccounts = (token, accounts) => ({ type: COMPOSE_SUGGESTIONS_READY, token, accounts, }) -export function selectComposeSuggestion(position, token, suggestion, path) { - return (dispatch, getState) => { - let completion, startPosition; +/** + * + */ +export const selectComposeSuggestion = (position, token, suggestion, path) => (dispatch, getState) => { + let completion, startPosition - if (typeof suggestion === 'object' && suggestion.id) { - completion = suggestion.native || suggestion.colons; - startPosition = position - 1; + if (typeof suggestion === 'object' && suggestion.id) { + completion = suggestion.native || suggestion.colons + startPosition = position - 1 - dispatch(useEmoji(suggestion)); - } else if (suggestion[0] === '#') { - completion = suggestion; - startPosition = position - 1; - } else { - completion = getState().getIn(['accounts', suggestion, 'acct']); - startPosition = position; - } + dispatch(useEmoji(suggestion)) + } else if (suggestion[0] === '#') { + completion = suggestion + startPosition = position - 1 + } else { + completion = getState().getIn(['accounts', suggestion, 'acct']) + startPosition = position + } - dispatch({ - type: COMPOSE_SUGGESTION_SELECT, - position: startPosition, - token, - completion, - path, - }); - }; -}; - -export function updateSuggestionTags(token) { - return { - type: COMPOSE_SUGGESTION_TAGS_UPDATE, + dispatch({ + type: COMPOSE_SUGGESTION_SELECT, + position: startPosition, token, - }; + completion, + path, + }) } -export function updateTagHistory(tags) { - return { - type: COMPOSE_TAG_HISTORY_UPDATE, - tags, - }; -} +/** + * + */ +export const updateSuggestionTags = (token) => ({ + type: COMPOSE_SUGGESTION_TAGS_UPDATE, + token, +}) -export function hydrateCompose() { - return (dispatch, getState) => { - const me = getState().getIn(['meta', 'me']); - const history = tagHistory.get(me); +/** + * + */ +export const updateTagHistory = (tags) => ({ + type: COMPOSE_TAG_HISTORY_UPDATE, + tags, +}) - if (history !== null) { - dispatch(updateTagHistory(history)); - } - }; -} +/** + * + */ +export const mountCompose = () => ({ + type: COMPOSE_MOUNT, +}) -function insertIntoTagHistory(recognizedTags, text) { - return (dispatch, getState) => { - const state = getState(); - const oldHistory = state.getIn(['compose', 'tagHistory']); - const me = state.getIn(['meta', 'me']); - const names = recognizedTags.map(tag => text.match(new RegExp(`#${tag.name}`, 'i'))[0].slice(1)); - const intersectedOldHistory = oldHistory.filter(name => names.findIndex(newName => newName.toLowerCase() === name.toLowerCase()) === -1); +/** + * + */ +export const unmountCompose = () => ({ + type: COMPOSE_UNMOUNT, +}) - names.push(...intersectedOldHistory.toJS()); +/** + * + */ +export const clearCompose = () => ({ + type: COMPOSE_CLEAR, +}) - const newHistory = names.slice(0, 1000); +/** + * + */ +export const changeComposeSensitivity = () => ({ + type: COMPOSE_SENSITIVITY_CHANGE, +}) - tagHistory.set(me, newHistory); - dispatch(updateTagHistory(newHistory)); - }; -} +/** + * + */ +export const changeComposeSpoilerness = () => ({ + type: COMPOSE_SPOILERNESS_CHANGE, +}) -export function mountCompose() { - return { - type: COMPOSE_MOUNT, - }; -}; +/** + * + */ +export const changeComposeSpoilerText = (text) => ({ + type: COMPOSE_SPOILER_TEXT_CHANGE, + text, +}) -export function unmountCompose() { - return { - type: COMPOSE_UNMOUNT, - }; -}; +/** + * + */ +export const changeComposeVisibility = (value) => ({ + type: COMPOSE_VISIBILITY_CHANGE, + value, +}) -export function changeComposeSensitivity() { - return { - type: COMPOSE_SENSITIVITY_CHANGE, - }; -}; +/** + * + */ +export const insertEmojiCompose = (emoji, needsSpace) => ({ + type: COMPOSE_EMOJI_INSERT, + emoji, + needsSpace, +}) -export function changeComposeSpoilerness() { - return { - type: COMPOSE_SPOILERNESS_CHANGE, - }; -}; +/** + * + */ +export const changeComposing = (value) => ({ + type: COMPOSE_COMPOSING_CHANGE, + value, +}) -export function changeComposeSpoilerText(text) { - return { - type: COMPOSE_SPOILER_TEXT_CHANGE, - text, - }; -}; +/** + * + */ +export const addPoll = () => ({ + type: COMPOSE_POLL_ADD, +}) -export function changeComposeVisibility(value) { - return { - type: COMPOSE_VISIBILITY_CHANGE, - value, - }; -}; +/** + * + */ +export const removePoll = () => ({ + type: COMPOSE_POLL_REMOVE, +}) -export function insertEmojiCompose(emoji, needsSpace) { - return { - type: COMPOSE_EMOJI_INSERT, - emoji, - needsSpace, - }; -}; +/** + * + */ +export const addPollOption = (title) => ({ + type: COMPOSE_POLL_OPTION_ADD, + title, +}) -export function changeComposing(value) { - return { - type: COMPOSE_COMPOSING_CHANGE, - value, - }; -}; +/** + * + */ +export const changePollOption = (index, title) => ({ + type: COMPOSE_POLL_OPTION_CHANGE, + index, + title, +}) -export function addPoll() { - return { - type: COMPOSE_POLL_ADD, - }; -}; - -export function removePoll() { - return { - type: COMPOSE_POLL_REMOVE, - }; -}; - -export function addPollOption(title) { - return { - type: COMPOSE_POLL_OPTION_ADD, - title, - }; -}; - -export function changePollOption(index, title) { - return { - type: COMPOSE_POLL_OPTION_CHANGE, - index, - title, - }; -}; - -export function removePollOption(index) { - return { - type: COMPOSE_POLL_OPTION_REMOVE, - index, - }; -}; +/** + * + */ +export const removePollOption = (index) => ({ + type: COMPOSE_POLL_OPTION_REMOVE, + index, +}) /** * diff --git a/app/javascript/gabsocial/actions/custom_emojis.js b/app/javascript/gabsocial/actions/custom_emojis.js index 486e271e..7cc9256a 100644 --- a/app/javascript/gabsocial/actions/custom_emojis.js +++ b/app/javascript/gabsocial/actions/custom_emojis.js @@ -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, }) diff --git a/app/javascript/gabsocial/actions/favorites.js b/app/javascript/gabsocial/actions/favorites.js index a1648cbd..271967aa 100644 --- a/app/javascript/gabsocial/actions/favorites.js +++ b/app/javascript/gabsocial/actions/favorites.js @@ -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, }) /** diff --git a/app/javascript/gabsocial/actions/filters.js b/app/javascript/gabsocial/actions/filters.js index 01559de0..bb831b3d 100644 --- a/app/javascript/gabsocial/actions/filters.js +++ b/app/javascript/gabsocial/actions/filters.js @@ -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, }) \ No newline at end of file diff --git a/app/javascript/gabsocial/actions/groups.js b/app/javascript/gabsocial/actions/groups.js index 1f9d5590..b247daa8 100644 --- a/app/javascript/gabsocial/actions/groups.js +++ b/app/javascript/gabsocial/actions/groups.js @@ -2,58 +2,60 @@ import { Map as ImmutableMap, List as ImmutableList, } from 'immutable' -import api, { getLinks } from '../api'; -import { me } from '../initial_state'; -import { importFetchedAccounts } from './importer'; -import { fetchRelationships } from './accounts'; +import api, { getLinks } from '../api' +import { me } from '../initial_state' +import { importFetchedAccounts } from './importer' +import { fetchRelationships } from './accounts' +import { updateStatusStats } from './statuses' import { + ACCEPTED_GROUP_TABS, GROUP_LIST_SORTING_TYPE_ALPHABETICAL, GROUP_LIST_SORTING_TYPE_MOST_POPULAR, } from '../constants' -export const GROUP_FETCH_REQUEST = 'GROUP_FETCH_REQUEST'; -export const GROUP_FETCH_SUCCESS = 'GROUP_FETCH_SUCCESS'; -export const GROUP_FETCH_FAIL = 'GROUP_FETCH_FAIL'; +export const GROUP_FETCH_REQUEST = 'GROUP_FETCH_REQUEST' +export const GROUP_FETCH_SUCCESS = 'GROUP_FETCH_SUCCESS' +export const GROUP_FETCH_FAIL = 'GROUP_FETCH_FAIL' -export const GROUP_RELATIONSHIPS_FETCH_REQUEST = 'GROUP_RELATIONSHIPS_FETCH_REQUEST'; -export const GROUP_RELATIONSHIPS_FETCH_SUCCESS = 'GROUP_RELATIONSHIPS_FETCH_SUCCESS'; -export const GROUP_RELATIONSHIPS_FETCH_FAIL = 'GROUP_RELATIONSHIPS_FETCH_FAIL'; +export const GROUP_RELATIONSHIPS_FETCH_REQUEST = 'GROUP_RELATIONSHIPS_FETCH_REQUEST' +export const GROUP_RELATIONSHIPS_FETCH_SUCCESS = 'GROUP_RELATIONSHIPS_FETCH_SUCCESS' +export const GROUP_RELATIONSHIPS_FETCH_FAIL = 'GROUP_RELATIONSHIPS_FETCH_FAIL' -export const GROUPS_FETCH_REQUEST = 'GROUPS_FETCH_REQUEST'; -export const GROUPS_FETCH_SUCCESS = 'GROUPS_FETCH_SUCCESS'; -export const GROUPS_FETCH_FAIL = 'GROUPS_FETCH_FAIL'; +export const GROUPS_FETCH_REQUEST = 'GROUPS_FETCH_REQUEST' +export const GROUPS_FETCH_SUCCESS = 'GROUPS_FETCH_SUCCESS' +export const GROUPS_FETCH_FAIL = 'GROUPS_FETCH_FAIL' -export const GROUP_JOIN_REQUEST = 'GROUP_JOIN_REQUEST'; -export const GROUP_JOIN_SUCCESS = 'GROUP_JOIN_SUCCESS'; -export const GROUP_JOIN_FAIL = 'GROUP_JOIN_FAIL'; +export const GROUP_JOIN_REQUEST = 'GROUP_JOIN_REQUEST' +export const GROUP_JOIN_SUCCESS = 'GROUP_JOIN_SUCCESS' +export const GROUP_JOIN_FAIL = 'GROUP_JOIN_FAIL' -export const GROUP_LEAVE_REQUEST = 'GROUP_LEAVE_REQUEST'; -export const GROUP_LEAVE_SUCCESS = 'GROUP_LEAVE_SUCCESS'; -export const GROUP_LEAVE_FAIL = 'GROUP_LEAVE_FAIL'; +export const GROUP_LEAVE_REQUEST = 'GROUP_LEAVE_REQUEST' +export const GROUP_LEAVE_SUCCESS = 'GROUP_LEAVE_SUCCESS' +export const GROUP_LEAVE_FAIL = 'GROUP_LEAVE_FAIL' -export const GROUP_MEMBERS_FETCH_REQUEST = 'GROUP_MEMBERS_FETCH_REQUEST'; -export const GROUP_MEMBERS_FETCH_SUCCESS = 'GROUP_MEMBERS_FETCH_SUCCESS'; -export const GROUP_MEMBERS_FETCH_FAIL = 'GROUP_MEMBERS_FETCH_FAIL'; +export const GROUP_MEMBERS_FETCH_REQUEST = 'GROUP_MEMBERS_FETCH_REQUEST' +export const GROUP_MEMBERS_FETCH_SUCCESS = 'GROUP_MEMBERS_FETCH_SUCCESS' +export const GROUP_MEMBERS_FETCH_FAIL = 'GROUP_MEMBERS_FETCH_FAIL' -export const GROUP_MEMBERS_EXPAND_REQUEST = 'GROUP_MEMBERS_EXPAND_REQUEST'; -export const GROUP_MEMBERS_EXPAND_SUCCESS = 'GROUP_MEMBERS_EXPAND_SUCCESS'; -export const GROUP_MEMBERS_EXPAND_FAIL = 'GROUP_MEMBERS_EXPAND_FAIL'; +export const GROUP_MEMBERS_EXPAND_REQUEST = 'GROUP_MEMBERS_EXPAND_REQUEST' +export const GROUP_MEMBERS_EXPAND_SUCCESS = 'GROUP_MEMBERS_EXPAND_SUCCESS' +export const GROUP_MEMBERS_EXPAND_FAIL = 'GROUP_MEMBERS_EXPAND_FAIL' -export const GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST = 'GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST'; -export const GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS'; -export const GROUP_REMOVED_ACCOUNTS_FETCH_FAIL = 'GROUP_REMOVED_ACCOUNTS_FETCH_FAIL'; +export const GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST = 'GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST' +export const GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS' +export const GROUP_REMOVED_ACCOUNTS_FETCH_FAIL = 'GROUP_REMOVED_ACCOUNTS_FETCH_FAIL' -export const GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST = 'GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST'; -export const GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS'; -export const GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL = 'GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL'; +export const GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST = 'GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST' +export const GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS' +export const GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL = 'GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL' -export const GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST = 'GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST'; -export const GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS'; -export const GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL = 'GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL'; +export const GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST = 'GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST' +export const GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS' +export const GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL = 'GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL' -export const GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST = 'GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST'; -export const GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS'; -export const GROUP_REMOVED_ACCOUNTS_CREATE_FAIL = 'GROUP_REMOVED_ACCOUNTS_CREATE_FAIL'; +export const GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST = 'GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST' +export const GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS = 'GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS' +export const GROUP_REMOVED_ACCOUNTS_CREATE_FAIL = 'GROUP_REMOVED_ACCOUNTS_CREATE_FAIL' export const GROUP_JOIN_REQUESTS_FETCH_REQUEST = 'GROUP_JOIN_REQUESTS_FETCH_REQUEST' export const GROUP_JOIN_REQUESTS_FETCH_SUCCESS = 'GROUP_JOIN_REQUESTS_FETCH_SUCCESS' @@ -69,18 +71,18 @@ export const GROUP_JOIN_REQUESTS_APPROVE_FAIL = 'GROUP_JOIN_REQUESTS_APPROVE_FAI export const GROUP_JOIN_REQUESTS_REJECT_SUCCESS = 'GROUP_JOIN_REQUESTS_REJECT_SUCCESS' export const GROUP_JOIN_REQUESTS_REJECT_FAIL = 'GROUP_JOIN_REQUESTS_REJECT_FAIL' -export const GROUP_REMOVE_STATUS_REQUEST = 'GROUP_REMOVE_STATUS_REQUEST'; -export const GROUP_REMOVE_STATUS_SUCCESS = 'GROUP_REMOVE_STATUS_SUCCESS'; -export const GROUP_REMOVE_STATUS_FAIL = 'GROUP_REMOVE_STATUS_FAIL'; +export const GROUP_REMOVE_STATUS_REQUEST = 'GROUP_REMOVE_STATUS_REQUEST' +export const GROUP_REMOVE_STATUS_SUCCESS = 'GROUP_REMOVE_STATUS_SUCCESS' +export const GROUP_REMOVE_STATUS_FAIL = 'GROUP_REMOVE_STATUS_FAIL' -export const GROUP_UPDATE_ROLE_REQUEST = 'GROUP_UPDATE_ROLE_REQUEST'; -export const GROUP_UPDATE_ROLE_SUCCESS = 'GROUP_UPDATE_ROLE_SUCCESS'; -export const GROUP_UPDATE_ROLE_FAIL = 'GROUP_UPDATE_ROLE_FAIL'; +export const GROUP_UPDATE_ROLE_REQUEST = 'GROUP_UPDATE_ROLE_REQUEST' +export const GROUP_UPDATE_ROLE_SUCCESS = 'GROUP_UPDATE_ROLE_SUCCESS' +export const GROUP_UPDATE_ROLE_FAIL = 'GROUP_UPDATE_ROLE_FAIL' -export const GROUP_CHECK_PASSWORD_RESET = 'GROUP_CHECK_PASSWORD_RESET'; -export const GROUP_CHECK_PASSWORD_REQUEST = 'GROUP_CHECK_PASSWORD_REQUEST'; -export const GROUP_CHECK_PASSWORD_SUCCESS = 'GROUP_CHECK_PASSWORD_SUCCESS'; -export const GROUP_CHECK_PASSWORD_FAIL = 'GROUP_CHECK_PASSWORD_FAIL'; +export const GROUP_CHECK_PASSWORD_RESET = 'GROUP_CHECK_PASSWORD_RESET' +export const GROUP_CHECK_PASSWORD_REQUEST = 'GROUP_CHECK_PASSWORD_REQUEST' +export const GROUP_CHECK_PASSWORD_SUCCESS = 'GROUP_CHECK_PASSWORD_SUCCESS' +export const GROUP_CHECK_PASSWORD_FAIL = 'GROUP_CHECK_PASSWORD_FAIL' export const GROUP_PIN_STATUS_REQUEST = 'GROUP_PIN_STATUS_REQUEST' export const GROUP_PIN_STATUS_SUCCESS = 'GROUP_PIN_STATUS_SUCCESS' @@ -90,6 +92,10 @@ export const GROUP_UNPIN_STATUS_REQUEST = 'GROUP_UNPIN_STATUS_REQUEST' export const GROUP_UNPIN_STATUS_SUCCESS = 'GROUP_UNPIN_STATUS_SUCCESS' export const GROUP_UNPIN_STATUS_FAIL = 'GROUP_UNPIN_STATUS_FAIL' +export const IS_PINNED_GROUP_STATUS_REQUEST = 'IS_PINNED_GROUP_STATUS_REQUEST' +export const IS_PINNED_GROUP_STATUS_SUCCESS = 'IS_PINNED_GROUP_STATUS_SUCCESS' +export const IS_PINNED_GROUP_STATUS_FAIL = 'IS_PINNED_GROUP_STATUS_FAIL' + export const GROUPS_BY_CATEGORY_FETCH_REQUEST = 'GROUPS_BY_CATEGORY_FETCH_REQUEST' export const GROUPS_BY_CATEGORY_FETCH_SUCCESS = 'GROUPS_BY_CATEGORY_FETCH_SUCCESS' export const GROUPS_BY_CATEGORY_FETCH_FAIL = 'GROUPS_BY_CATEGORY_FETCH_FAIL' @@ -103,6 +109,10 @@ export const GROUP_TIMELINE_TOP_SORT = 'GROUP_TIMELINE_TOP_SORT' export const GROUP_SORT = 'GROUP_SORT' +/** + * @description Import a group into redux + * @param {ImmutableMap} group + */ export const importGroup = (group) => (dispatch) => { dispatch(fetchGroupSuccess(group)) } @@ -112,83 +122,85 @@ export const importGroups = (groups) => (dispatch) => { groups.map((group) => dispatch(fetchGroupSuccess(group))) } -export const fetchGroup = (id) => (dispatch, getState) => { - dispatch(fetchGroupRelationships([id])); +/** + * @description Fetch a group with the given groupId + * @param {string} groupId + */ +export const fetchGroup = (groupId) => (dispatch, getState) => { + if (!groupId) return - if (getState().getIn(['groups', id])) { - return; - } + dispatch(fetchGroupRelationships([groupId])) - dispatch(fetchGroupRequest(id)); + // Check if exists already + if (getState().getIn(['groups', groupId])) return - api(getState).get(`/api/v1/groups/${id}`) - .then(({ data }) => dispatch(fetchGroupSuccess(data))) - .catch(err => dispatch(fetchGroupFail(id, err))); -}; + dispatch(fetchGroupRequest(groupId)) -export const fetchGroupRequest = id => ({ + api(getState).get(`/api/v1/groups/${groupId}`) + .then((response) => dispatch(fetchGroupSuccess(response.data))) + .catch((err) => dispatch(fetchGroupFail(groupId, err))) +} + +const fetchGroupRequest = (groupId) => ({ type: GROUP_FETCH_REQUEST, - id, -}); + groupId, +}) -export const fetchGroupSuccess = group => ({ +const fetchGroupSuccess = (group) => ({ type: GROUP_FETCH_SUCCESS, group, -}); +}) -export const fetchGroupFail = (id, error) => ({ +const fetchGroupFail = (groupId, error) => ({ type: GROUP_FETCH_FAIL, - id, + groupId, error, -}); +}) -export function fetchGroupRelationships(groupIds) { - return (dispatch, getState) => { - if (!me) return; +/** + * @description Fetch relationships for the given groupIds and current user. For example + * if the current user is a member, admin, mod or not. + * @param {Array} groupIds + */ +export const fetchGroupRelationships = (groupIds) => (dispatch, getState) => { + if (!me || !Array.isArray(groupIds)) return - const loadedRelationships = getState().get('group_relationships'); - const newGroupIds = groupIds.filter(id => loadedRelationships.get(id, null) === null); + const loadedRelationships = getState().get('group_relationships') + const newGroupIds = groupIds.filter((id) => loadedRelationships.get(id, null) === null) - if (newGroupIds.length === 0) { - return; - } + if (newGroupIds.length === 0) return - dispatch(fetchGroupRelationshipsRequest(newGroupIds)); + dispatch(fetchGroupRelationshipsRequest(newGroupIds)) - api(getState).get(`/api/v1/groups/${newGroupIds[0]}/relationships?${newGroupIds.map(id => `id[]=${id}`).join('&')}`).then(response => { - dispatch(fetchGroupRelationshipsSuccess(response.data)); - }).catch(error => { - dispatch(fetchGroupRelationshipsFail(error)); - }); - }; -}; + api(getState).get(`/api/v1/groups/${newGroupIds[0]}/relationships?${newGroupIds.map(id => `id[]=${id}`).join('&')}`).then((response) => { + dispatch(fetchGroupRelationshipsSuccess(response.data)) + }).catch((error) => { + dispatch(fetchGroupRelationshipsFail(error)) + }) +} -export function fetchGroupRelationshipsRequest(ids) { - return { - type: GROUP_RELATIONSHIPS_FETCH_REQUEST, - ids, - skipLoading: true, - }; -}; +const fetchGroupRelationshipsRequest = (groupIds) => ({ + type: GROUP_RELATIONSHIPS_FETCH_REQUEST, + groupIds, +}) -export function fetchGroupRelationshipsSuccess(relationships) { - return { - type: GROUP_RELATIONSHIPS_FETCH_SUCCESS, - relationships, - skipLoading: true, - }; -}; +const fetchGroupRelationshipsSuccess = (relationships) => ({ + type: GROUP_RELATIONSHIPS_FETCH_SUCCESS, + relationships, +}) -export function fetchGroupRelationshipsFail(error) { - return { - type: GROUP_RELATIONSHIPS_FETCH_FAIL, - error, - skipLoading: true, - }; -}; +const fetchGroupRelationshipsFail = (error) => ({ + type: GROUP_RELATIONSHIPS_FETCH_FAIL, + error, +}) -export const fetchGroups = (tab) => (dispatch, getState) => { - if (!me && tab !== 'featured') return +/** + * @description Fetch all groups (limited uniquely per tab, non paginated) by tab. Import + * groups and fetch relationships for each if tab !== member. + * @param {String} tab + */ +export const fetchGroupsByTab = (tab) => (dispatch, getState) => { + if (!me && tab !== 'featured' || ACCEPTED_GROUP_TABS.indexOf(tab) === -1) return // Don't refetch or fetch when loading const isLoading = getState().getIn(['group_lists', tab, 'isLoading']) @@ -198,32 +210,41 @@ export const fetchGroups = (tab) => (dispatch, getState) => { dispatch(fetchGroupsRequest(tab)) - api(getState).get('/api/v1/groups?tab=' + tab) - .then(({ data }) => { - dispatch(fetchGroupsSuccess(data, tab)) - dispatch(fetchGroupRelationships(data.map(item => item.id))) + api(getState).get(`/api/v1/groups?tab=${tab}`) + .then((response) => { + dispatch(fetchGroupsSuccess(response.data, tab)) + if (tab !== 'member') { + dispatch(fetchGroupRelationships(response.data.map(item => item.id))) + } }) .catch((err) => dispatch(fetchGroupsFail(err, tab))) } -export const fetchGroupsRequest = (tab) => ({ +const fetchGroupsRequest = (tab) => ({ type: GROUPS_FETCH_REQUEST, tab, -}); +}) export const fetchGroupsSuccess = (groups, tab) => ({ type: GROUPS_FETCH_SUCCESS, groups, tab, -}); +}) -export const fetchGroupsFail = (error, tab) => ({ +const fetchGroupsFail = (error, tab) => ({ type: GROUPS_FETCH_FAIL, error, tab, -}); +}) +/** + * @description Fetch all groups (limited to 100, non paginated) by category. Import groups + * and fetch relationships for each. + * @param {String} category + */ export const fetchGroupsByCategory = (category) => (dispatch, getState) => { + if (!category) return + // Don't refetch or fetch when loading const isLoading = getState().getIn(['group_lists', 'by_category', category, 'isLoading'], false) @@ -232,31 +253,38 @@ export const fetchGroupsByCategory = (category) => (dispatch, getState) => { dispatch(fetchGroupsByCategoryRequest(category)) api(getState).get(`/api/v1/groups/_/category/${category}`) - .then(({ data }) => { - dispatch(fetchGroupsByCategorySuccess(data, category)) - dispatch(fetchGroupRelationships(data.map(item => item.id))) + .then((response) => { + dispatch(fetchGroupsByCategorySuccess(response.data, category)) + dispatch(fetchGroupRelationships(response.data.map(item => item.id))) }) .catch((err) => dispatch(fetchGroupsByCategoryFail(err, category))) } -export const fetchGroupsByCategoryRequest = (category) => ({ +const fetchGroupsByCategoryRequest = (category) => ({ type: GROUPS_BY_CATEGORY_FETCH_REQUEST, category, }) -export const fetchGroupsByCategorySuccess = (groups, category) => ({ +const fetchGroupsByCategorySuccess = (groups, category) => ({ type: GROUPS_BY_CATEGORY_FETCH_SUCCESS, groups, category, }) -export const fetchGroupsByCategoryFail = (error, category) => ({ +const fetchGroupsByCategoryFail = (error, category) => ({ type: GROUPS_BY_CATEGORY_FETCH_FAIL, error, category, }) +/** + * @description Fetch all groups (limited to 100, non paginated) by tag. Import groups + * and fetch relationships for each. + * @param {String} tag + */ export const fetchGroupsByTag = (tag) => (dispatch, getState) => { + if (!tag) return + // Don't refetch or fetch when loading const isLoading = getState().getIn(['group_lists', 'by_tag', tag, 'isLoading'], false) @@ -265,9 +293,9 @@ export const fetchGroupsByTag = (tag) => (dispatch, getState) => { dispatch(fetchGroupsByTagRequest(tag)) api(getState).get(`/api/v1/groups/_/tag/${tag}`) - .then(({ data }) => { - dispatch(fetchGroupsByTagSuccess(data, tag)) - dispatch(fetchGroupRelationships(data.map(item => item.id))) + .then((response) => { + dispatch(fetchGroupsByTagSuccess(response.data, tag)) + dispatch(fetchGroupRelationships(response.data.map(item => item.id))) }) .catch((err) => dispatch(fetchGroupsByTagFail(err, tag))) } @@ -289,539 +317,498 @@ export const fetchGroupsByTagFail = (error, tag) => ({ tag, }) -export function joinGroup(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(joinGroupRequest(id)); - - api(getState).post(`/api/v1/groups/${id}/accounts`).then(response => { - dispatch(joinGroupSuccess(response.data)); - }).catch(error => { - dispatch(joinGroupFail(id, error)); - }); - }; -}; - -export function leaveGroup(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(leaveGroupRequest(id)); - - api(getState).delete(`/api/v1/groups/${id}/accounts`).then(response => { - dispatch(leaveGroupSuccess(response.data)); - }).catch(error => { - dispatch(leaveGroupFail(id, error)); - }); - }; -}; - -export function joinGroupRequest(id) { - return { - type: GROUP_JOIN_REQUEST, - id, - }; -}; - -export function joinGroupSuccess(relationship) { - return { - type: GROUP_JOIN_SUCCESS, - relationship - }; -}; - -export function joinGroupFail(error) { - return { - type: GROUP_JOIN_FAIL, - error, - }; -}; - -export function leaveGroupRequest(id) { - return { - type: GROUP_LEAVE_REQUEST, - id, - }; -}; - -export function leaveGroupSuccess(relationship) { - return { - type: GROUP_LEAVE_SUCCESS, - relationship, - }; -}; - -export function leaveGroupFail(error) { - return { - type: GROUP_LEAVE_FAIL, - error, - }; -}; - -export function fetchMembers(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(fetchMembersRequest(id)); - - api(getState).get(`/api/v1/groups/${id}/accounts`).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(importFetchedAccounts(response.data)); - dispatch(fetchMembersSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(fetchMembersFail(id, error)); - }); - }; -}; - -export function fetchMembersRequest(id) { - return { - type: GROUP_MEMBERS_FETCH_REQUEST, - id, - }; -}; - -export function fetchMembersSuccess(id, accounts, next) { - return { - type: GROUP_MEMBERS_FETCH_SUCCESS, - id, - accounts, - next, - }; -}; - -export function fetchMembersFail(id, error) { - return { - type: GROUP_MEMBERS_FETCH_FAIL, - id, - error, - }; -}; - -export function expandMembers(id) { - return (dispatch, getState) => { - if (!me) return; - - const url = getState().getIn(['user_lists', 'groups', id, 'next']) - const isLoading = getState().getIn(['user_lists', 'groups', id, 'isLoading']) - - if (url === null || isLoading) return - - dispatch(expandMembersRequest(id)); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(importFetchedAccounts(response.data)); - dispatch(expandMembersSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(expandMembersFail(id, error)); - }); - }; -}; - -export function expandMembersRequest(id) { - return { - type: GROUP_MEMBERS_EXPAND_REQUEST, - id, - }; -}; - -export function expandMembersSuccess(id, accounts, next) { - return { - type: GROUP_MEMBERS_EXPAND_SUCCESS, - id, - accounts, - next, - }; -}; - -export function expandMembersFail(id, error) { - return { - type: GROUP_MEMBERS_EXPAND_FAIL, - id, - error, - }; -}; - -export function fetchRemovedAccounts(id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(fetchRemovedAccountsRequest(id)); - - api(getState).get(`/api/v1/groups/${id}/removed_accounts`).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(importFetchedAccounts(response.data)); - dispatch(fetchRemovedAccountsSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(fetchRemovedAccountsFail(id, error)); - }); - }; -}; - -export function fetchRemovedAccountsRequest(id) { - return { - type: GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST, - id, - }; -}; - -export function fetchRemovedAccountsSuccess(id, accounts, next) { - return { - type: GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS, - id, - accounts, - next, - }; -}; - -export function fetchRemovedAccountsFail(id, error) { - return { - type: GROUP_REMOVED_ACCOUNTS_FETCH_FAIL, - id, - error, - }; -}; - -export function expandRemovedAccounts(id) { - return (dispatch, getState) => { - if (!me) return; - - const url = getState().getIn(['user_lists', 'group_removed_accounts', id, 'next']); - const isLoading = getState().getIn(['user_lists', 'group_removed_accounts', id, 'isLoading']) - - if (url === null || isLoading) return - - dispatch(expandRemovedAccountsRequest(id)); - - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next'); - - dispatch(importFetchedAccounts(response.data)); - dispatch(expandRemovedAccountsSuccess(id, response.data, next ? next.uri : null)); - dispatch(fetchRelationships(response.data.map(item => item.id))); - }).catch(error => { - dispatch(expandRemovedAccountsFail(id, error)); - }); - }; -}; - -export function expandRemovedAccountsRequest(id) { - return { - type: GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST, - id, - }; -}; - -export function expandRemovedAccountsSuccess(id, accounts, next) { - return { - type: GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS, - id, - accounts, - next, - }; -}; - -export function expandRemovedAccountsFail(id, error) { - return { - type: GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL, - id, - error, - }; -}; - -export function removeRemovedAccount(groupId, id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(removeRemovedAccountRequest(groupId, id)); - - api(getState).delete(`/api/v1/groups/${groupId}/removed_accounts?account_id=${id}`).then(response => { - dispatch(removeRemovedAccountSuccess(groupId, id)); - }).catch(error => { - dispatch(removeRemovedAccountFail(groupId, id, error)); - }); - }; -}; - -export function removeRemovedAccountRequest(groupId, id) { - return { - type: GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST, - groupId, - id, - }; -}; - -export function removeRemovedAccountSuccess(groupId, id) { - return { - type: GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS, - groupId, - id, - }; -}; - -export function removeRemovedAccountFail(groupId, id, error) { - return { - type: GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL, - groupId, - id, - error, - }; -}; - -export function createRemovedAccount(groupId, id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(createRemovedAccountRequest(groupId, id)); - - api(getState).post(`/api/v1/groups/${groupId}/removed_accounts?account_id=${id}`).then(response => { - dispatch(createRemovedAccountSuccess(groupId, id)); - }).catch(error => { - dispatch(createRemovedAccountFail(groupId, id, error)); - }); - }; -}; - -export function createRemovedAccountRequest(groupId, id) { - return { - type: GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST, - groupId, - id, - }; -}; - -export function createRemovedAccountSuccess(groupId, id) { - return { - type: GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS, - groupId, - id, - }; -}; - -export function createRemovedAccountFail(groupId, id, error) { - return { - type: GROUP_REMOVED_ACCOUNTS_CREATE_FAIL, - groupId, - id, - error, - }; -}; - -export function groupRemoveStatus(groupId, id) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(groupRemoveStatusRequest(groupId, id)); - - api(getState).delete(`/api/v1/groups/${groupId}/statuses/${id}`).then(response => { - dispatch(groupRemoveStatusSuccess(groupId, id)); - }).catch(error => { - dispatch(groupRemoveStatusFail(groupId, id, error)); - }); - }; -}; - -export function groupRemoveStatusRequest(groupId, id) { - return { - type: GROUP_REMOVE_STATUS_REQUEST, - groupId, - id, - }; -}; - -export function groupRemoveStatusSuccess(groupId, id) { - return { - type: GROUP_REMOVE_STATUS_SUCCESS, - groupId, - id, - }; -}; - -export function groupRemoveStatusFail(groupId, id, error) { - return { - type: GROUP_REMOVE_STATUS_FAIL, - groupId, - id, - error, - }; -}; - -export function updateRole(groupId, id, role) { - return (dispatch, getState) => { - if (!me) return; - - dispatch(updateRoleRequest(groupId, id)); - - api(getState).patch(`/api/v1/groups/${groupId}/accounts?account_id=${id}`, { role }).then(response => { - dispatch(updateRoleSuccess(groupId, id)); - }).catch(error => { - dispatch(updateRoleFail(groupId, id, error)); - }); - }; -}; - -export function updateRoleRequest(groupId, id) { - return { - type: GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST, - groupId, - id, - }; -}; - -export function updateRoleSuccess(groupId, id) { - return { - type: GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS, - groupId, - id, - }; -}; - -export function updateRoleFail(groupId, id, error) { - return { - type: GROUP_REMOVED_ACCOUNTS_CREATE_FAIL, - groupId, - id, - error, - }; -}; - -export function checkGroupPassword(groupId, password) { - return (dispatch, getState) => { - if (!me) return - - dispatch(checkGroupPasswordRequest()) - - api(getState).post(`/api/v1/groups/${groupId}/password`, { password }).then((response) => { - dispatch(joinGroupSuccess(response.data)) - dispatch(checkGroupPasswordSuccess()) - }).catch(error => { - dispatch(checkGroupPasswordFail(error)) - }) - } +/** + * @description Join group with the given groupId and return group relationships + * @param {String} groupId + */ +export const joinGroup = (groupId) => (dispatch, getState) => { + if (!me || !groupId) return + + dispatch(joinGroupRequest(groupId)) + + api(getState).post(`/api/v1/groups/${groupId}/accounts`).then((response) => { + dispatch(joinGroupSuccess(response.data)) + }).catch((error) => { + dispatch(joinGroupFail(groupId, error)) + }) } -export function checkGroupPasswordReset() { - return { - type: GROUP_CHECK_PASSWORD_RESET, - } +const joinGroupRequest = (groupId) => ({ + type: GROUP_JOIN_REQUEST, + groupId, +}) + +const joinGroupSuccess = (relationship) => ({ + type: GROUP_JOIN_SUCCESS, + relationship +}) + +const joinGroupFail = (error) => ({ + type: GROUP_JOIN_FAIL, + error, +}) + +/** + * @description Leave group with the given groupId and return group relationships + * @param {String} groupId + */ +export const leaveGroup = (groupId) => (dispatch, getState) => { + if (!me || !groupId) return + + dispatch(leaveGroupRequest(groupId)) + + api(getState).delete(`/api/v1/groups/${groupId}/accounts`).then((response) => { + dispatch(leaveGroupSuccess(response.data)) + }).catch((error) => { + dispatch(leaveGroupFail(groupId, error)) + }) } -export function checkGroupPasswordRequest() { - return { - type: GROUP_CHECK_PASSWORD_REQUEST, - } +const leaveGroupRequest = (groupId) => ({ + type: GROUP_LEAVE_REQUEST, + groupId, +}) + +const leaveGroupSuccess = (relationship) => ({ + type: GROUP_LEAVE_SUCCESS, + relationship, +}) + +const leaveGroupFail = (error) => ({ + type: GROUP_LEAVE_FAIL, + error, +}) + +/** + * @description Fetch members for the given groupId and imports paginated accounts + * and sets in user_lists reducer. + * @param {String} groupId + */ +export const fetchMembers = (groupId) => (dispatch, getState) => { + if (!me || !groupId) return + + dispatch(fetchMembersRequest(groupId)) + + api(getState).get(`/api/v1/groups/${groupId}/accounts`).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(fetchMembersSuccess(groupId, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(fetchMembersFail(groupId, error)) + }) } -export function checkGroupPasswordSuccess() { - return { - type: GROUP_CHECK_PASSWORD_SUCCESS, - } +const fetchMembersRequest = (groupId) => ({ + type: GROUP_MEMBERS_FETCH_REQUEST, + groupId, +}) + +const fetchMembersSuccess = (groupId, accounts, next) => ({ + type: GROUP_MEMBERS_FETCH_SUCCESS, + groupId, + accounts, + next, +}) + +const fetchMembersFail = (groupId, error) => ({ + type: GROUP_MEMBERS_FETCH_FAIL, + groupId, + error, +}) + +/** + * @description Expand members for the given groupId and imports paginated accounts + * and sets in user_lists reducer. + * @param {String} groupId + */ +export const expandMembers = (groupId) => (dispatch, getState) => { + if (!me || !groupId) return + + const url = getState().getIn(['user_lists', 'groups', groupId, 'next']) + const isLoading = getState().getIn(['user_lists', 'groups', groupId, 'isLoading']) + + if (url === null || isLoading) return + + dispatch(expandMembersRequest(groupId)) + + api(getState).get(url).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(expandMembersSuccess(groupId, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(expandMembersFail(groupId, error)) + }) } -export function checkGroupPasswordFail(error) { - return { - type: GROUP_CHECK_PASSWORD_FAIL, - error, - } +const expandMembersRequest = (groupId) => ({ + type: GROUP_MEMBERS_EXPAND_REQUEST, + groupId, +}) +`` +const expandMembersSuccess = (groupId, accounts, next) => ({ + type: GROUP_MEMBERS_EXPAND_SUCCESS, + groupId, + accounts, + next, +}) + +const expandMembersFail = (groupId, error) => ({ + type: GROUP_MEMBERS_EXPAND_FAIL, + groupId, + error, +}) + +/** + * @description Fetch removed accounts for the given groupId and imports paginated + * accounts and sets in user_lists reducer. + * @param {String} groupId + */ +export const fetchRemovedAccounts = (groupId) => (dispatch, getState) => { + if (!me || !groupId) return + + dispatch(fetchRemovedAccountsRequest(groupId)) + + api(getState).get(`/api/v1/groups/${groupId}/removed_accounts`).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(fetchRemovedAccountsSuccess(groupId, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(fetchRemovedAccountsFail(groupId, error)) + }) } -export function fetchJoinRequests(id) { - return (dispatch, getState) => { - if (!me) return +const fetchRemovedAccountsRequest = (groupId) => ({ + type: GROUP_REMOVED_ACCOUNTS_FETCH_REQUEST, + groupId, +}) - dispatch(fetchJoinRequestsRequest(id)) +const fetchRemovedAccountsSuccess = (groupId, accounts, next) => ({ + type: GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS, + groupId, + accounts, + next, +}) - api(getState).get(`/api/v1/groups/${id}/join_requests`).then((response) => { - const next = getLinks(response).refs.find(link => link.rel === 'next') +const fetchRemovedAccountsFail = (groupId, error) => ({ + type: GROUP_REMOVED_ACCOUNTS_FETCH_FAIL, + groupId, + error, +}) - dispatch(importFetchedAccounts(response.data)) - dispatch(fetchJoinRequestsSuccess(id, response.data, next ? next.uri : null)) - dispatch(fetchRelationships(response.data.map(item => item.id))) - }).catch((error) => { - dispatch(fetchJoinRequestsFail(id, error)) - }) - } +/** + * @description Expand likes for the given statusId and imports paginated accounts + * and sets in user_lists reducer. + * @param {String} statusId + */ +export const expandRemovedAccounts = (groupId) => (dispatch, getState) => { + if (!me || !groupId) return + + const url = getState().getIn(['user_lists', 'group_removed_accounts', groupId, 'next']) + const isLoading = getState().getIn(['user_lists', 'group_removed_accounts', groupId, 'isLoading']) + + if (url === null || isLoading) return + + dispatch(expandRemovedAccountsRequest(groupId)) + + api(getState).get(url).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(expandRemovedAccountsSuccess(groupId, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(expandRemovedAccountsFail(groupId, error)) + }) } -export function fetchJoinRequestsRequest(id) { - return { - type: GROUP_JOIN_REQUESTS_FETCH_REQUEST, - id, - } +const expandRemovedAccountsRequest = (groupId) => ({ + type: GROUP_REMOVED_ACCOUNTS_EXPAND_REQUEST, + groupId, +}) + +const expandRemovedAccountsSuccess = (groupId, accounts, next) => ({ + type: GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS, + groupId, + accounts, + next, +}) + +const expandRemovedAccountsFail = (groupId, error) => ({ + type: GROUP_REMOVED_ACCOUNTS_EXPAND_FAIL, + groupId, + error, +}) + +/** + * @description Remove a "removed account" from a group with the given groupId and accountId. + * @param {String} groupId + * @param {String} accountId + */ +export const removeRemovedAccount = (groupId, accountId) => (dispatch, getState) => { + if (!me || !groupId || !accountId) return + + dispatch(removeRemovedAccountRequest(groupId, accountId)) + + api(getState).delete(`/api/v1/groups/${groupId}/removed_accounts?account_id=${accountId}`).then((response) => { + dispatch(removeRemovedAccountSuccess(groupId, accountId)) + }).catch((error) => { + dispatch(removeRemovedAccountFail(groupId, accountId, error)) + }) } -export function fetchJoinRequestsSuccess(id, accounts, next) { - return { - type: GROUP_JOIN_REQUESTS_FETCH_SUCCESS, - id, - accounts, - next, - } +const removeRemovedAccountRequest = (groupId, accountId) => ({ + type: GROUP_REMOVED_ACCOUNTS_REMOVE_REQUEST, + groupId, + accountId, +}) + +const removeRemovedAccountSuccess = (groupId, accountId) => ({ + type: GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS, + groupId, + accountId, +}) + +const removeRemovedAccountFail = (groupId, accountId, error) => ({ + type: GROUP_REMOVED_ACCOUNTS_REMOVE_FAIL, + groupId, + accountId, + error, +}) + +/** + * @description Remove an account with given accountId from group with given groupId + * @param {String} groupId + * @param {String} accountId + */ +export const createRemovedAccount = (groupId, accountId) => (dispatch, getState) => { + if (!me) return + + dispatch(createRemovedAccountRequest(groupId, accountId)) + + api(getState).post(`/api/v1/groups/${groupId}/removed_accounts?account_id=${accountId}`).then((response) => { + dispatch(createRemovedAccountSuccess(groupId, accountId)) + }).catch((error) => { + dispatch(createRemovedAccountFail(groupId, accountId, error)) + }) } -export function fetchJoinRequestsFail(id, error) { - return { - type: GROUP_JOIN_REQUESTS_FETCH_FAIL, - id, - error, - } +const createRemovedAccountRequest = (groupId, accountId) => ({ + type: GROUP_REMOVED_ACCOUNTS_CREATE_REQUEST, + groupId, + accountId, +}) + +const createRemovedAccountSuccess = (groupId, accountId) => ({ + type: GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS, + groupId, + accountId, +}) + +const createRemovedAccountFail = (groupId, accountId, error) => ({ + type: GROUP_REMOVED_ACCOUNTS_CREATE_FAIL, + groupId, + accountId, + error, +}) + +/** + * @description Remove a status from a group with given groupId and statusId. Then + * remove the status from the group timeline on success. + * @param {String} groupId + * @param {String} statusId + */ +export const groupRemoveStatus = (groupId, statusId) => (dispatch, getState) => { + if (!me || !groupId || !statusId) return + + dispatch(groupRemoveStatusRequest(groupId, statusId)) + + api(getState).delete(`/api/v1/groups/${groupId}/statuses/${statusId}`).then((response) => { + dispatch(groupRemoveStatusSuccess(groupId, statusId)) + }).catch((error) => { + dispatch(groupRemoveStatusFail(groupId, statusId, error)) + }) } -export function expandJoinRequests(id) { - return (dispatch, getState) => { - if (!me) return +const groupRemoveStatusRequest = (groupId, statusId) => ({ + type: GROUP_REMOVE_STATUS_REQUEST, + groupId, + statusId, +}) - const url = getState().getIn(['user_lists', 'group_join_requests', id, 'next']) - const isLoading = getState().getIn(['user_lists', 'group_join_requests', id, 'isLoading']) +const groupRemoveStatusSuccess = (groupId, statusId) => ({ + type: GROUP_REMOVE_STATUS_SUCCESS, + groupId, + statusId, +}) - if (url === null || isLoading) return +const groupRemoveStatusFail = (groupId, statusId, error) => ({ + type: GROUP_REMOVE_STATUS_FAIL, + groupId, + statusId, + error, +}) - dispatch(expandJoinRequestsRequest(id)) +/** + * @description Update role to admin, moderator for given accountId in given groupId + * @param {String} groupId + * @param {String} accountId + * @param {String} role + */ +export const updateRole = (groupId, accountId, role) => (dispatch, getState) => { + if (!me || !groupId || !accountId || !role) return - api(getState).get(url).then(response => { - const next = getLinks(response).refs.find(link => link.rel === 'next') + dispatch(updateRoleRequest(groupId, accountId)) - dispatch(importFetchedAccounts(response.data)) - dispatch(expandJoinRequestsSuccess(id, response.data, next ? next.uri : null)) - dispatch(fetchRelationships(response.data.map(item => item.id))) - }).catch(error => { - dispatch(expandJoinRequestsFail(id, error)) - }) - } + api(getState).patch(`/api/v1/groups/${groupId}/accounts?account_id=${accountId}`, { role }).then((response) => { + dispatch(updateRoleSuccess(groupId, accountId)) + }).catch((error) => { + dispatch(updateRoleFail(groupId, accountId, error)) + }) } -export function expandJoinRequestsRequest(id) { - return { - type: GROUP_JOIN_REQUESTS_EXPAND_REQUEST, - id, - } +const updateRoleRequest = (groupId, accountId) => ({ + type: GROUP_UPDATE_ROLE_REQUEST, + groupId, + accountId, +}) + +const updateRoleSuccess = (groupId, accountId) => ({ + type: GROUP_UPDATE_ROLE_SUCCESS, + groupId, + accountId, +}) + +const updateRoleFail = (groupId, accountId, error) => ({ + type: GROUP_UPDATE_ROLE_FAIL, + groupId, + accountId, + error, +}) + +/** + * @description Reset the group password check map when group password model opens + */ +export const checkGroupPasswordReset = () => ({ + type: GROUP_CHECK_PASSWORD_RESET, +}) + +/** + * + */ +export const checkGroupPassword = (groupId, password) => (dispatch, getState) => { + if (!me || !groupId) return + + dispatch(checkGroupPasswordRequest()) + + api(getState).post(`/api/v1/groups/${groupId}/password`, { password }).then((response) => { + dispatch(joinGroupSuccess(response.data)) + dispatch(checkGroupPasswordSuccess()) + }).catch((error) => { + dispatch(checkGroupPasswordFail(error)) + }) } -export function expandJoinRequestsSuccess(id, accounts, next) { - return { - type: GROUP_JOIN_REQUESTS_EXPAND_SUCCESS, - id, - accounts, - next, - } +const checkGroupPasswordRequest = () => ({ + type: GROUP_CHECK_PASSWORD_REQUEST, +}) + +const checkGroupPasswordSuccess = () => ({ + type: GROUP_CHECK_PASSWORD_SUCCESS, +}) + +export const checkGroupPasswordFail = (error) => ({ + type: GROUP_CHECK_PASSWORD_FAIL, + error, +}) + +/** + * + */ +export const fetchJoinRequests = (id) => (dispatch, getState) => { + if (!me) return + + dispatch(fetchJoinRequestsRequest(id)) + + api(getState).get(`/api/v1/groups/${id}/join_requests`).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(fetchJoinRequestsSuccess(id, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(fetchJoinRequestsFail(id, error)) + }) } -export function expandJoinRequestsFail(id, error) { - return { - type: GROUP_JOIN_REQUESTS_EXPAND_FAIL, - id, - error, - } +const fetchJoinRequestsRequest = (id) => ({ + type: GROUP_JOIN_REQUESTS_FETCH_REQUEST, + id, +}) + +const fetchJoinRequestsSuccess = (id, accounts, next) => ({ + type: GROUP_JOIN_REQUESTS_FETCH_SUCCESS, + id, + accounts, + next, +}) + +const fetchJoinRequestsFail = (id, error) => ({ + type: GROUP_JOIN_REQUESTS_FETCH_FAIL, + id, + error, +}) + +/** + * + */ +export const expandJoinRequests = (id) => (dispatch, getState) => { + if (!me) return + + const url = getState().getIn(['user_lists', 'group_join_requests', id, 'next']) + const isLoading = getState().getIn(['user_lists', 'group_join_requests', id, 'isLoading']) + + if (url === null || isLoading) return + + dispatch(expandJoinRequestsRequest(id)) + + api(getState).get(url).then((response) => { + const next = getLinks(response).refs.find(link => link.rel === 'next') + + dispatch(importFetchedAccounts(response.data)) + dispatch(expandJoinRequestsSuccess(id, response.data, next ? next.uri : null)) + dispatch(fetchRelationships(response.data.map(item => item.id))) + }).catch((error) => { + dispatch(expandJoinRequestsFail(id, error)) + }) } +const expandJoinRequestsRequest = (id) => ({ + type: GROUP_JOIN_REQUESTS_EXPAND_REQUEST, + id, +}) + +const expandJoinRequestsSuccess = (id, accounts, next) => ({ + type: GROUP_JOIN_REQUESTS_EXPAND_SUCCESS, + id, + accounts, + next, +}) + +const expandJoinRequestsFail = (id, error) => ({ + type: GROUP_JOIN_REQUESTS_EXPAND_FAIL, + id, + error, +}) + +/** + * + */ export const approveJoinRequest = (accountId, groupId) => (dispatch, getState) => { if (!me) return @@ -832,23 +819,22 @@ export const approveJoinRequest = (accountId, groupId) => (dispatch, getState) = }) } -export function approveJoinRequestSuccess(accountId, groupId) { - return { - type: GROUP_JOIN_REQUESTS_APPROVE_SUCCESS, - accountId, - groupId, - } -} +const approveJoinRequestSuccess = (accountId, groupId) => ({ + type: GROUP_JOIN_REQUESTS_APPROVE_SUCCESS, + accountId, + groupId, +}) -export function approveJoinRequestFail(accountId, groupId, error) { - return { - type: GROUP_JOIN_REQUESTS_APPROVE_FAIL, - accountId, - groupId, - error, - } -} +const approveJoinRequestFail = (accountId, groupId, error) => ({ + type: GROUP_JOIN_REQUESTS_APPROVE_FAIL, + accountId, + groupId, + error, +}) +/** + * + */ export const rejectJoinRequest = (accountId, groupId) => (dispatch, getState) => { if (!me) return @@ -859,99 +845,125 @@ export const rejectJoinRequest = (accountId, groupId) => (dispatch, getState) => }) } -export function rejectJoinRequestSuccess(accountId, groupId) { - return { - type: GROUP_JOIN_REQUESTS_REJECT_SUCCESS, - accountId, - groupId, - } +const rejectJoinRequestSuccess = (accountId, groupId) => ({ + type: GROUP_JOIN_REQUESTS_REJECT_SUCCESS, + accountId, + groupId, +}) + +const rejectJoinRequestFail = (accountId, groupId, error) => ({ + type: GROUP_JOIN_REQUESTS_REJECT_FAIL, + accountId, + groupId, + error, +}) + +/** + * + */ +export const pinGroupStatus = (groupId, statusId) => (dispatch, getState) => { + if (!me || !groupId || !statusId) return + + dispatch(pinGroupStatusRequest(groupId)) + + api(getState).post(`/api/v1/groups/${groupId}/pin`, { statusId }).then((response) => { + dispatch(updateStatusStats(response.data)) + dispatch(pinGroupStatusSuccess(groupId, statusId)) + }).catch((error) => { + dispatch(pinGroupStatusFail(groupId, statusId, error)) + }) } -export function rejectJoinRequestFail(accountId, groupId, error) { - return { - type: GROUP_JOIN_REQUESTS_REJECT_FAIL, - accountId, - groupId, - error, - } +const pinGroupStatusRequest = (groupId) => ({ + type: GROUP_PIN_STATUS_REQUEST, + groupId, +}) + +const pinGroupStatusSuccess = (groupId, statusId) => ({ + type: GROUP_PIN_STATUS_SUCCESS, + groupId, + statusId, +}) + +const pinGroupStatusFail = (groupId, statusId, error) => ({ + type: GROUP_PIN_STATUS_FAIL, + groupId, + statusId, + error, +}) + +/** + * + */ +export const unpinGroupStatus = (groupId, statusId) =>(dispatch, getState) => { + if (!me || !groupId || !statusId) return + + dispatch(unpinGroupStatusRequest(groupId)) + + api(getState).post(`/api/v1/groups/${groupId}/unpin`, { statusId }).then((response) => { + dispatch(updateStatusStats(response.data)) + dispatch(unpinGroupStatusSuccess(groupId, statusId)) + }).catch((error) => { + dispatch(unpinGroupStatusFail(groupId, statusId, error)) + }) } -export function pinGroupStatus(groupId, statusId) { - return (dispatch, getState) => { - if (!me) return +const unpinGroupStatusRequest = (groupId) => ({ + type: GROUP_UNPIN_STATUS_REQUEST, + groupId, +}) - dispatch(pinGroupStatusRequest(groupId)) +const unpinGroupStatusSuccess = (groupId, statusId) => ({ + type: GROUP_UNPIN_STATUS_SUCCESS, + groupId, + statusId, +}) - api(getState).post(`/api/v1/groups/${groupId}/pin`, { statusId }).then((response) => { - dispatch(pinGroupStatusSuccess(groupId, statusId)) - }).catch((error) => { - dispatch(pinGroupStatusFail(groupId, statusId, error)) - }) - } +const unpinGroupStatusFail = (groupId, statusId, error) => ({ + type: GROUP_UNPIN_STATUS_FAIL, + groupId, + statusId, + error, +}) + + +/** + * + */ +export const isPinnedGroupStatus = (groupId, statusId) => (dispatch, getState) => { + if (!me || !groupId || !statusId) return + + dispatch(isPinnedGroupStatusRequest(groupId, statusId)) + + api(getState).get(`/api/v1/groups/${groupId}/pin?statusId=${statusId}`).then((response) => { + dispatch(updateStatusStats(response.data)) + }).catch((error) => { + dispatch(isPinnedGroupStatusFail(groupId, statusId, error)) + }) } -export function pinGroupStatusRequest(groupId) { - return { - type: GROUP_PIN_STATUS_REQUEST, - groupId, - } -} +const isPinnedGroupStatusRequest = (groupId, statusId) => ({ + type: IS_PINNED_GROUP_STATUS_REQUEST, + groupId, + statusId, +}) -export function pinGroupStatusSuccess(groupId, statusId) { - return { - type: GROUP_PIN_STATUS_SUCCESS, - groupId, - statusId, - } -} +const isPinnedGroupStatusSuccess = (groupId, statusId) => ({ + type: IS_PINNED_GROUP_STATUS_SUCCESS, + groupId, + statusId, +}) -export function pinGroupStatusFail(groupId, statusId, error) { - return { - type: GROUP_PIN_STATUS_FAIL, - groupId, - statusId, - error, - } -} - -export function unpinGroupStatus(groupId, statusId) { - return (dispatch, getState) => { - if (!me) return - - dispatch(unpinGroupStatusRequest(groupId)) - - api(getState).post(`/api/v1/groups/${groupId}/unpin`, { statusId }).then((response) => { - dispatch(unpinGroupStatusSuccess(groupId, statusId)) - }).catch((error) => { - dispatch(unpinGroupStatusFail(groupId, statusId, error)) - }) - } -} - -export function unpinGroupStatusRequest(groupId) { - return { - type: GROUP_UNPIN_STATUS_REQUEST, - groupId, - } -} - -export function unpinGroupStatusSuccess(groupId, statusId) { - return { - type: GROUP_UNPIN_STATUS_SUCCESS, - groupId, - statusId, - } -} - -export function unpinGroupStatusFail(groupId, statusId, error) { - return { - type: GROUP_UNPIN_STATUS_FAIL, - groupId, - statusId, - error, - } -} +const isPinnedGroupStatusFail = (groupId, statusId, error) => ({ + type: IS_PINNED_GROUP_STATUS_FAIL, + groupId, + statusId, + error, +}) +/** + * + */ export const sortGroups = (tab, sortType) => (dispatch, getState) => { const groupIdsByTab = getState().getIn(['group_lists', tab, 'items'], ImmutableList()).toJS() const allGroups = getState().get('groups', ImmutableMap()).toJS() @@ -974,16 +986,14 @@ export const sortGroups = (tab, sortType) => (dispatch, getState) => { const sortedGroupsIdsByTab = groupsByTab.map((group) => group.id) dispatch(groupsSort(tab, sortedGroupsIdsByTab)) -}; - -export function groupsSort(tab, groupIds) { - return { - type: GROUP_SORT, - tab, - groupIds, - } } +export const groupsSort = (tab, groupIds) =>({ + type: GROUP_SORT, + tab, + groupIds, +}) + export const setGroupTimelineSort = (sortValue) => (dispatch) => { dispatch({ type: GROUP_TIMELINE_SORT, diff --git a/app/javascript/gabsocial/actions/importer/index.js b/app/javascript/gabsocial/actions/importer/index.js index b9c1630f..c98a256b 100644 --- a/app/javascript/gabsocial/actions/importer/index.js +++ b/app/javascript/gabsocial/actions/importer/index.js @@ -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); } diff --git a/app/javascript/gabsocial/actions/importer/normalizer.js b/app/javascript/gabsocial/actions/importer/normalizer.js index 69c3c26d..7cdbcf12 100644 --- a/app/javascript/gabsocial/actions/importer/normalizer.js +++ b/app/javascript/gabsocial/actions/importer/normalizer.js @@ -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(//g, '\n').replace(/<\/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(//g, '\n').replace(/<\/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 } \ No newline at end of file diff --git a/app/javascript/gabsocial/actions/interactions.js b/app/javascript/gabsocial/actions/interactions.js index 657ee262..2dd2d01a 100644 --- a/app/javascript/gabsocial/actions/interactions.js +++ b/app/javascript/gabsocial/actions/interactions.js @@ -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, -}) \ No newline at end of file + 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, +}) diff --git a/app/javascript/gabsocial/actions/notifications.js b/app/javascript/gabsocial/actions/notifications.js index 99f584b3..5a3d737f 100644 --- a/app/javascript/gabsocial/actions/notifications.js +++ b/app/javascript/gabsocial/actions/notifications.js @@ -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, diff --git a/app/javascript/gabsocial/actions/status_revisions.js b/app/javascript/gabsocial/actions/status_revisions.js index 6425a381..081fe0ba 100644 --- a/app/javascript/gabsocial/actions/status_revisions.js +++ b/app/javascript/gabsocial/actions/status_revisions.js @@ -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, diff --git a/app/javascript/gabsocial/actions/statuses.js b/app/javascript/gabsocial/actions/statuses.js index 3c9074ae..008b29df 100644 --- a/app/javascript/gabsocial/actions/statuses.js +++ b/app/javascript/gabsocial/actions/statuses.js @@ -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, - }; -}; \ No newline at end of file +/** + * + */ +export const updateStatusStats = (data) => ({ + type: UPDATE_STATUS_STATS, + data, +}) \ No newline at end of file diff --git a/app/javascript/gabsocial/actions/streaming.js b/app/javascript/gabsocial/actions/streaming.js index 60c6ae42..e33caeb5 100644 --- a/app/javascript/gabsocial/actions/streaming.js +++ b/app/javascript/gabsocial/actions/streaming.js @@ -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) { + // + }, + } + }) +} \ No newline at end of file diff --git a/app/javascript/gabsocial/actions/suggestions.js b/app/javascript/gabsocial/actions/suggestions.js index efc6a21b..8e4f66aa 100644 --- a/app/javascript/gabsocial/actions/suggestions.js +++ b/app/javascript/gabsocial/actions/suggestions.js @@ -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, diff --git a/app/javascript/gabsocial/actions/timelines.js b/app/javascript/gabsocial/actions/timelines.js index 560d4b92..af65ae18 100644 --- a/app/javascript/gabsocial/actions/timelines.js +++ b/app/javascript/gabsocial/actions/timelines.js @@ -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) +} diff --git a/app/javascript/gabsocial/components/account.js b/app/javascript/gabsocial/components/account.js index 3a771d74..45f17804 100644 --- a/app/javascript/gabsocial/components/account.js +++ b/app/javascript/gabsocial/components/account.js @@ -80,7 +80,7 @@ class Account extends ImmutablePureComponent { ) : - const avatarSize = compact ? 42 : 52 + const avatarSize = compact ? 40 : 52 const dismissBtn = !showDismiss ? null : (