Commiting

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

@ -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)

@ -27,7 +27,7 @@ class Api::V1::FiltersController < Api::BaseController
def destroy
@filter.destroy!
render_empty
render_empty_success
end
private

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -28,7 +28,7 @@ class Api::V1::ListsController < Api::BaseController
def destroy
@list.destroy!
render_empty
render_empty_success
end
private

@ -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

@ -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

@ -26,7 +26,7 @@ class Api::V1::ScheduledStatusesController < Api::BaseController
def destroy
@status.destroy!
render_empty
render_empty_success
end
private

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -7,7 +7,7 @@ class Api::Web::SettingsController < Api::Web::BaseController
setting.data = params[:data]
setting.save!
render_empty
render_empty_success
end
private

@ -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

File diff suppressed because it is too large Load Diff

@ -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,
})
/**

@ -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 })
}

@ -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,
})

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

File diff suppressed because it is too large Load Diff

@ -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,
})

@ -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,
})
/**

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

File diff suppressed because it is too large Load Diff

@ -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);
}

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

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

@ -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,

@ -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,

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

@ -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) {
//
},
}
})
}

@ -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,

@ -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)
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

@ -31,15 +31,3 @@ delegate(document, '.media-spoiler-hide-button', 'click', () => {
});
});
delegate(document, '#domain_block_severity', 'change', ({ target }) => {
const rejectMediaDiv = document.querySelector('.input.with_label.domain_block_reject_media');
const rejectReportsDiv = document.querySelector('.input.with_label.domain_block_reject_reports');
if (rejectMediaDiv) {
rejectMediaDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
}
if (rejectReportsDiv) {
rejectReportsDiv.style.display = (target.value === 'suspend') ? 'none' : 'block';
}
});

@ -35,7 +35,7 @@ class SortingQueryBuilder < BaseService
query = Status.unscoped.without_replies
query = query.joins(:status_stat).order(top_order) unless ['newest'].include? sort_type
query = query.where('statuses.created_at > ?', date_limit)
query = query.where(group: @group) unless group.nil?
query = query.where(group: group) unless group.nil?
query = query.where('statuses.id > ? AND statuses.id <> ?', max_id, max_id) unless max_id.nil? || max_id.empty?
query = query.limit(20)

@ -148,7 +148,7 @@ module AccountInteractions
end
def bookmarked?(status)
status_bookmarks.where(account: self).exists?
status_bookmarks.where(account: self, status: status).exists?
end
def reblogged?(status)

@ -72,6 +72,10 @@ class Group < ApplicationRecord
end
end
def has_password?
return !!self.password && self.password.gsub(/\s+/, "").length > 1 && self.password.to_s != "null"
end
private
def set_password

@ -1,16 +1,15 @@
# frozen_string_literal: true
class GroupRelationshipsPresenter
attr_reader :member, :admin, :moderator, :requested
def initialize(group_ids, current_account_id, **options)
@group_ids = group_ids.map { |a| a.is_a?(Group) ? a.id : a }
@current_account_id = current_account_id
@member = Group.member_map(@group_ids, @current_account_id)
@admin = Group.admin_map(@group_ids, @current_account_id)
@moderator = Group.moderator_map(@group_ids, @current_account_id)
@requested = Group.requested_map(@group_ids, @current_account_id)
end
attr_reader :member, :admin, :moderator, :requested
def initialize(group_ids, current_account_id, **options)
@group_ids = group_ids.map { |a| a.is_a?(Group) ? a.id : a }
@current_account_id = current_account_id
@member = Group.member_map(@group_ids, @current_account_id)
@admin = Group.admin_map(@group_ids, @current_account_id)
@moderator = Group.moderator_map(@group_ids, @current_account_id)
@requested = Group.requested_map(@group_ids, @current_account_id)
end
end

@ -1,7 +1,7 @@
# frozen_string_literal: true
class StatusRelationshipsPresenter
attr_reader :reblogs_map, :favourites_map, :group_pins_map
attr_reader :reblogs_map, :favourites_map
def initialize(statuses, current_account_id = nil, **options)
if current_account_id.nil?

@ -13,7 +13,7 @@ class REST::GroupSerializer < ActiveModel::Serializer
end
def has_password
return !!object.password && object.password.gsub(/\s+/, "").length > 1 && object.password.to_s != "null"
object.has_password?
end
def password

@ -0,0 +1,22 @@
# frozen_string_literal: true
class REST::StatusBookmarkedSerializer < ActiveModel::Serializer
attributes :status_id, :account_id, :bookmarked
def status_id
object.id.to_s
end
def account_id
current_user.account.id
end
def bookmarked
if !current_user.nil?
current_user.account.bookmarked?(object)
else
false
end
end
end

@ -0,0 +1,22 @@
# frozen_string_literal: true
class REST::StatusGroupPinnedSerializer < ActiveModel::Serializer
attributes :status_id, :group_id, :pinned_by_group
def status_id
object.id.to_s
end
def group_id
instance_options[:group_id]
end
def pinned_by_group
if !current_user.nil? || !group_id
!GroupPinnedStatus.where(status_id: status_id, group_id: group_id).empty?
else
false
end
end
end

@ -0,0 +1,22 @@
# frozen_string_literal: true
class REST::StatusPinnedSerializer < ActiveModel::Serializer
attributes :status_id, :account_id, :pinned
def status_id
object.id.to_s
end
def account_id
current_user.account.id
end
def pinned
if !current_user.nil?
current_user.account.pinned?(object)
else
false
end
end
end

@ -3,7 +3,7 @@
class REST::StatusSerializer < ActiveModel::Serializer
attributes :id, :created_at, :revised_at, :in_reply_to_id, :in_reply_to_account_id,
:sensitive, :spoiler_text, :visibility, :language,
:url, :replies_count, :reblogs_count,
:url, :replies_count, :reblogs_count, :pinnable, :pinnable_by_group,
:favourites_count, :quote_of_id, :expires_at, :has_quote
attribute :favourited, if: :current_user?
@ -32,6 +32,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
has_one :preloadable_poll, key: :poll, serializer: REST::PollSerializer
def id
# puts "tilly instance:" + instance_options.inspect
object.id.to_s
end
@ -134,22 +135,14 @@ class REST::StatusSerializer < ActiveModel::Serializer
end
def bookmarked
if instance_options && instance_options[:relationships]
instance_options[:relationships].bookmarks_map[object.id] || false
else
current_user.account.bookmarked?(object)
end
return
end
def pinned
if instance_options && instance_options[:relationships]
instance_options[:relationships].pins_map[object.id] || false
else
current_user.account.pinned?(object)
end
return
end
def pinnable?
def pinnable
current_user? &&
current_user.account_id == object.account_id &&
!object.reblog? &&
@ -157,14 +150,10 @@ class REST::StatusSerializer < ActiveModel::Serializer
end
def pinned_by_group
if instance_options && instance_options[:relationships]
instance_options[:relationships].group_pins_map[object.id] || false
else
false
end
return
end
def pinnable_by_group?
def pinnable_by_group
if object.group_id?
true
else

@ -34,6 +34,14 @@ class REST::StatusStatSerializer < ActiveModel::Serializer
end
end
def reblogs_count
if instance_options && instance_options[:unreblog]
object.reblogs_count - 1
else
object.reblogs_count
end
end
def current_user?
!current_user.nil?
end

@ -3,8 +3,8 @@
class GroupPinnedStatusValidator < ActiveModel::Validator
def validate(groupPin)
groupPin.errors.add(:base, I18n.t('statuses.group_pin_errors.reblog')) if groupPin.status.reblog?
groupPin.errors.add(:base, I18n.t('statuses.pin_errors.ungrouped')) unless groupPin.status.group_id
groupPin.errors.add(:base, I18n.t('statuses.pin_errors.notGroupStatus')) if groupPin.status.group_id != groupPin.group.id
groupPin.errors.add(:base, I18n.t('statuses.group_pin_errors.ungrouped')) unless groupPin.status.group_id
groupPin.errors.add(:base, I18n.t('statuses.group_pin_errors.notGroupStatus')) if groupPin.status.group_id != groupPin.group.id
groupPin.errors.add(:base, I18n.t('statuses.group_pin_errors.limit')) if groupPin.group.group_pinned_statuses.count >= 4
end
end

@ -16,4 +16,4 @@ nl:
status:
attributes:
reblog:
taken: van toot bestaat al
taken: van gab bestaat al

@ -414,7 +414,7 @@ ar:
disabled: لا أحد
title: المستخدِمون المصرح لهم لإرسال الدعوات
show_known_fediverse_at_about_page:
desc_html: عند التثبت ، سوف تظهر toots من جميع fediverse المعروفة على عرض مسبق. وإلا فإنه سيعرض فقط toots المحلية.
desc_html: عند التثبت ، سوف تظهر gabs من جميع fediverse المعروفة على عرض مسبق. وإلا فإنه سيعرض فقط gabs المحلية.
title: إظهار الفيديفرس الموحَّد في خيط المُعايَنة
show_staff_badge:
desc_html: عرض شارة الموظفين على صفحة المستخدم

@ -34,10 +34,10 @@ ast:
people_followed_by: Persones a les que sigue %{name}
people_who_follow: Persones que siguen a %{name}
posts:
one: Toot
other: Toots
posts_tab_heading: Toots
posts_with_replies: Toots y rempuestes
one: Gab
other: Gabs
posts_tab_heading: Gabs
posts_with_replies: Gabs y rempuestes
reserved_username: El nome d'usuariu ta acutáu
roles:
bot: Robó
@ -165,7 +165,7 @@ ast:
exports:
archive_takeout:
date: Data
hint_html: Pues solicitar un archivu colos tos <strong>toots y ficheros xubíos</strong>. Los datos esportaos van tar nel formatu ActivityPub, llexible pa cualesquier software que seya compatible. Pues solicitar un archivu cada 7 díes.
hint_html: Pues solicitar un archivu colos tos <strong>gabs y ficheros xubíos</strong>. Los datos esportaos van tar nel formatu ActivityPub, llexible pa cualesquier software que seya compatible. Pues solicitar un archivu cada 7 díes.
request: Solicitar l'archivu
size: Tamañu
blocks: Xente que bloquiesti
@ -238,7 +238,7 @@ ast:
reblog:
body: "%{name} compartió'l to estáu:"
subject: "%{name} compartió'l to estáu"
title: Compartición nueva de toot
title: Compartición nueva de gab
number:
human:
decimal_units:
@ -309,13 +309,13 @@ ast:
video:
one: "%{count} videu"
other: "%{count} vídeos"
boosted_from_html: Compartióse'l toot dende %{acct_link}
boosted_from_html: Compartióse'l gab dende %{acct_link}
language_detection: Deteutala automáticamente
pin_errors:
limit: Yá fixesti'l númberu máxiumu de toots
ownership: Nun pue fixase'l toot d'otra persona
private: Nun puen fixase los toots que nun seyan públicos
reblog: Nun pue fixase un toot compartíu
limit: Yá fixesti'l númberu máxiumu de gabs
ownership: Nun pue fixase'l gab d'otra persona
private: Nun puen fixase los gabs que nun seyan públicos
reblog: Nun pue fixase un gab compartíu
show_more: Amosar más
title: "%{name}: «%{quote}»"
visibilities:

@ -1,7 +1,7 @@
---
ca:
about:
about_hashtag_html: Aquests són toots públics etiquetats amb <strong>#%{hashtag}</strong>. Pots interactuar amb ells si tens un compte a qualsevol lloc del fediverse.
about_hashtag_html: Aquests són gabs públics etiquetats amb <strong>#%{hashtag}</strong>. Pots interactuar amb ells si tens un compte a qualsevol lloc del fediverse.
about_gabsocial_html: Gab Social és una xarxa social basada en protocols web oberts i en programari lliure i de codi obert. Està descentralitzat com el correu electrònic.
about_this: Quant a
active_count_after: actiu
@ -59,10 +59,10 @@ ca:
pin_errors:
following: Has d'estar seguint la persona que vulguis avalar
posts:
one: Toot
other: Toots
posts_tab_heading: Toots
posts_with_replies: Toots i respostes
one: Gab
other: Gabs
posts_tab_heading: Gabs
posts_with_replies: Gabs i respostes
reserved_username: El nom d'usuari està reservat
roles:
admin: Administrador
@ -214,7 +214,7 @@ ca:
unsuspend_account: "%{name} ha llevat la suspensió del compte de %{target}"
update_custom_emoji: "%{name} ha actualitzat l'emoji %{target}"
update_status: "%{name} estat actualitzat per %{target}"
deleted_status: "(toot suprimit)"
deleted_status: "(gab suprimit)"
title: Registre d'auditoria
custom_emojis:
by_domain: Domini
@ -339,11 +339,11 @@ ca:
relays:
add_new: Afegiu un nou relay
delete: Esborra
description_html: Un <strong>relay de federació</strong> és un servidor intermediari que intercanvia grans volums de toots públics entre servidors que es subscriuen i publiquen en ell. <strong>Pot ajudar a servidors petits i mitjans a descobrir contingut del fedivers</strong>, no fent necessari que els usuaris locals manualment segueixin altres persones de servidors remots.
description_html: Un <strong>relay de federació</strong> és un servidor intermediari que intercanvia grans volums de gabs públics entre servidors que es subscriuen i publiquen en ell. <strong>Pot ajudar a servidors petits i mitjans a descobrir contingut del fedivers</strong>, no fent necessari que els usuaris locals manualment segueixin altres persones de servidors remots.
disable: Inhabilita
disabled: Desactivat
enable: Activat
enable_hint: Una vegada habilitat el teu servidor es subscriurà a tots els toots públics d'aquest relay i començarà a enviar-hi tots els toots públics d'aquest servidor.
enable_hint: Una vegada habilitat el teu servidor es subscriurà a tots els gabs públics d'aquest relay i començarà a enviar-hi tots els gabs públics d'aquest servidor.
enabled: Activat
inbox_url: URL del Relay
pending: S'està esperant l'aprovació del relay
@ -426,7 +426,7 @@ ca:
open: Qualsevol pot registrar-se
title: Mode de registres
show_known_fediverse_at_about_page:
desc_html: Quan s'activa, mostrarà tots els toots de tot el fedivers conegut en vista prèvia. En cas contrari, només es mostraran toots locals.
desc_html: Quan s'activa, mostrarà tots els gabs de tot el fedivers conegut en vista prèvia. En cas contrari, només es mostraran gabs locals.
title: Mostra el fedivers conegut en vista prèvia de la línia de temps
show_staff_badge:
desc_html: Mostra una insígnia de personal en la pàgina d'usuari
@ -596,7 +596,7 @@ ca:
archive_takeout:
date: Data
download: Descarrega larxiu
hint_html: Pots sol·licitar un arxiu dels teus <strong>toots i dels fitxers multimèdia pujats</strong>. Les dades exportades tindran el format ActivityPub, llegible per qualsevol programari compatible. Pots sol·licitar un arxiu cada 7 dies.
hint_html: Pots sol·licitar un arxiu dels teus <strong>gabs i dels fitxers multimèdia pujats</strong>. Les dades exportades tindran el format ActivityPub, llegible per qualsevol programari compatible. Pots sol·licitar un arxiu cada 7 dies.
in_progress: Compilant el teu arxiu...
request: Sol·licita el teu arxiu
size: Tamany
@ -652,8 +652,8 @@ ca:
i_am_html: Sóc %{username} a %{service}.
identity: Identitat
inactive: Inactiu
publicize_checkbox: 'I tooteja això:'
publicize_toot: 'Està provat! Sóc %{username} a %{service}: %{url}'
publicize_checkbox: 'I gab això:'
publicize_gab: 'Està provat! Sóc %{username} a %{service}: %{url}'
status: Estat de verificació
view_proof: Veure la prova
imports:
@ -796,20 +796,20 @@ ca:
remote_interaction:
favourite:
proceed: Procedir a afavorir
prompt: 'Vols marcar com a favorit aquest toot:'
prompt: 'Vols marcar com a favorit aquest gab:'
reblog:
proceed: Procedir a impulsar
prompt: 'Vols impulsar aquest toot:'
prompt: 'Vols impulsar aquest gab:'
reply:
proceed: Procedir a respondre
prompt: 'Vols respondre a aquest toot:'
prompt: 'Vols respondre a aquest gab:'
remote_unfollow:
error: Error
title: Títol
unfollowed: Sense seguir
scheduled_statuses:
over_daily_limit: Has superat el límit de %{limit} toots programats per a aquell dia
over_total_limit: Has superat el limit de %{limit} toots programats
over_daily_limit: Has superat el límit de %{limit} gabs programats per a aquell dia
over_total_limit: Has superat el limit de %{limit} gabs programats
too_soon: La data programada ha de ser futura
sessions:
activity: Última activitat
@ -889,9 +889,9 @@ ca:
open_in_web: Obre en la web
over_character_limit: Límit de caràcters de %{max} superat
pin_errors:
limit: Ja has fixat el màxim nombre de toots
ownership: No es pot fixar el toot d'algú altre
private: No es pot fixar el toot no públic
limit: Ja has fixat el màxim nombre de gabs
ownership: No es pot fixar el gab d'algú altre
private: No es pot fixar el gab no públic
reblog: No es pot fixar un impuls
poll:
total_votes:
@ -909,7 +909,7 @@ ca:
unlisted: No llistat
unlisted_long: Tothom ho pot veure, però no es mostra en la història federada
stream_entries:
pinned: Toot fixat
pinned: Gab fixat
reblogged: ha impulsat
sensitive_content: Contingut sensible
terms:
@ -919,8 +919,8 @@ ca:
<ul>
<li><em>Informació bàsica del compte</em>: Si et registres en aquest servidor, se´t pot demanar que introdueixis un nom d'usuari, una adreça de correu electrònic i una contrasenya. També pots introduir informació de perfil addicional, com ara un nom de visualització i una biografia, i carregar una imatge de perfil i de capçalera. El nom d'usuari, el nom de visualització, la biografia, la imatge de perfil i la imatge de capçalera sempre apareixen públicament.</li>
<li><em>Publicacions, seguiment i altra informació pública</em>: La llista de persones que segueixes s'enumeren públicament i el mateix passa amb els teus seguidors. Quan envies un missatge, la data i l'hora s'emmagatzemen, així com l'aplicació que va enviar el missatge. Els missatges poden contenir multimèdia, com ara imatges i vídeos. Els toots públics i no llistats estan disponibles públicament. En quan tinguis un toot en el teu perfil, aquest també és informació pública. Les teves entrades es lliuren als teus seguidors que en alguns casos significa que es lliuren a diferents servidors en els quals s'hi emmagatzemen còpies. Quan suprimeixes publicacions, també es lliuraran als teus seguidors. L'acció d'impulsar o marcar com a favorit una publicació sempre és pública.</li>
<li><em>Toots directes i per a només seguidors</em>: Totes les publicacions s'emmagatzemen i processen al servidor. Els toots per a només seguidors només es lliuren als teus seguidors i als usuaris que s'esmenten en ells i els toots directes només es lliuren als usuaris esmentats. En alguns casos, significa que es lliuren a diferents servidors i s'hi emmagatzemen còpies. Fem un esforç de bona fe per limitar l'accés a aquestes publicacions només a les persones autoritzades, però és possible que altres servidors no ho facin. Per tant, és important revisar els servidors als quals pertanyen els teus seguidors. Pots canviar la opció de aprovar o rebutjar els nous seguidors manualment a la configuració. <em>Tingues en compte que els operadors del servidor i qualsevol servidor receptor poden visualitzar aquests missatges</em> i els destinataris poden fer una captura de pantalla, copiar-los o tornar-los a compartir. <em>No comparteixis cap informació perillosa a Gab Social.</em></li>
<li><em>Publicacions, seguiment i altra informació pública</em>: La llista de persones que segueixes s'enumeren públicament i el mateix passa amb els teus seguidors. Quan envies un missatge, la data i l'hora s'emmagatzemen, així com l'aplicació que va enviar el missatge. Els missatges poden contenir multimèdia, com ara imatges i vídeos. Els gabs públics i no llistats estan disponibles públicament. En quan tinguis un gab en el teu perfil, aquest també és informació pública. Les teves entrades es lliuren als teus seguidors que en alguns casos significa que es lliuren a diferents servidors en els quals s'hi emmagatzemen còpies. Quan suprimeixes publicacions, també es lliuraran als teus seguidors. L'acció d'impulsar o marcar com a favorit una publicació sempre és pública.</li>
<li><em>Gabs directes i per a només seguidors</em>: Totes les publicacions s'emmagatzemen i processen al servidor. Els gabs per a només seguidors només es lliuren als teus seguidors i als usuaris que s'esmenten en ells i els gabs directes només es lliuren als usuaris esmentats. En alguns casos, significa que es lliuren a diferents servidors i s'hi emmagatzemen còpies. Fem un esforç de bona fe per limitar l'accés a aquestes publicacions només a les persones autoritzades, però és possible que altres servidors no ho facin. Per tant, és important revisar els servidors als quals pertanyen els teus seguidors. Pots canviar la opció de aprovar o rebutjar els nous seguidors manualment a la configuració. <em>Tingues en compte que els operadors del servidor i qualsevol servidor receptor poden visualitzar aquests missatges</em> i els destinataris poden fer una captura de pantalla, copiar-los o tornar-los a compartir. <em>No comparteixis cap informació perillosa a Gab Social.</em></li>
<li><em>IPs i altres metadades</em>: Quan inicies sessió registrem l'adreça IP en que l'has iniciat, així com el nom de l'aplicació o navegador. Totes les sessions registrades estan disponibles per a la teva revisió i revocació a la configuració. L'última adreça IP utilitzada s'emmagatzema durant un màxim de 12 mesos. També podrem conservar els registres que inclouen l'adreça IP de cada sol·licitud al nostre servidor.</li>
</ul>
@ -971,7 +971,7 @@ ca:
<p>No venem, comercialitzem ni transmetem a tercers la teva informació d'identificació personal. Això no inclou tercers de confiança que ens ajuden a operar el nostre lloc, a dur a terme el nostre servei o a servir-te, sempre que aquestes parts acceptin mantenir confidencial aquesta informació. També podem publicar la teva informació quan creiem que l'alliberament és apropiat per complir amb la llei, fer complir les polítiques del nostre lloc o protegir els nostres drets o altres drets, propietat o seguretat.</p>
<p>Els altres servidors de la teva xarxa poden descarregar contingut públic. Els teus toots públics i per a només seguidors es lliuren als servidors on resideixen els teus seguidors i els missatges directes s'envien als servidors dels destinataris, sempre que aquests seguidors o destinataris resideixin en un servidor diferent d'aquest.</p>
<p>Els altres servidors de la teva xarxa poden descarregar contingut públic. Els teus gabs públics i per a només seguidors es lliuren als servidors on resideixen els teus seguidors i els missatges directes s'envien als servidors dels destinataris, sempre que aquests seguidors o destinataris resideixin en un servidor diferent d'aquest.</p>
<p>Quan autoritzes una aplicació a utilitzar el teu compte, segons l'abast dels permisos que aprovis, pot accedir a la teva informació de perfil pública, a la teva llista de seguits, als teus seguidors, a les teves llistes, a totes les teves publicacions i als teus favorits. Les aplicacions mai no poden accedir a la teva adreça de correu electrònic o contrasenya.</p>
@ -1028,7 +1028,7 @@ ca:
explanation:
disable: Mentre el teu compte estigui congelat les dades romandran intactes però no pots dur a terme cap acció fins que no estigui desbloquejat.
silence: Mentre el teu compte estigui limitat només les persones que ja et segueixen veuen les teves dades en aquest servidor i pots ser exclòs de diverses llistes públiques. No obstant això, d'altres encara poden seguir-te manualment.
suspend: El teu compte s'ha suspès i tots els teus toots i fitxers multimèdia penjats s'han eliminat irreversiblement d'aquest servidor i dels servidors on tenies seguidors.
suspend: El teu compte s'ha suspès i tots els teus gabs i fitxers multimèdia penjats s'han eliminat irreversiblement d'aquest servidor i dels servidors on tenies seguidors.
review_server_policies: Revisa les polítiques del servidor
subject:
disable: S'ha congelat el teu compte %{acct}

@ -653,7 +653,7 @@ co:
identity: Identità
inactive: Inattiva
publicize_checkbox: 'È mandà stu statutu:'
publicize_toot: 'Hè pruvata! Sò %{username} nantà %{service}: %{url}'
publicize_gab: 'Hè pruvata! Sò %{username} nantà %{service}: %{url}'
status: Statutu di a verificazione
view_proof: Vede a prova
imports:

@ -1,7 +1,7 @@
---
cs:
about:
about_hashtag_html: Tohle jsou veřejné tooty označené hashtagem <strong>#%{hashtag}</strong>. Pokud máte účet kdekoliv ve fedivesmíru, můžete s nimi interagovat.
about_hashtag_html: Tohle jsou veřejné gabs označené hashtagem <strong>#%{hashtag}</strong>. Pokud máte účet kdekoliv ve fedivesmíru, můžete s nimi interagovat.
about_gabsocial_html: Gab Social je sociální síť založená na otevřených webových protokolech a svobodném, otevřeném softwaru. Je decentralizovaná jako e-mail.
about_this: O tomto serveru
active_count_after: aktivních
@ -30,9 +30,9 @@ cs:
server_stats: 'Statistika serveru:'
source_code: Zdrojový kód
status_count_after:
few: tooty
one: toot
other: tootů
few: gab
one: Gab
other: gab
status_count_before: Kteří napsali
tagline: Sociální síť, která bojuje za svobodu slova, osobní svobodu a volný tok informací online. Všichni jsou vítáni.
terms: Podmínky používání
@ -62,11 +62,11 @@ cs:
pin_errors:
following: Musíte již sledovat osobu, kterou chcete podpořit
posts:
few: Tooty
one: Toot
other: Tootů
posts_tab_heading: Tooty
posts_with_replies: Tooty a odpovědi
few: Gabs
one: Gab
other: Gabs
posts_tab_heading: Gabs
posts_with_replies: Gabs a odpovědi
reserved_username: Toto uživatelské jméno je rezervováno
roles:
admin: Administrátor
@ -175,7 +175,7 @@ cs:
targeted_reports: Nahlášeni ostatními
silence: Utišit
silenced: Utišen/a
statuses: Tooty
statuses: Gabs
subscribe: Odebírat
suspended: Pozastaven/a
title: Účty
@ -199,7 +199,7 @@ cs:
destroy_custom_emoji: "%{name} zničil/a emoji %{target}"
destroy_domain_block: "%{name} odblokoval/a doménu %{target}"
destroy_email_domain_block: "%{name} odebral/a e-mailovou doménu %{target} z černé listiny"
destroy_status: "%{name} odstranil/a toot uživatele %{target}"
destroy_status: "%{name} odstranil/a gab uživatele %{target}"
disable_2fa_user: "%{name} vypnul/a dvoufázové ověřování pro uživatele %{target}"
disable_custom_emoji: "%{name} zakázal/a emoji %{target}"
disable_user: "%{name} zakázal/a přihlašování pro uživatele %{target}"
@ -217,8 +217,8 @@ cs:
unsilence_account: "%{name} odtišil/a účet uživatele %{target}"
unsuspend_account: "%{name} zrušil/a pozastavení účtu uživatele %{target}"
update_custom_emoji: "%{name} aktualizoval/a emoji %{target}"
update_status: "%{name} aktualizoval/a toot uživatele %{target}"
deleted_status: "(smazaný toot)"
update_status: "%{name} aktualizoval/a gab uživatele %{target}"
deleted_status: "(smazaný gab)"
title: Záznam auditu
custom_emojis:
by_domain: Doména
@ -345,11 +345,11 @@ cs:
relays:
add_new: Přidat nový most
delete: Smazat
description_html: "<strong>Federovací most</strong> je přechodný server, který vyměňuje velká množství veřejných tootů mezi servery, které z něj odebírají a publikují na něj. <strong>Může pomoci malým a středně velkým serverům objevovat obsah z fedivesmíru</strong>, což by jinak vyžadovalo, aby místní uživatelé manuálně sledovali jiné lidi na vzdálených serverech."
description_html: "<strong>Federovací most</strong> je přechodný server, který vyměňuje velká množství veřejných gab mezi servery, které z něj odebírají a publikují na něj. <strong>Může pomoci malým a středně velkým serverům objevovat obsah z fedivesmíru</strong>, což by jinak vyžadovalo, aby místní uživatelé manuálně sledovali jiné lidi na vzdálených serverech."
disable: Zakázat
disabled: Zakázáno
enable: Povolit
enable_hint: Je-li tohle povoleno, začne váš server odebírat všechny veřejné tooty z tohoto mostu a odesílat na něj své vlastní veřejné tooty.
enable_hint: Je-li tohle povoleno, začne váš server odebírat všechny veřejné gabs z tohoto mostu a odesílat na něj své vlastní veřejné gabs.
enabled: Povoleno
inbox_url: URL mostu
pending: Čekám na souhlas mostu
@ -392,7 +392,7 @@ cs:
updated_at: Aktualizováno
settings:
activity_api_enabled:
desc_html: Počty lokálně publikovaných tootů, aktivních uživatelů a nových registrací, v týdenních intervalech
desc_html: Počty lokálně publikovaných gab, aktivních uživatelů a nových registrací, v týdenních intervalech
title: Publikovat hromadné statistiky o uživatelské aktivitě
bootstrap_timeline_accounts:
desc_html: Je-li uživatelských jmen více, oddělujte je čárkami. Lze zadat pouze místní a odemknuté účty. Je-li tohle prázdné, jsou výchozí hodnotou všichni místní administrátoři.
@ -432,7 +432,7 @@ cs:
open: Kdokoliv se může registrovat
title: Režim registrací
show_known_fediverse_at_about_page:
desc_html: Je-li tohle zapnuto, zobrazí se v náhledu tooty z celého známého fedivesmíru. Jinak budou zobrazeny pouze místní tooty.
desc_html: Je-li tohle zapnuto, zobrazí se v náhledu gabs z celého známého fedivesmíru. Jinak budou zobrazeny pouze místní gabs.
title: Zobrazit na náhledu časové osy celý známý fedivesmír
show_staff_badge:
desc_html: Zobrazit na stránce uživatele odznak člena personálu
@ -467,8 +467,8 @@ cs:
media:
title: Média
no_media: Žádná média
no_status_selected: Nebyly změněny žádné tooty, neboť žádné nebyly vybrány
title: Tooty účtu
no_status_selected: Nebyly změněny žádné gabs, neboť žádné nebyly vybrány
title: Gabs účtu
with_media: S médii
subscriptions:
callback_url: Zpáteční URL
@ -506,7 +506,7 @@ cs:
settings: 'Změnit volby e-mailu: %{link}'
view: 'Zobrazit:'
view_profile: Zobrazit profil
view_status: Zobrazit toot
view_status: Zobrazit gab
applications:
created: Aplikace úspěšně vytvořena
destroyed: Aplikace úspěšně smazána
@ -603,7 +603,7 @@ cs:
archive_takeout:
date: Datum
download: Stáhnout svůj archiv
hint_html: Můžete si vyžádat archiv vašich <strong>tootů a nahraných médií</strong>. Exportovaná data budou ve formátu ActivityPub a budou čitelná kterýmkoliv kompatibilním softwarem. Archiv si můžete vyžádat každých 7 dní.
hint_html: Můžete si vyžádat archiv vašich <strong>gab a nahraných médií</strong>. Exportovaná data budou ve formátu ActivityPub a budou čitelná kterýmkoliv kompatibilním softwarem. Archiv si můžete vyžádat každých 7 dní.
in_progress: Kompiluji váš archiv…
request: Vyžádat svůj archiv
size: Velikost
@ -660,8 +660,8 @@ cs:
i_am_html: Na %{service} jsem %{username}.
identity: Identita
inactive: Neaktivní
publicize_checkbox: 'A tootnout tohle:'
publicize_toot: 'Je to dokázáno! Na %{service} jsem %{username}: %{url}'
publicize_checkbox: 'A gab tohle:'
publicize_gab: 'Je to dokázáno! Na %{service} jsem %{username}: %{url}'
status: Stav ověření
view_proof: Zobrazit důkaz
imports:
@ -707,7 +707,7 @@ cs:
limit: Dosáhl/a jste maximálního počtu seznamů
media_attachments:
validations:
images_and_video: K tootu, který již obsahuje obrázky, nelze připojit video
images_and_video: K gab, který již obsahuje obrázky, nelze připojit video
too_many: Nelze připojit více než 4 soubory
migrations:
acct: přezdívka@doména nového účtu
@ -731,8 +731,8 @@ cs:
other: "%{count} nových oznámení od vaší poslední návštěvy"
title: Ve vaší nepřítomnosti…
favourite:
body: 'Váš toot si oblíbil/a %{name}:'
subject: "%{name} si oblíbil/a váš toot"
body: 'Váš gab si oblíbil/a %{name}:'
subject: "%{name} si oblíbil/a váš gab"
title: Nové oblíbení
follow:
body: "%{name} vás nyní sleduje!"
@ -749,8 +749,8 @@ cs:
subject: Byl/a jste zmíněn/a uživatelem %{name}
title: Nová zmínka
reblog:
body: 'Váš toot byl repostnutý uživatelem %{name}:'
subject: "%{name} repostnul/a váš toot"
body: 'Váš gab byl repostnutý uživatelem %{name}:'
subject: "%{name} repostnul/a váš gab"
title: Nový repost
number:
human:
@ -807,20 +807,20 @@ cs:
remote_interaction:
favourite:
proceed: Pokračovat k oblíbení
prompt: 'Chcete si oblíbit tento toot:'
prompt: 'Chcete si oblíbit tento gab:'
reblog:
proceed: Pokračovat k repostnutí
prompt: 'Chcete repostnout tento toot:'
prompt: 'Chcete repostnout tento gab:'
reply:
proceed: Pokračovat k odpovězení
prompt: 'Chcete odpovědět na tento toot:'
prompt: 'Chcete odpovědět na tento gab:'
remote_unfollow:
error: Chyba
title: Nadpis
unfollowed: Už nesledujete
scheduled_statuses:
over_daily_limit: Překročil/a jste limit %{limit} plánovaných tootů pro tento den
over_total_limit: Překročil/a jste limit %{limit} plánovaných tootů
over_daily_limit: Překročil/a jste limit %{limit} plánovaných gab pro tento den
over_total_limit: Překročil/a jste limit %{limit} plánovaných gab
too_soon: Plánované datum musí být v budoucnosti
sessions:
activity: Nejnovější aktivita
@ -903,9 +903,9 @@ cs:
open_in_web: Otevřít na webu
over_character_limit: limit %{max} znaků byl překročen
pin_errors:
limit: Už jste si připnul/a maximální počet tootů
ownership: Nelze připnout toot někoho jiného
private: Nelze připnout neveřejné tooty
limit: Už jste si připnul/a maximální počet gab
ownership: Nelze připnout gab někoho jiného
private: Nelze připnout neveřejné gabs
reblog: Nelze připnout repost
poll:
total_votes:
@ -924,7 +924,7 @@ cs:
unlisted: Neuvedené
unlisted_long: Všichni mohou vidět, ale nebudou zahrnuty ve veřejných časových osách
stream_entries:
pinned: Připnutý toot
pinned: Připnutý gab
reblogged: repostnul/a
sensitive_content: Citlivý obsah
terms:
@ -1042,8 +1042,8 @@ cs:
warning:
explanation:
disable: Zatímco je váš účet zmražen, zůstávají data vašeho účtu nedotčená, ale nemůžete vykonávat žádné akce, dokud nebude odemčen.
silence: Zatímco je váš účet omezen, mohou vaše tooty na tomto serveru vidět pouze lidé, kteří váš již sledují, a můžete být vyloučen/a z různých veřejných výpisů. Ostatní vás však pořád mohou manuálně sledovat.
suspend: Váš účet byl pozastaven a všechny vaše tooty a vaše nahrané mediální soubory byly nenávratně odstraněny z tohoto serveru a serverů, na kterých jste měl/a sledující.
silence: Zatímco je váš účet omezen, mohou vaše gabs na tomto serveru vidět pouze lidé, kteří váš již sledují, a můžete být vyloučen/a z různých veřejných výpisů. Ostatní vás však pořád mohou manuálně sledovat.
suspend: Váš účet byl pozastaven a všechny vaše gabs a vaše nahrané mediální soubory byly nenávratně odstraněny z tohoto serveru a serverů, na kterých jste měl/a sledující.
review_server_policies: Posoudit politiku serveru
subject:
disable: Váš účet %{acct} byl zmražen

@ -653,7 +653,7 @@ de:
identity: Identität
inactive: Inaktiv
publicize_checkbox: 'Und poste das:'
publicize_toot: 'Es ist offiziell! Ich bin %{username} auf %{service}: %{url}'
publicize_gab: 'Es ist offiziell! Ich bin %{username} auf %{service}: %{url}'
status: Verifizierungsstatus
view_proof: Zeige Nachweis
imports:

@ -127,11 +127,11 @@ cs:
read:notifications: vidět vaše oznámení
read:reports: vidět vaše nahlášení
read:search: vyhledávat za vás
read:statuses: vidět všechny tooty
read:statuses: vidět všechny gabs
write: měnit všechna data vašeho účtu
write:accounts: měnit váš profil
write:blocks: blokovat účty a domény
write:favourites: oblibovat si tooty
write:favourites: oblibovat si gabs
write:filters: vytvářet filtry
write:follows: sledovat lidi
write:lists: vytvářet seznamy
@ -139,4 +139,4 @@ cs:
write:mutes: skrývat lidi a konverzace
write:notifications: vymazávat vaše oznámení
write:reports: nahlašovat jiné uživatele
write:statuses: publikovat tooty
write:statuses: publikovat gabs

@ -126,13 +126,13 @@ nl:
read:lists: zie jouw lijsten
read:mutes: zie jouw genegeerde gebruikers
read:notifications: zie jouw meldingen
read:reports: zie jouw gerapporteerde toots
read:reports: zie jouw gerapporteerde gabs
read:search: namens jou zoeken
read:statuses: zie alle toots
read:statuses: zie alle gabs
write: alle gegevens van jouw account bewerken
write:accounts: jouw profiel bewerken
write:blocks: accounts en domeinen blokkeren
write:favourites: toots als favoriet markeren
write:favourites: gabs als favoriet markeren
write:filters: filters aanmaken
write:follows: mensen volgen
write:lists: lijsten aanmaken
@ -140,4 +140,4 @@ nl:
write:mutes: mensen en gesprekken negeren
write:notifications: meldingen verwijderen
write:reports: andere mensen rapporteren
write:statuses: toots publiceren
write:statuses: gabs publiceren

@ -681,7 +681,7 @@ en:
identity: Identity
inactive: Inactive
publicize_checkbox: 'And gab this:'
publicize_toot: 'I am %{username} on %{service}: %{url}'
publicize_gab: 'I am %{username} on %{service}: %{url}'
status: Verification status
view_proof: View proof
imports:

@ -649,7 +649,7 @@ eo:
identity: Identeco
inactive: Malaktiva
publicize_checkbox: 'And gab this:'
publicize_toot: 'It is proven! I am %{username} on %{service}: %{url}'
publicize_gab: 'It is proven! I am %{username} on %{service}: %{url}'
status: Confirmo statuso
view_proof: Vidi pruvo
imports:

@ -1,7 +1,7 @@
---
es:
about:
about_hashtag_html: Estos son toots públicos etiquetados con <strong>#%{hashtag}</strong>. Puedes interactuar con ellos si tienes una cuenta en cualquier parte del fediverso.
about_hashtag_html: Estos son gabs públicos etiquetados con <strong>#%{hashtag}</strong>. Puedes interactuar con ellos si tienes una cuenta en cualquier parte del fediverso.
about_gabsocial_html: Gab Social es un servidor de red social <em>libre y de código abierto</em>. Una alternativa <em>descentralizada</em> a plataformas comerciales, que evita el riesgo de que una única compañía monopolice tu comunicación. Cualquiera puede ejecutar Gab Social y participar sin problemas en la <em>red social</em>.
about_this: Acerca de esta instancia
administered_by: 'Administrado por:'
@ -48,10 +48,10 @@ es:
pin_errors:
following: Debes estar siguiendo a la persona a la que quieres aprobar
posts:
one: Toot
other: Toots
posts_tab_heading: Toots
posts_with_replies: Toots con respuestas
one: Gab
other: Gabs
posts_tab_heading: Gabs
posts_with_replies: Gabs con respuestas
reserved_username: El nombre de usuario está reservado
roles:
admin: Administrador
@ -289,11 +289,11 @@ es:
relays:
add_new: Añadir un nuevo relés
delete: Borrar
description_html: Un <strong>relés de federation</strong> es un servidor intermedio que intercambia grandes volúmenes de toots públicos entre servidores que se suscriben y publican en él. <strong>Puede ayudar a servidores pequeños y medianos a descubir contenido del fediverso</strong>, que de otra manera requeriría que los usuarios locales siguiesen manialmente a personas de servidores remotos.
description_html: Un <strong>relés de federation</strong> es un servidor intermedio que intercambia grandes volúmenes de gabs públicos entre servidores que se suscriben y publican en él. <strong>Puede ayudar a servidores pequeños y medianos a descubir contenido del fediverso</strong>, que de otra manera requeriría que los usuarios locales siguiesen manialmente a personas de servidores remotos.
disable: Deshabilitar
disabled: Deshabilitado
enable: Hablitar
enable_hint: Una vez conectado, tu servidor se suscribirá a todos los toots públicos de este relés, y comenzará a enviar los toots públicos de este servidor hacia él.
enable_hint: Una vez conectado, tu servidor se suscribirá a todos los gabs públicos de este relés, y comenzará a enviar los gabs públicos de este servidor hacia él.
enabled: Habilitado
inbox_url: URL del relés
pending: Esperando la aprobación del relés
@ -367,7 +367,7 @@ es:
disabled: Nadie
title: Permitir invitaciones de
show_known_fediverse_at_about_page:
desc_html: Cuando esté activado, se mostrarán toots de todo el fediverso conocido en la vista previa. En otro caso, se mostrarán solamente toots locales.
desc_html: Cuando esté activado, se mostrarán gabs de todo el fediverso conocido en la vista previa. En otro caso, se mostrarán solamente gabs locales.
title: Mostrar fediverso conocido en la vista previa de la historia
show_staff_badge:
desc_html: Mostrar un parche de staff en la página de un usuario
@ -503,7 +503,7 @@ es:
archive_takeout:
date: Fecha
download: Descargar tu archivo
hint_html: Puedes solicitar un archivo de tus <strong>toots y materiales subidos</strong>. Los datos exportados estarán en formato ActivityPub, legibles por cualquier software compatible.
hint_html: Puedes solicitar un archivo de tus <strong>gabs y materiales subidos</strong>. Los datos exportados estarán en formato ActivityPub, legibles por cualquier software compatible.
in_progress: Recopilando tu archivo...
request: Solicitar tu archivo
size: Tamaño
@ -615,8 +615,8 @@ es:
subject: Fuiste mencionado por %{name}
title: Nueva mención
reblog:
body: "%{name} ha retooteado tu estado:"
subject: "%{name} ha retooteado tu estado"
body: "%{name} ha reposted tu estado:"
subject: "%{name} ha reposted tu estado"
title: Nueva difusión
number:
human:
@ -722,8 +722,8 @@ es:
over_character_limit: Límite de caracteres de %{max} superado
pin_errors:
limit: Ya has fijado el número máximo de publicaciones
ownership: El toot de alguien más no puede fijarse
private: Los toots no-públicos no pueden fijarse
ownership: El gab de alguien más no puede fijarse
private: Los gabs no-públicos no pueden fijarse
reblog: Un repost no puede fijarse
show_more: Mostrar más
sign_in_to_participate: Regístrate para participar en la conversación
@ -736,8 +736,8 @@ es:
unlisted: Público, pero no mostrar en la historia federada
unlisted_long: Todos pueden ver, pero no está listado en las líneas de tiempo públicas
stream_entries:
pinned: Toot fijado
reblogged: retooteado
pinned: Gab fijado
reblogged: reposted
sensitive_content: Contenido sensible
terms:
title: Términos del Servicio y Políticas de Privacidad de %{instance}

@ -1,7 +1,7 @@
---
eu:
about:
about_hashtag_html: Hauek <strong>#%{hashtag}</strong> traola duten toot publikoak dira. Fedibertsoko edozein kontu baduzu harremanetan jarri zaitezke.
about_hashtag_html: Hauek <strong>#%{hashtag}</strong> traola duten gab publikoak dira. Fedibertsoko edozein kontu baduzu harremanetan jarri zaitezke.
about_gabsocial_html: Gab Social web protokolo ireki eta libreak darabiltzan gizarte sare bat da. E-mail sarea bezala deszentralizatua da.
about_this: Honi buruz
administered_by: 'Administratzailea(k):'
@ -48,10 +48,10 @@ eu:
pin_errors:
following: Onetsi nahi duzun pertsona aurretik jarraitu behar duzu
posts:
one: Toot
other: Toot
posts_tab_heading: Tootak
posts_with_replies: Toot eta erantzunak
one: Gab
other: Gab
posts_tab_heading: Gabs
posts_with_replies: Gabs eta erantzunak
reserved_username: Erabiltzaile-izena erreserbatuta dago
roles:
admin: Administratzailea
@ -316,11 +316,11 @@ eu:
relays:
add_new: Gehitu hari berria
delete: Ezabatu
description_html: "<strong>Federazio errele</strong> bat zerbitzari tartekari bat da, bertara harpidetutako eta bertan argitaratzen duten zerbitzarien artean toot publiko kopuru handiak banatzen ditu. <strong>Zerbitzari txiki eta ertainei Fedibertsoko edukia aurkitzen laguntzen die</strong>, bestela erabiltzaile lokalek eskuz jarraitu beharko lituzkete urruneko zerbitzarietako erabiltzaileak."
description_html: "<strong>Federazio errele</strong> bat zerbitzari tartekari bat da, bertara harpidetutako eta bertan argitaratzen duten zerbitzarien artean gab publiko kopuru handiak banatzen ditu. <strong>Zerbitzari txiki eta ertainei Fedibertsoko edukia aurkitzen laguntzen die</strong>, bestela erabiltzaile lokalek eskuz jarraitu beharko lituzkete urruneko zerbitzarietako erabiltzaileak."
disable: Desgaitu
disabled: Desgaituta
enable: Gaitu
enable_hint: Behin gaituta, zure zerbitzaria errele honetako toot publiko guztietara harpidetuko da, eta zerbitzari honetako toot publikoak errelera bidaltzen hasiko da.
enable_hint: Behin gaituta, zure zerbitzaria errele honetako gab publiko guztietara harpidetuko da, eta zerbitzari honetako gab publikoak errelera bidaltzen hasiko da.
enabled: Gaituta
inbox_url: Errelearen URLa
pending: Erreleak onartzearen zain
@ -397,7 +397,7 @@ eu:
disabled: Inor ez
title: Baimendu hauen gobidapenak
show_known_fediverse_at_about_page:
desc_html: Txandakatzean, fedibertsu ezagun osoko toot-ak bistaratuko ditu aurrebistan. Bestela, toot lokalak besterik ez ditu erakutsiko.
desc_html: Txandakatzean, fedibertsu ezagun osoko gab-ak bistaratuko ditu aurrebistan. Bestela, gab lokalak besterik ez ditu erakutsiko.
title: Erakutsi fedibertsu ezagun osoko denbora-lerroa aurrebistan
show_staff_badge:
desc_html: Erakutsi langile banda erabiltzailearen orrian
@ -557,7 +557,7 @@ eu:
archive_takeout:
date: Data
download: Deskargatu zure artxiboa
hint_html: Zure <strong>toot eta igotako multimedia</strong>ren artxibo bat eskatu dezakezu. Esportatutako datuak ActivityPub formatua izango dute, bateragarria den edozein programarekin irakurtzeko. Artxiboa 7 egunetan behin eska dezakezu.
hint_html: Zure <strong>gab eta igotako multimedia</strong>ren artxibo bat eskatu dezakezu. Esportatutako datuak ActivityPub formatua izango dute, bateragarria den edozein programarekin irakurtzeko. Artxiboa 7 egunetan behin eska dezakezu.
in_progress: Zure artxiboa biltzen...
request: Eskatu zure artxiboa
size: Tamaina
@ -712,20 +712,20 @@ eu:
remote_interaction:
favourite:
proceed: Bihurtu gogoko
prompt: 'Toot hau gogoko bihurtu nahi duzu:'
prompt: 'Gab hau gogoko bihurtu nahi duzu:'
reblog:
proceed: Eman bultzada
prompt: 'Toot honi bultzada eman nahi diozu:'
prompt: 'Gab honi bultzada eman nahi diozu:'
reply:
proceed: Ekin erantzuteari
prompt: 'Toot honi erantzun nahi diozu:'
prompt: 'Gab honi erantzun nahi diozu:'
remote_unfollow:
error: Errorea
title: Izenburua
unfollowed: Jarraitzeari utzita
scheduled_statuses:
over_daily_limit: Egun horretarako programatutako toot kopuruaren muga gainditu duzu (%{limit})
over_total_limit: Programatutako toot kopuruaren muga gainditu duzu (%{limit})
over_daily_limit: Egun horretarako programatutako gab kopuruaren muga gainditu duzu (%{limit})
over_total_limit: Programatutako gab kopuruaren muga gainditu duzu (%{limit})
too_soon: Programatutako data etorkizunean egon behar du
sessions:
activity: Azken jarduera
@ -798,9 +798,9 @@ eu:
open_in_web: Ireki web-ean
over_character_limit: "%{max}eko karaktere muga gaindituta"
pin_errors:
limit: Gehienez finkatu daitekeen toot kopurua finkatu duzu jada
ownership: Ezin duzu beste norbaiten toot bat finkatu
private: Ezin dira publikoak ez diren toot-ak finkatu
limit: Gehienez finkatu daitekeen gab kopurua finkatu duzu jada
ownership: Ezin duzu beste norbaiten gab bat finkatu
private: Ezin dira publikoak ez diren gab-ak finkatu
reblog: Bultzada bat ezin da finkatu
show_more: Erakutsi gehiago
sign_in_to_participate: Eman izena elkarrizketan parte hartzeko
@ -813,7 +813,7 @@ eu:
unlisted: Zerrendatu gabea
unlisted_long: Edonork ikusi dezake, baina ez da denbora-lerro publikoetan agertzen
stream_entries:
pinned: Finkatutako toot-a
pinned: Finkatutako gab-a
reblogged: "(r)en bultzada"
sensitive_content: 'Kontuz: Eduki hunkigarria'
terms:
@ -931,8 +931,8 @@ eu:
warning:
explanation:
disable: Zure kontua izoztuta dagoen bitartean, zure kontua bere horretan dirau, baina ezin duzu ekintzarik burutu desblokeatzen den arte.
silence: Zure kontua murriztua dagoen bitartean, jada zu jarraitzen zaituztenak besterik ez dituzte zure Toot-ak ikusiko zerbitzari honetan, eta agian zerrenda publikoetatik kenduko zaizu. Hala ere besteek oraindik zu jarraitu zaitzakete.
suspend: Zure kontua kanporatua izan da, zure toot guztiak eta multimedia fitxategiak behin betiko ezabatu dira zerbitzari honetatik, eta zure jarraitzaileen zerbitzarietatik.
silence: Zure kontua murriztua dagoen bitartean, jada zu jarraitzen zaituztenak besterik ez dituzte zure Gab-ak ikusiko zerbitzari honetan, eta agian zerrenda publikoetatik kenduko zaizu. Hala ere besteek oraindik zu jarraitu zaitzakete.
suspend: Zure kontua kanporatua izan da, zure gab guztiak eta multimedia fitxategiak behin betiko ezabatu dira zerbitzari honetatik, eta zure jarraitzaileen zerbitzarietatik.
review_server_policies: Berrikusi zerbitzariko politikak
subject:
disable: Zure %{acct} kontua izoztu da

Some files were not shown because too many files have changed in this diff Show More