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

View File

@ -1,8 +1,8 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::BaseController < ApplicationController class Api::BaseController < ApplicationController
DEFAULT_STATUSES_LIMIT = 18 DEFAULT_STATUSES_LIMIT = 20
DEFAULT_ACCOUNTS_LIMIT = 40 DEFAULT_ACCOUNTS_LIMIT = 20
include RateLimitHeaders include RateLimitHeaders
@ -82,8 +82,8 @@ class Api::BaseController < ApplicationController
end end
end end
def render_empty def render_empty_success(message = nil)
render json: {}, status: 200 render json: { success: true, error: false, message: message }, status: 200
end end
def authorize_if_got_token!(*scopes) def authorize_if_got_token!(*scopes)

View File

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

View File

@ -14,12 +14,12 @@ class Api::V1::FollowRequestsController < Api::BaseController
def authorize def authorize
AuthorizeFollowService.new.call(account, current_account) AuthorizeFollowService.new.call(account, current_account)
NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account)) NotifyService.new.call(current_account, Follow.find_by(account: account, target_account: current_account))
render_empty render_empty_success
end end
def reject def reject
RejectFollowService.new.call(account, current_account) RejectFollowService.new.call(account, current_account)
render_empty render_empty_success
end end
private private

View File

@ -19,29 +19,26 @@ class Api::V1::Groups::AccountsController < Api::BaseController
def create def create
authorize @group, :join? 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 render json: { error: true, message: 'Unable to join group. Incorrect password.' }, status: 422
end else
if @group.is_private if @group.is_private
@group.join_requests << current_account @group.join_requests << current_account
else else
@group.accounts << current_account @group.accounts << current_account
if current_user.allows_group_in_home_feed?
current_user.force_regeneration!
end
end end
render json: @group, serializer: REST::GroupRelationshipSerializer, relationships: relationships render json: @group, serializer: REST::GroupRelationshipSerializer, relationships: relationships
end end
end
def update def update
authorize @group, :update_account? authorize @group, :update_account?
@account = @group.accounts.find(params[:account_id]) @account = @group.accounts.find(params[:account_id])
GroupAccount.where(group: @group, account: @account).update(group_account_params) GroupAccount.where(group: @group, account: @account).update(group_account_params)
render_empty render_empty_success
end end
def destroy def destroy
@ -51,9 +48,6 @@ class Api::V1::Groups::AccountsController < Api::BaseController
else else
authorize @group, :leave? authorize @group, :leave?
GroupAccount.where(group: @group, account_id: current_account.id).destroy_all 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 end
render json: @group, serializer: REST::GroupRelationshipSerializer, relationships: relationships render json: @group, serializer: REST::GroupRelationshipSerializer, relationships: relationships

View File

@ -11,24 +11,28 @@ class Api::V1::Groups::PinsController < Api::BaseController
def create def create
authorize @group, :update? authorize @group, :update?
pin = GroupPinnedStatus.find_by(group: @group, status: @status)
if pin.nil?
GroupPinnedStatus.create!(group: @group, status: @status) GroupPinnedStatus.create!(group: @group, status: @status)
render json: @status, serializer: REST::StatusSerializer 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 end
def show def show
# is status pinned by user of group? render json: @status, serializer: REST::StatusGroupPinnedSerializer, group_id: @group.id
end end
def destroy def destroy
authorize @group, :update? authorize @group, :update?
pin = GroupPinnedStatus.find_by(group: @group, status: @status) pin = GroupPinnedStatus.find_by(group: @group, status: @status)
if pin if pin
pin.destroy! pin.destroy!
end end
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusGroupPinnedSerializer, group_id: @group.id
end end
private private

View File

@ -23,7 +23,7 @@ class Api::V1::Groups::RemovedAccountsController < Api::BaseController
@account = @group.accounts.find(params[:account_id]) @account = @group.accounts.find(params[:account_id])
@group.removed_accounts << @account @group.removed_accounts << @account
GroupAccount.where(group: @group, account: @account).destroy_all GroupAccount.where(group: @group, account: @account).destroy_all
render_empty render_empty_success
end end
def destroy def destroy
@ -31,7 +31,7 @@ class Api::V1::Groups::RemovedAccountsController < Api::BaseController
@account = @group.removed_accounts.find(params[:account_id]) @account = @group.removed_accounts.find(params[:account_id])
GroupRemovedAccount.where(group: @group, account: @account).destroy_all GroupRemovedAccount.where(group: @group, account: @account).destroy_all
render_empty render_empty_success
end end
private private

View File

@ -16,12 +16,12 @@ class Api::V1::GroupsController < Api::BaseController
@groups = Group.where(id: @groupIds).limit(150).all @groups = Group.where(id: @groupIds).limit(150).all
when 'new' when 'new'
if !current_user 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 end
@groups = Group.where(is_archived: false).limit(24).order('created_at DESC').all @groups = Group.where(is_archived: false).limit(24).order('created_at DESC').all
when 'member' when 'member'
if !current_user 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 end
@groups = Group.joins(:group_accounts).where(is_archived: false, group_accounts: { account: current_account }).order('group_accounts.id DESC').all @groups = Group.joins(:group_accounts).where(is_archived: false, group_accounts: { account: current_account }).order('group_accounts.id DESC').all
when 'admin' when 'admin'
@ -36,7 +36,7 @@ class Api::V1::GroupsController < Api::BaseController
def by_category def by_category
if !current_user 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 end
@groupCategory = nil @groupCategory = nil
@ -54,7 +54,7 @@ class Api::V1::GroupsController < Api::BaseController
def by_tag def by_tag
if !current_user 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 end
@groups = [] @groups = []
@ -94,7 +94,7 @@ class Api::V1::GroupsController < Api::BaseController
@group.is_archived = true @group.is_archived = true
@group.save! @group.save!
render_empty render_empty_success
end end
def destroy_status def destroy_status
@ -102,7 +102,7 @@ class Api::V1::GroupsController < Api::BaseController
status = Status.find(params[:status_id]) status = Status.find(params[:status_id])
GroupUnlinkStatusService.new.call(current_account, @group, status) GroupUnlinkStatusService.new.call(current_account, @group, status)
render_empty render_empty_success
end end
def approve_status def approve_status
@ -110,7 +110,7 @@ class Api::V1::GroupsController < Api::BaseController
status = Status.find(params[:status_id]) status = Status.find(params[:status_id])
GroupApproveStatusService.new.call(current_account, @group, status) GroupApproveStatusService.new.call(current_account, @group, status)
render_empty render_empty_success
end end
private private

View File

@ -21,12 +21,12 @@ class Api::V1::Lists::AccountsController < Api::BaseController
end end
end end
render_empty render_empty_success
end end
def destroy def destroy
ListAccount.where(list: @list, account_id: account_ids).destroy_all ListAccount.where(list: @list, account_id: account_ids).destroy_all
render_empty render_empty_success
end end
private private

View File

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

View File

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

View File

@ -21,12 +21,12 @@ class Api::V1::NotificationsController < Api::BaseController
def clear def clear
current_account.notifications.delete_all current_account.notifications.delete_all
render_empty render_empty_success
end end
def mark_read def mark_read
current_account.notifications.find(params[:id]).mark_read! current_account.notifications.find(params[:id]).mark_read!
render_empty render_empty_success
end end
private private

View File

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

View File

@ -9,14 +9,15 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
def create def create
if current_user.account.is_pro if current_user.account.is_pro
@status = bookmarked_status @status = bookmarked_status
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusBookmarkedSerializer
else else
render json: { error: 'You need to be a GabPRO member to access this' }, status: 422 render json: { error: 'You need to be a GabPRO member to access this' }, status: 422
end end
end end
def show def show
# is status bookmarked by user? @status = requested_status
render json: @status, serializer: REST::StatusBookmarkedSerializer
end end
def destroy def destroy
@ -27,7 +28,7 @@ class Api::V1::Statuses::BookmarksController < Api::BaseController
bookmark = StatusBookmark.find_by!(account: current_user.account, status: @status) bookmark = StatusBookmark.find_by!(account: current_user.account, status: @status)
bookmark.destroy! 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 else
render json: { error: 'You need to be a GabPRO member to access this' }, status: 422 render json: { error: 'You need to be a GabPRO member to access this' }, status: 422
end end

View File

@ -8,7 +8,6 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
def create def create
@status = favourited_status @status = favourited_status
puts "tilly -- status: " + @status.inspect
render json: @status, serializer: REST::StatusStatSerializer render json: @status, serializer: REST::StatusStatSerializer
end end
@ -18,7 +17,10 @@ class Api::V1::Statuses::FavouritesController < Api::BaseController
UnfavouriteWorker.perform_async(current_user.account_id, @status.id) 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 end
private private

View File

@ -8,12 +8,17 @@ class Api::V1::Statuses::PinsController < Api::BaseController
before_action :set_status before_action :set_status
def create def create
pin = StatusPin.find_by(account: current_account, status: @status)
if pin.nil?
StatusPin.create!(account: current_account, status: @status) StatusPin.create!(account: current_account, status: @status)
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusPinnedSerializer
else
return render json: { error: 'Status is already pinned' }, status: 500
end
end end
def show def show
# is status pinned by user? render json: @status, serializer: REST::StatusPinnedSerializer
end end
def destroy def destroy
@ -23,7 +28,7 @@ class Api::V1::Statuses::PinsController < Api::BaseController
pin.destroy! pin.destroy!
end end
render json: @status, serializer: REST::StatusSerializer render json: @status, serializer: REST::StatusPinnedSerializer
end end
private private

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true # frozen_string_literal: true
class Api::V1::Statuses::RepostedByAccountsController < Api::BaseController class Api::V1::Statuses::RebloggedByAccountsController < Api::BaseController
include Authorization include Authorization
before_action -> { authorize_if_got_token! :read, :'read:accounts' } before_action -> { authorize_if_got_token! :read, :'read:accounts' }
@ -36,13 +36,13 @@ class Api::V1::Statuses::RepostedByAccountsController < Api::BaseController
def next_path def next_path
if records_continue? 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
end end
def prev_path def prev_path
unless @accounts.empty? 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
end end

View File

@ -7,22 +7,25 @@ class Api::V1::Statuses::ReblogsController < Api::BaseController
before_action :require_user! before_action :require_user!
def create def create
if !current_user.account.local? || !status_for_reblog.local @relog = status_for_reblog
return render json: { error: 'Invalid action' }, status: 422 ReblogService.new.call(current_user.account, @relog, reblog_params)
end render json: @relog, serializer: REST::StatusStatSerializer
@status = ReblogService.new.call(current_user.account, status_for_reblog, reblog_params)
render json: @status, serializer: REST::StatusSerializer
end end
def destroy def destroy
@status = status_for_destroy.reblog @my_relog = status_for_destroy
@reblogs_map = { @status.id => false } @original_status = @my_relog.reblog
authorize status_for_destroy, :unreblog? authorize @my_relog, :unreblog?
RemovalWorker.perform_async(status_for_destroy.id)
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 end
private private

View File

@ -23,7 +23,7 @@ class Api::V1::SuggestionsController < Api::BaseController
def destroy def destroy
PotentialFriendshipTracker.remove(current_account.id, params[:id]) PotentialFriendshipTracker.remove(current_account.id, params[:id])
render_empty render_empty_success
end end
end end

View File

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

View File

@ -66,6 +66,7 @@ module SignatureVerification
return account unless verify_signature(account, signature, compare_signed_string).nil? 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}" @signature_verification_failure_reason = "Verification failed for #{account.username}@#{account.domain} #{account.uri}"
@signed_request_account = nil @signed_request_account = nil
end end

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

@ -19,6 +19,7 @@ import {
import { defineMessages } from 'react-intl' import { defineMessages } from 'react-intl'
import { openModal, closeModal } from './modal' import { openModal, closeModal } from './modal'
import { import {
MODAL_COMPOSE,
STATUS_EXPIRATION_OPTION_5_MINUTES, STATUS_EXPIRATION_OPTION_5_MINUTES,
STATUS_EXPIRATION_OPTION_60_MINUTES, STATUS_EXPIRATION_OPTION_60_MINUTES,
STATUS_EXPIRATION_OPTION_6_HOURS, STATUS_EXPIRATION_OPTION_6_HOURS,
@ -90,19 +91,43 @@ const messages = defineMessages({
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' }, uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
}) })
const COMPOSE_PANEL_BREAKPOINT = 600 + (285 * 1) + (10 * 1) /**
*
*/
export const hydrateCompose = () => (dispatch, getState) => {
const me = getState().getIn(['meta', 'me'])
const history = tagHistory.get(me)
export const ensureComposeIsVisible = (getState, routerHistory) => { if (history !== null) {
if (!getState().getIn(['compose', 'mounted']) && window.innerWidth < COMPOSE_PANEL_BREAKPOINT) { dispatch(updateTagHistory(history))
routerHistory.push('/posts/new')
} }
} }
export function changeCompose(text, markdown, replyId, isStandalone, caretPosition) { /**
return function (dispatch, getState) { *
*/
const insertIntoTagHistory = (recognizedTags, text) => (dispatch, getState) => {
const state = getState()
const oldHistory = state.getIn(['compose', 'tagHistory'])
const me = state.getIn(['meta', 'me'])
const names = recognizedTags.map(tag => text.match(new RegExp(`#${tag.name}`, 'i'))[0].slice(1))
const intersectedOldHistory = oldHistory.filter(name => names.findIndex(newName => newName.toLowerCase() === name.toLowerCase()) === -1)
names.push(...intersectedOldHistory.toJS())
const newHistory = names.slice(0, 1000)
tagHistory.set(me, newHistory)
dispatch(updateTagHistory(newHistory))
}
/**
*
*/
export const changeCompose = (text, markdown, replyId, isStandalone, caretPosition) => (dispatch, getState) => {
const reduxReplyToId = getState().getIn(['compose', 'in_reply_to']) const reduxReplyToId = getState().getIn(['compose', 'in_reply_to'])
const existingText = getState().getIn(['compose', 'text']).trim() const existingText = getState().getIn(['compose', 'text']).trim()
const isModalOpen = getState().getIn(['modal', 'modalType']) === 'COMPOSE' || isStandalone const isModalOpen = getState().getIn(['modal', 'modalType']) === MODAL_COMPOSE || isStandalone
let status let status
if (!!replyId) { if (!!replyId) {
@ -116,7 +141,7 @@ export function changeCompose(text, markdown, replyId, isStandalone, caretPositi
if (existingText.length === 0 && text.trim().length > 0) { if (existingText.length === 0 && text.trim().length > 0) {
dispatch({ dispatch({
type: COMPOSE_REPLY, type: COMPOSE_REPLY,
status: status, status,
text: text, text: text,
}) })
} else if (existingText.length > 0 && text.trim().length > 0) { } else if (existingText.length > 0 && text.trim().length > 0) {
@ -126,7 +151,7 @@ export function changeCompose(text, markdown, replyId, isStandalone, caretPositi
onConfirm: () => { onConfirm: () => {
dispatch({ dispatch({
type: COMPOSE_REPLY, type: COMPOSE_REPLY,
status: status, status,
}) })
dispatch({ dispatch({
type: COMPOSE_CHANGE, type: COMPOSE_CHANGE,
@ -179,68 +204,75 @@ export function changeCompose(text, markdown, replyId, isStandalone, caretPositi
caretPosition: caretPosition, caretPosition: caretPosition,
}) })
} }
}
} }
export function replyCompose(status, router, showModal) { /**
return (dispatch) => { *
*/
export const replyCompose = (status, router, showModal) => (dispatch) => {
dispatch({ dispatch({
type: COMPOSE_REPLY, type: COMPOSE_REPLY,
status: status, status,
}); })
if (isMobile(window.innerWidth)) { if (isMobile(window.innerWidth)) {
router.history.push('/compose') router.history.push('/compose')
} else { } else {
if (showModal) { if (showModal) {
dispatch(openModal('COMPOSE')); dispatch(openModal(MODAL_COMPOSE))
} }
} }
}; }
};
export function quoteCompose(status, router) { /**
return (dispatch) => { *
*/
export const quoteCompose = (status, router) => (dispatch) => {
dispatch({ dispatch({
type: COMPOSE_QUOTE, type: COMPOSE_QUOTE,
status: status, status,
}); })
if (isMobile(window.innerWidth)) { if (isMobile(window.innerWidth)) {
router.history.push('/compose') router.history.push('/compose')
} else { } else {
dispatch(openModal('COMPOSE')); dispatch(openModal(MODAL_COMPOSE))
} }
}; }
};
export function cancelReplyCompose() { /**
return { *
*/
export const cancelReplyCompose = () => ({
type: COMPOSE_REPLY_CANCEL, type: COMPOSE_REPLY_CANCEL,
}; })
};
export function resetCompose() { /**
return { *
*/
export const resetCompose = () => ({
type: COMPOSE_RESET, type: COMPOSE_RESET,
}; })
};
export function mentionCompose(account) { /**
return (dispatch) => { *
*/
export const mentionCompose = (account) => (dispatch) => {
dispatch({ dispatch({
type: COMPOSE_MENTION, type: COMPOSE_MENTION,
account: account, account: account,
}); })
dispatch(openModal('COMPOSE')); dispatch(openModal(MODAL_COMPOSE))
}; }
};
export function handleComposeSubmit(dispatch, getState, response, status) { /**
if (!dispatch || !getState) return; *
*/
export const handleComposeSubmit = (dispatch, getState, response, status) => {
if (!dispatch || !getState) return
const isScheduledStatus = response.data.scheduled_at !== undefined; const isScheduledStatus = response.data.scheduled_at !== undefined
if (isScheduledStatus) { if (isScheduledStatus) {
// dispatch(showAlertForError({ // dispatch(showAlertForError({
// response: { // response: {
@ -248,38 +280,40 @@ export function handleComposeSubmit(dispatch, getState, response, status) {
// status: 200, // status: 200,
// statusText: 'Successfully scheduled status', // statusText: 'Successfully scheduled status',
// } // }
// })); // }))
dispatch(submitComposeSuccess({ ...response.data })); dispatch(submitComposeSuccess({ ...response.data }))
return; return
} }
dispatch(insertIntoTagHistory(response.data.tags, status)); dispatch(insertIntoTagHistory(response.data.tags, status))
dispatch(submitComposeSuccess({ ...response.data })); dispatch(submitComposeSuccess({ ...response.data }))
// To make the app more responsive, immediately push the status into the timeline // To make the app more responsive, immediately push the status into the timeline
// : todo : push into comment, reload parent status, etc. // : todo : push into comment, reload parent status, etc.
const insertIfOnline = timelineId => { const insertIfOnline = (timelineId) => {
const timeline = getState().getIn(['timelines', timelineId]); const timeline = getState().getIn(['timelines', timelineId])
if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) { if (timeline && timeline.get('items').size > 0 && timeline.getIn(['items', 0]) !== null && timeline.get('online')) {
dispatch(updateTimeline(timelineId, { ...response.data })); dispatch(updateTimeline(timelineId, { ...response.data }))
}
} }
};
if (response.data.visibility === 'public') { if (response.data.visibility === 'public') {
insertIfOnline('home'); insertIfOnline('home')
} }
} }
export function submitCompose(groupId, replyToId = null, router, isStandalone, autoJoinGroup) { /**
return function (dispatch, getState) { *
if (!me) return; */
export const submitCompose = (groupId, replyToId = null, router, isStandalone, autoJoinGroup) => (dispatch, getState) => {
if (!me) return
if (autoJoinGroup) dispatch(joinGroup(groupId)) if (autoJoinGroup) dispatch(joinGroup(groupId))
let status = getState().getIn(['compose', 'text'], ''); let status = getState().getIn(['compose', 'text'], '')
let markdown = getState().getIn(['compose', 'markdown'], ''); let markdown = getState().getIn(['compose', 'markdown'], '')
const media = getState().getIn(['compose', 'media_attachments']); const media = getState().getIn(['compose', 'media_attachments'])
const isPrivateGroup = !!groupId ? getState().getIn(['groups', groupId, 'is_private'], false) : false const isPrivateGroup = !!groupId ? getState().getIn(['groups', groupId, 'is_private'], false) : false
const replacer = (match) => { const replacer = (match) => {
@ -298,19 +332,19 @@ export function submitCompose(groupId, replyToId = null, router, isStandalone, a
const inReplyToId = getState().getIn(['compose', 'in_reply_to'], null) || replyToId const inReplyToId = getState().getIn(['compose', 'in_reply_to'], null) || replyToId
dispatch(submitComposeRequest()); dispatch(submitComposeRequest())
dispatch(closeModal()); dispatch(closeModal())
const id = getState().getIn(['compose', 'id']); const id = getState().getIn(['compose', 'id'])
const endpoint = id === null const endpoint = id === null
? '/api/v1/statuses' ? '/api/v1/statuses'
: `/api/v1/statuses/${id}`; : `/api/v1/statuses/${id}`
const method = id === null ? 'post' : 'put'; const method = id === null ? 'post' : 'put'
let scheduled_at = getState().getIn(['compose', 'scheduled_at'], null); let scheduled_at = getState().getIn(['compose', 'scheduled_at'], null)
if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate(); if (scheduled_at !== null) scheduled_at = moment.utc(scheduled_at).toDate()
let expires_at = getState().getIn(['compose', 'expires_at'], null); let expires_at = getState().getIn(['compose', 'expires_at'], null)
if (expires_at) { if (expires_at) {
if (expires_at === STATUS_EXPIRATION_OPTION_5_MINUTES) { if (expires_at === STATUS_EXPIRATION_OPTION_5_MINUTES) {
@ -351,243 +385,220 @@ export function submitCompose(groupId, replyToId = null, router, isStandalone, a
headers: { headers: {
'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']), 'Idempotency-Key': getState().getIn(['compose', 'idempotencyKey']),
}, },
}).then(function (response) { }).then((response) => {
handleComposeSubmit(dispatch, getState, response, status); handleComposeSubmit(dispatch, getState, response, status)
}).catch(function (error) { }).catch((error) => {
dispatch(submitComposeFail(error)); dispatch(submitComposeFail(error))
}); })
};
};
export function submitComposeRequest() {
return {
type: COMPOSE_SUBMIT_REQUEST,
};
};
export function submitComposeSuccess(status) {
return {
type: COMPOSE_SUBMIT_SUCCESS,
status: status,
};
};
export function clearCompose() {
return {
type: COMPOSE_CLEAR,
};
};
export function submitComposeFail(error) {
return {
type: COMPOSE_SUBMIT_FAIL,
error: error,
}
} }
export function uploadCompose(files) { const submitComposeRequest = () => ({
return function (dispatch, getState) { type: COMPOSE_SUBMIT_REQUEST,
})
const submitComposeSuccess = (status) => ({
type: COMPOSE_SUBMIT_SUCCESS,
status,
})
const submitComposeFail = (error) => ({
type: COMPOSE_SUBMIT_FAIL,
error,
})
/**
*
*/
export const uploadCompose = (files) => (dispatch, getState) => {
if (!me) return if (!me) return
const uploadLimit = 4 const uploadLimit = 4
const media = getState().getIn(['compose', 'media_attachments']) const media = getState().getIn(['compose', 'media_attachments'])
const pending = getState().getIn(['compose', 'pending_media_attachments']) const pending = getState().getIn(['compose', 'pending_media_attachments'])
const progress = new Array(files.length).fill(0); const progress = new Array(files.length).fill(0)
let total = Array.from(files).reduce((a, v) => a + v.size, 0); let total = Array.from(files).reduce((a, v) => a + v.size, 0)
if (files.length + media.size + pending > uploadLimit) { if (files.length + media.size + pending > uploadLimit) {
// dispatch(showAlert(undefined, messages.uploadErrorLimit)); // dispatch(showAlert(undefined, messages.uploadErrorLimit))
return; return
} }
if (getState().getIn(['compose', 'poll'])) { if (getState().getIn(['compose', 'poll'])) {
// dispatch(showAlert(undefined, messages.uploadErrorPoll)); // dispatch(showAlert(undefined, messages.uploadErrorPoll))
return; return
} }
dispatch(uploadComposeRequest()); dispatch(uploadComposeRequest())
for (const [i, f] of Array.from(files).entries()) { for (const [i, f] of Array.from(files).entries()) {
if (media.size + i > 3) break; if (media.size + i > 3) break
resizeImage(f).then((file) => { resizeImage(f).then((file) => {
const data = new FormData(); const data = new FormData()
data.append('file', file); data.append('file', file)
// Account for disparity in size of original image and resized data // Account for disparity in size of original image and resized data
total += file.size - f.size; total += file.size - f.size
return api(getState).post('/api/v1/media', data, { return api(getState).post('/api/v1/media', data, {
onUploadProgress: function({ loaded }){ onUploadProgress: ({ loaded }) => {
progress[i] = loaded; progress[i] = loaded
dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total)); dispatch(uploadComposeProgress(progress.reduce((a, v) => a + v, 0), total))
}, },
}).then(({ data }) => dispatch(uploadComposeSuccess(data))); }).then(({ data }) => dispatch(uploadComposeSuccess(data)))
}).catch(error => dispatch(uploadComposeFail(error, true))); }).catch((error) => dispatch(uploadComposeFail(error, true)))
}; }
}; }
};
export function changeUploadCompose(id, params) { const uploadComposeRequest = () => ({
return (dispatch, getState) => {
if (!me) return;
dispatch(changeUploadComposeRequest());
api(getState).put(`/api/v1/media/${id}`, params).then(response => {
dispatch(changeUploadComposeSuccess(response.data));
}).catch(error => {
dispatch(changeUploadComposeFail(id, error));
});
};
};
export function changeUploadComposeRequest() {
return {
type: COMPOSE_UPLOAD_CHANGE_REQUEST,
skipLoading: true,
};
};
export function changeUploadComposeSuccess(media) {
return {
type: COMPOSE_UPLOAD_CHANGE_SUCCESS,
media: media,
skipLoading: true,
};
};
export function changeUploadComposeFail(error, decrement = false) {
return {
type: COMPOSE_UPLOAD_CHANGE_FAIL,
error: error,
decrement: decrement,
skipLoading: true,
};
};
export function uploadComposeRequest() {
return {
type: COMPOSE_UPLOAD_REQUEST, type: COMPOSE_UPLOAD_REQUEST,
skipLoading: true, })
};
};
export function uploadComposeProgress(loaded, total) { const uploadComposeProgress = (loaded, total) => ({
return {
type: COMPOSE_UPLOAD_PROGRESS, type: COMPOSE_UPLOAD_PROGRESS,
loaded: loaded, loaded: loaded,
total: total, total: total,
}; })
};
export function uploadComposeSuccess(media) { const uploadComposeSuccess = (media) => ({
return {
type: COMPOSE_UPLOAD_SUCCESS, type: COMPOSE_UPLOAD_SUCCESS,
media: media, media: media,
skipLoading: true, })
};
};
export function uploadComposeFail(error) { const uploadComposeFail = (error) => ({
return {
type: COMPOSE_UPLOAD_FAIL, type: COMPOSE_UPLOAD_FAIL,
error: error, error,
skipLoading: true, })
};
};
export function undoUploadCompose(media_id) { /**
return { *
*/
export const changeUploadCompose = (id, params) => (dispatch, getState) => {
if (!me) return
dispatch(changeUploadComposeRequest())
api(getState).put(`/api/v1/media/${id}`, params).then((response) => {
dispatch(changeUploadComposeSuccess(response.data))
}).catch((error) => {
dispatch(changeUploadComposeFail(id, error))
})
}
const changeUploadComposeRequest = () => ({
type: COMPOSE_UPLOAD_CHANGE_REQUEST,
})
const changeUploadComposeSuccess = (media) => ({
type: COMPOSE_UPLOAD_CHANGE_SUCCESS,
media: media,
})
const changeUploadComposeFail = (error, decrement = false) => ({
type: COMPOSE_UPLOAD_CHANGE_FAIL,
error,
decrement: decrement,
})
/**
*
*/
export const undoUploadCompose = (media_id) => ({
type: COMPOSE_UPLOAD_UNDO, type: COMPOSE_UPLOAD_UNDO,
media_id: media_id, media_id: media_id,
}; })
};
export function clearComposeSuggestions() { /**
*
*/
export const clearComposeSuggestions = () => {
if (cancelFetchComposeSuggestionsAccounts) { if (cancelFetchComposeSuggestionsAccounts) {
cancelFetchComposeSuggestionsAccounts(); cancelFetchComposeSuggestionsAccounts()
} }
return { return {
type: COMPOSE_SUGGESTIONS_CLEAR, type: COMPOSE_SUGGESTIONS_CLEAR,
}; }
}; }
/**
*
*/
export const fetchComposeSuggestions = (token) => (dispatch, getState) => {
switch (token[0]) {
case ':':
fetchComposeSuggestionsEmojis(dispatch, getState, token)
break
case '#':
fetchComposeSuggestionsTags(dispatch, getState, token)
break
default:
fetchComposeSuggestionsAccounts(dispatch, getState, token)
break
}
}
const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => { const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, token) => {
if (cancelFetchComposeSuggestionsAccounts) { if (cancelFetchComposeSuggestionsAccounts) {
cancelFetchComposeSuggestionsAccounts(); cancelFetchComposeSuggestionsAccounts()
} }
api(getState).get('/api/v1/accounts/search', { api(getState).get('/api/v1/accounts/search', {
cancelToken: new CancelToken(cancel => { cancelToken: new CancelToken(cancel => {
cancelFetchComposeSuggestionsAccounts = cancel; cancelFetchComposeSuggestionsAccounts = cancel
}), }),
params: { params: {
q: token.slice(1), q: token.slice(1),
resolve: false, resolve: false,
limit: 4, limit: 4,
}, },
}).then(response => { }).then((response) => {
dispatch(importFetchedAccounts(response.data)); dispatch(importFetchedAccounts(response.data))
dispatch(readyComposeSuggestionsAccounts(token, response.data)); dispatch(readyComposeSuggestionsAccounts(token, response.data))
}).catch(error => { }).catch((error) => {
if (!isCancel(error)) { if (!isCancel(error)) {
// dispatch(showAlertForError(error)); // dispatch(showAlertForError(error))
} }
}); })
}, 200, { leading: true, trailing: true }); }, 200, { leading: true, trailing: true })
const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => { const fetchComposeSuggestionsEmojis = (dispatch, getState, token) => {
const results = emojiSearch(token.replace(':', ''), { maxResults: 5 }); const results = emojiSearch(token.replace(':', ''), { maxResults: 5 })
dispatch(readyComposeSuggestionsEmojis(token, results)); dispatch(readyComposeSuggestionsEmojis(token, results))
}; }
const fetchComposeSuggestionsTags = (dispatch, getState, token) => { const fetchComposeSuggestionsTags = (dispatch, getState, token) => {
dispatch(updateSuggestionTags(token)); dispatch(updateSuggestionTags(token))
}; }
export function fetchComposeSuggestions(token) { const readyComposeSuggestionsEmojis = (token, emojis) => ({
return (dispatch, getState) => {
switch (token[0]) {
case ':':
fetchComposeSuggestionsEmojis(dispatch, getState, token);
break;
case '#':
fetchComposeSuggestionsTags(dispatch, getState, token);
break;
default:
fetchComposeSuggestionsAccounts(dispatch, getState, token);
break;
}
};
};
export function readyComposeSuggestionsEmojis(token, emojis) {
return {
type: COMPOSE_SUGGESTIONS_READY, type: COMPOSE_SUGGESTIONS_READY,
token, token,
emojis, emojis,
}; })
};
export const readyComposeSuggestionsAccounts = (token, accounts) => ({ const readyComposeSuggestionsAccounts = (token, accounts) => ({
type: COMPOSE_SUGGESTIONS_READY, type: COMPOSE_SUGGESTIONS_READY,
token, token,
accounts, accounts,
}) })
export function selectComposeSuggestion(position, token, suggestion, path) { /**
return (dispatch, getState) => { *
let completion, startPosition; */
export const selectComposeSuggestion = (position, token, suggestion, path) => (dispatch, getState) => {
let completion, startPosition
if (typeof suggestion === 'object' && suggestion.id) { if (typeof suggestion === 'object' && suggestion.id) {
completion = suggestion.native || suggestion.colons; completion = suggestion.native || suggestion.colons
startPosition = position - 1; startPosition = position - 1
dispatch(useEmoji(suggestion)); dispatch(useEmoji(suggestion))
} else if (suggestion[0] === '#') { } else if (suggestion[0] === '#') {
completion = suggestion; completion = suggestion
startPosition = position - 1; startPosition = position - 1
} else { } else {
completion = getState().getIn(['accounts', suggestion, 'acct']); completion = getState().getIn(['accounts', suggestion, 'acct'])
startPosition = position; startPosition = position
} }
dispatch({ dispatch({
@ -596,138 +607,131 @@ export function selectComposeSuggestion(position, token, suggestion, path) {
token, token,
completion, completion,
path, path,
}); })
}; }
};
export function updateSuggestionTags(token) { /**
return { *
*/
export const updateSuggestionTags = (token) => ({
type: COMPOSE_SUGGESTION_TAGS_UPDATE, type: COMPOSE_SUGGESTION_TAGS_UPDATE,
token, token,
}; })
}
export function updateTagHistory(tags) { /**
return { *
*/
export const updateTagHistory = (tags) => ({
type: COMPOSE_TAG_HISTORY_UPDATE, type: COMPOSE_TAG_HISTORY_UPDATE,
tags, tags,
}; })
}
export function hydrateCompose() { /**
return (dispatch, getState) => { *
const me = getState().getIn(['meta', 'me']); */
const history = tagHistory.get(me); export const mountCompose = () => ({
if (history !== null) {
dispatch(updateTagHistory(history));
}
};
}
function insertIntoTagHistory(recognizedTags, text) {
return (dispatch, getState) => {
const state = getState();
const oldHistory = state.getIn(['compose', 'tagHistory']);
const me = state.getIn(['meta', 'me']);
const names = recognizedTags.map(tag => text.match(new RegExp(`#${tag.name}`, 'i'))[0].slice(1));
const intersectedOldHistory = oldHistory.filter(name => names.findIndex(newName => newName.toLowerCase() === name.toLowerCase()) === -1);
names.push(...intersectedOldHistory.toJS());
const newHistory = names.slice(0, 1000);
tagHistory.set(me, newHistory);
dispatch(updateTagHistory(newHistory));
};
}
export function mountCompose() {
return {
type: COMPOSE_MOUNT, type: COMPOSE_MOUNT,
}; })
};
export function unmountCompose() { /**
return { *
*/
export const unmountCompose = () => ({
type: COMPOSE_UNMOUNT, type: COMPOSE_UNMOUNT,
}; })
};
export function changeComposeSensitivity() { /**
return { *
*/
export const clearCompose = () => ({
type: COMPOSE_CLEAR,
})
/**
*
*/
export const changeComposeSensitivity = () => ({
type: COMPOSE_SENSITIVITY_CHANGE, type: COMPOSE_SENSITIVITY_CHANGE,
}; })
};
export function changeComposeSpoilerness() { /**
return { *
*/
export const changeComposeSpoilerness = () => ({
type: COMPOSE_SPOILERNESS_CHANGE, type: COMPOSE_SPOILERNESS_CHANGE,
}; })
};
export function changeComposeSpoilerText(text) { /**
return { *
*/
export const changeComposeSpoilerText = (text) => ({
type: COMPOSE_SPOILER_TEXT_CHANGE, type: COMPOSE_SPOILER_TEXT_CHANGE,
text, text,
}; })
};
export function changeComposeVisibility(value) { /**
return { *
*/
export const changeComposeVisibility = (value) => ({
type: COMPOSE_VISIBILITY_CHANGE, type: COMPOSE_VISIBILITY_CHANGE,
value, value,
}; })
};
export function insertEmojiCompose(emoji, needsSpace) { /**
return { *
*/
export const insertEmojiCompose = (emoji, needsSpace) => ({
type: COMPOSE_EMOJI_INSERT, type: COMPOSE_EMOJI_INSERT,
emoji, emoji,
needsSpace, needsSpace,
}; })
};
export function changeComposing(value) { /**
return { *
*/
export const changeComposing = (value) => ({
type: COMPOSE_COMPOSING_CHANGE, type: COMPOSE_COMPOSING_CHANGE,
value, value,
}; })
};
export function addPoll() { /**
return { *
*/
export const addPoll = () => ({
type: COMPOSE_POLL_ADD, type: COMPOSE_POLL_ADD,
}; })
};
export function removePoll() { /**
return { *
*/
export const removePoll = () => ({
type: COMPOSE_POLL_REMOVE, type: COMPOSE_POLL_REMOVE,
}; })
};
export function addPollOption(title) { /**
return { *
*/
export const addPollOption = (title) => ({
type: COMPOSE_POLL_OPTION_ADD, type: COMPOSE_POLL_OPTION_ADD,
title, title,
}; })
};
export function changePollOption(index, title) { /**
return { *
*/
export const changePollOption = (index, title) => ({
type: COMPOSE_POLL_OPTION_CHANGE, type: COMPOSE_POLL_OPTION_CHANGE,
index, index,
title, title,
}; })
};
export function removePollOption(index) { /**
return { *
*/
export const removePollOption = (index) => ({
type: COMPOSE_POLL_OPTION_REMOVE, type: COMPOSE_POLL_OPTION_REMOVE,
index, index,
}; })
};
/** /**
* *

View File

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

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -14,7 +14,10 @@ export const STATUSES_IMPORT = 'STATUSES_IMPORT'
export const POLLS_IMPORT = 'POLLS_IMPORT' export const POLLS_IMPORT = 'POLLS_IMPORT'
export const ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP = 'ACCOUNT_FETCH_FAIL_FOR_USERNAME_LOOKUP' 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)) { if (array.every(element => element.id !== object.id)) {
array.push(object); array.push(object);
} }

View File

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

View File

@ -1,5 +1,9 @@
import api from '../api' import api, { getLinks } from '../api'
import { importFetchedAccounts, importFetchedStatus } from './importer' import {
importFetchedAccounts,
importFetchedStatus,
} from './importer'
import { fetchRelationships } from './accounts'
import { updateStatusStats } from './statuses' import { updateStatusStats } from './statuses'
import { me } from '../initial_state' 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_SUCCESS = 'REPOSTS_FETCH_SUCCESS'
export const REPOSTS_FETCH_FAIL = 'REPOSTS_FETCH_FAIL' 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_REQUEST = 'PIN_REQUEST'
export const PIN_SUCCESS = 'PIN_SUCCESS' export const PIN_SUCCESS = 'PIN_SUCCESS'
export const PIN_FAIL = 'PIN_FAIL' 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_SUCCESS = 'UNPIN_SUCCESS'
export const UNPIN_FAIL = 'UNPIN_FAIL' 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_REQUEST = 'BOOKMARK_REQUEST'
export const BOOKMARK_SUCCESS = 'BOOKMARK_SUCCESS' export const BOOKMARK_SUCCESS = 'BOOKMARK_SUCCESS'
export const BOOKMARK_FAIL = 'BOOKMARK_FAIL' 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_SUCCESS = 'UNBOOKMARK_SUCCESS'
export const UNBOOKMARK_FAIL = 'UNBOOKMARK_FAIL' 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_REQUEST = 'LIKES_FETCH_REQUEST'
export const LIKES_FETCH_SUCCESS = 'LIKES_FETCH_SUCCESS' export const LIKES_FETCH_SUCCESS = 'LIKES_FETCH_SUCCESS'
export const LIKES_FETCH_FAIL = 'LIKES_FETCH_FAIL' 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) => { export const repost = (status) => (dispatch, getState) => {
if (!me) return if (!me || !status) return
dispatch(repostRequest(status)) dispatch(repostRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/reblog`).then((response) => { 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 dispatch(updateStatusStats(response.data))
// interested in how the original is modified, hence passing it skipping the wrapper
dispatch(importFetchedStatus(response.data.reblog))
dispatch(repostSuccess(status)) dispatch(repostSuccess(status))
}).catch((error) => { }).catch((error) => {
dispatch(repostFail(status, error)) dispatch(repostFail(status, error))
}) })
} }
export const repostRequest = (status) => ({ const repostRequest = (status) => ({
type: REPOST_REQUEST, type: REPOST_REQUEST,
status: status, status,
skipLoading: true,
}) })
export const repostSuccess = (status) => ({ const repostSuccess = (status) => ({
type: REPOST_SUCCESS, type: REPOST_SUCCESS,
status: status, status,
skipLoading: true,
}) })
export const repostFail = (status, error) => ({ const repostFail = (status, error) => ({
type: REPOST_FAIL, type: REPOST_FAIL,
status: status, status,
error: error, error,
skipLoading: true,
}) })
/** /**
* * @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) => { export const unrepost = (status) => (dispatch, getState) => {
if (!me) return if (!me || !status) return
dispatch(unrepostRequest(status)) dispatch(unrepostRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then((response) => { api(getState).post(`/api/v1/statuses/${status.get('id')}/unreblog`).then((response) => {
dispatch(importFetchedStatus(response.data)) dispatch(updateStatusStats(response.data))
dispatch(unrepostSuccess(status)) dispatch(unrepostSuccess(status))
}).catch((error) => { }).catch((error) => {
dispatch(unrepostFail(status, error)) dispatch(unrepostFail(status, error))
}) })
} }
export const unrepostRequest = (status) => ({ const unrepostRequest = (status) => ({
type: UNREPOST_REQUEST, type: UNREPOST_REQUEST,
status: status, status,
skipLoading: true,
}) })
export const unrepostSuccess = (status) => ({ const unrepostSuccess = (status) => ({
type: UNREPOST_SUCCESS, type: UNREPOST_SUCCESS,
status: status, status,
skipLoading: true,
}) })
export const unrepostFail = (status, error) => ({ const unrepostFail = (status, error) => ({
type: UNREPOST_FAIL, type: UNREPOST_FAIL,
status: status, status,
error: error, error,
skipLoading: true,
}) })
/** /**
* * @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) => { export const favorite = (status) => (dispatch, getState) => {
if (!me) return if (!me || !status) return
dispatch(favoriteRequest(status)) dispatch(favoriteRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then((response) => { api(getState).post(`/api/v1/statuses/${status.get('id')}/favourite`).then((response) => {
dispatch(updateStatusStats(response.data)) dispatch(updateStatusStats(response.data))
dispatch(favoriteSuccess(status)) dispatch(favoriteSuccess(response.data))
}).catch((error) => { }).catch((error) => {
dispatch(favoriteFail(status, error)) dispatch(favoriteFail(status, error))
}) })
} }
export const favoriteRequest = (status) => ({ const favoriteRequest = (status) => ({
type: FAVORITE_REQUEST, type: FAVORITE_REQUEST,
status: status, status,
skipLoading: true,
}) })
export const favoriteSuccess = (status) => ({ const favoriteSuccess = (data) => ({
type: FAVORITE_SUCCESS, type: FAVORITE_SUCCESS,
status: status, data,
skipLoading: true,
}) })
export const favoriteFail = (status, error) => ({ const favoriteFail = (status, error) => ({
type: FAVORITE_FAIL, type: FAVORITE_FAIL,
status: status, status,
error: error, error,
skipLoading: true,
}) })
/** /**
* * @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) => { export const unfavorite = (status) => (dispatch, getState) => {
if (!me) return if (!me || !status) return
dispatch(unfavoriteRequest(status)) dispatch(unfavoriteRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then((response) => { api(getState).post(`/api/v1/statuses/${status.get('id')}/unfavourite`).then((response) => {
dispatch(importFetchedStatus(response.data)) dispatch(updateStatusStats(response.data))
dispatch(unfavoriteSuccess(status)) dispatch(unfavoriteSuccess(status))
}).catch((error) => { }).catch((error) => {
dispatch(unfavoriteFail(status, error)) dispatch(unfavoriteFail(status, error))
}) })
} }
export const unfavoriteRequest = (status) => ({ const unfavoriteRequest = (status) => ({
type: UNFAVORITE_REQUEST, type: UNFAVORITE_REQUEST,
status: status, status,
skipLoading: true,
}) })
export const unfavoriteSuccess = (status) => ({ const unfavoriteSuccess = (status) => ({
type: UNFAVORITE_SUCCESS, type: UNFAVORITE_SUCCESS,
status: status, status,
skipLoading: true,
}) })
export const unfavoriteFail = (status, error) => ({ const unfavoriteFail = (status, error) => ({
type: UNFAVORITE_FAIL, type: UNFAVORITE_FAIL,
status: 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,
error, 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) => { export const pin = (status) => (dispatch, getState) => {
if (!me) return if (!me || !status) return
dispatch(pinRequest(status)) dispatch(pinRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then((response) => { api(getState).post(`/api/v1/statuses/${status.get('id')}/pin`).then((response) => {
dispatch(importFetchedStatus(response.data)) dispatch(updateStatusStats(response.data))
dispatch(pinSuccess(status)) dispatch(pinSuccess(status))
}).catch((error) => { }).catch((error) => {
dispatch(pinFail(status, error)) dispatch(pinFail(status, error))
}) })
} }
export const pinRequest = (status) => ({ const pinRequest = (status) => ({
type: PIN_REQUEST, type: PIN_REQUEST,
status, status,
skipLoading: true,
}) })
export const pinSuccess = (status) => ({ const pinSuccess = (status) => ({
type: PIN_SUCCESS, type: PIN_SUCCESS,
status, status,
skipLoading: true,
}) })
export const pinFail = (status, error) => ({ const pinFail = (status, error) => ({
type: PIN_FAIL, type: PIN_FAIL,
status, status,
error, 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) => { export const unpin = (status) => (dispatch, getState) => {
if (!me) return if (!me || !status) return
dispatch(unpinRequest(status)) dispatch(unpinRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then((response) => { api(getState).post(`/api/v1/statuses/${status.get('id')}/unpin`).then((response) => {
dispatch(importFetchedStatus(response.data)) dispatch(updateStatusStats(response.data))
dispatch(unpinSuccess(status)) dispatch(unpinSuccess(status, response.data.account_id))
}).catch((error) => { }).catch((error) => {
dispatch(unpinFail(status, error)) dispatch(unpinFail(status, error))
}) })
} }
export const unpinRequest = (status) => ({ const unpinRequest = (status) => ({
type: UNPIN_REQUEST, type: UNPIN_REQUEST,
status, status,
skipLoading: true,
}) })
export const unpinSuccess = (status) => ({ const unpinSuccess = (status, accountId) => ({
type: UNPIN_SUCCESS, type: UNPIN_SUCCESS,
accountId,
status, status,
skipLoading: true,
}) })
export const unpinFail = (status, error) => ({ const unpinFail = (status, error) => ({
type: UNPIN_FAIL, type: UNPIN_FAIL,
status, status,
error, error,
skipLoading: true,
}) })
/** /**
* * @description Check if a status is pinned to the current user account.
* @param {String} statusId
*/ */
export const fetchLikes = (id) => (dispatch, getState) => { export const isPin = (statusId) => (dispatch, getState) => {
dispatch(fetchLikesRequest(id)) if (!me || !statusId) return
api(getState).get(`/api/v1/statuses/${id}/favourited_by`).then((response) => { dispatch(isPinRequest(statusId))
dispatch(importFetchedAccounts(response.data))
dispatch(fetchLikesSuccess(id, response.data)) api(getState).get(`/api/v1/statuses/${statusId}/pin`).then((response) => {
dispatch(updateStatusStats(response.data))
dispatch(isPinSuccess(statusId))
}).catch((error) => { }).catch((error) => {
dispatch(fetchLikesFail(id, error)) dispatch(isPinFail(statusId, error))
}) })
} }
export const fetchLikesRequest = (id) => ({ const isPinRequest = (statusId) => ({
type: LIKES_FETCH_REQUEST, type: IS_PIN_REQUEST,
id, statusId,
}) })
export const fetchLikesSuccess = (id, accounts) => ({ const isPinSuccess = (statusId) => ({
type: LIKES_FETCH_SUCCESS, type: IS_PIN_SUCCESS,
id, statusId,
accounts,
}) })
export const fetchLikesFail = (id, error) => ({ const isPinFail = (statusId, error) => ({
type: LIKES_FETCH_FAIL, type: IS_PIN_FAIL,
statusId,
error, 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) => { export const bookmark = (status) => (dispatch, getState) => {
if (!me || !status) return
dispatch(bookmarkRequest(status)) dispatch(bookmarkRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then((response) => { api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then((response) => {
dispatch(importFetchedStatus(response.data)) dispatch(updateStatusStats(response.data))
dispatch(bookmarkSuccess(status, response.data)) dispatch(bookmarkSuccess(status))
}).catch((error) => { }).catch((error) => {
dispatch(bookmarkFail(status, error)) dispatch(bookmarkFail(status, error))
}) })
} }
export const bookmarkRequest = (status) => ({ const bookmarkRequest = (status) => ({
type: BOOKMARK_REQUEST, type: BOOKMARK_REQUEST,
status: status, status,
}) })
export const bookmarkSuccess = (status, response) => ({ const bookmarkSuccess = (status) => ({
type: BOOKMARK_SUCCESS, type: BOOKMARK_SUCCESS,
status: status, status,
response: response,
}) })
export const bookmarkFail = (status, error) => ({ const bookmarkFail = (status, error) => ({
type: BOOKMARK_FAIL, type: BOOKMARK_FAIL,
status: status, status,
error: error, 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) => { export const unbookmark = (status) => (dispatch, getState) => {
if (!me || !status) return
dispatch(unbookmarkRequest(status)) dispatch(unbookmarkRequest(status))
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then((response) => { api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then((response) => {
dispatch(importFetchedStatus(response.data)) dispatch(updateStatusStats(response.data))
dispatch(unbookmarkSuccess(status, response.data)) dispatch(unbookmarkSuccess(status))
}).catch((error) => { }).catch((error) => {
dispatch(unbookmarkFail(status, error)) dispatch(unbookmarkFail(status, error))
}) })
} }
export const unbookmarkRequest = (status) => ({ const unbookmarkRequest = (status) => ({
type: UNBOOKMARK_REQUEST, type: UNBOOKMARK_REQUEST,
status: status, status,
}) })
export const unbookmarkSuccess = (status, response) => ({ const unbookmarkSuccess = (status) => ({
type: UNBOOKMARK_SUCCESS, type: UNBOOKMARK_SUCCESS,
status: status, status,
response: response,
}) })
export const unbookmarkFail = (status, error) => ({ const unbookmarkFail = (status, error) => ({
type: UNBOOKMARK_FAIL, type: UNBOOKMARK_FAIL,
status: status, status,
error: error, error,
})
/**
* @description Check if a status is bookmarked to the current user account.
* @param {String} statusId
*/
export const isBookmark = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
dispatch(isBookmarkRequest(statusId))
api(getState).get(`/api/v1/statuses/${statusId}/bookmark`).then((response) => {
dispatch(updateStatusStats(response.data))
dispatch(isBookmarkSuccess(statusId))
}).catch((error) => {
dispatch(isBookmarkFail(statusId, error))
})
}
const isBookmarkRequest = (statusId) => ({
type: IS_BOOKMARK_REQUEST,
statusId,
})
const isBookmarkSuccess = (statusId) => ({
type: IS_BOOKMARK_SUCCESS,
statusId,
})
const isBookmarkFail = (statusId, error) => ({
type: IS_BOOKMARK_FAIL,
statusId,
error,
})
/**
* @description Fetch reposts for the given statusId and imports paginated accounts
* and sets in user_lists reducer.
* @param {String} statusId
*/
export const fetchReposts = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
dispatch(fetchRepostsRequest(statusId))
api(getState).get(`/api/v1/statuses/${statusId}/reblogged_by`).then((response) => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(fetchRepostsSuccess(statusId, response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch((error) => {
dispatch(fetchRepostsFail(statusId, error))
})
}
const fetchRepostsRequest = (statusId) => ({
type: REPOSTS_FETCH_REQUEST,
statusId,
})
const fetchRepostsSuccess = (statusId, accounts, next) => ({
type: REPOSTS_FETCH_SUCCESS,
statusId,
accounts,
next,
})
const fetchRepostsFail = (statusId, error) => ({
type: REPOSTS_FETCH_FAIL,
statusId,
error,
})
/**
* @description Expand reposts for the given statusId and imports paginated accounts
* and sets in user_lists reducer.
* @param {String} statusId
*/
export const expandReposts = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
const url = getState().getIn(['user_lists', 'reblogged_by', statusId, 'next'])
const isLoading = getState().getIn(['user_lists', 'reblogged_by', statusId, 'isLoading'])
if (url === null || isLoading) return
dispatch(expandRepostsRequest(statusId))
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(expandRepostsSuccess(statusId, response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch(error => dispatch(expandRepostsFail(error)))
}
const expandRepostsRequest = (statusId) => ({
type: REPOSTS_EXPAND_REQUEST,
statusId,
})
const expandRepostsSuccess = (statusId, accounts, next) => ({
type: REPOSTS_EXPAND_SUCCESS,
statusId,
accounts,
next,
})
const expandRepostsFail = (statusId, error) => ({
type: REPOSTS_EXPAND_FAIL,
statusId,
error,
})
/**
* @description Fetch likes for the given statusId and imports paginated accounts
* and sets in user_lists reducer.
* @param {String} statusId
*/
export const fetchLikes = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
dispatch(fetchLikesRequest(statusId))
api(getState).get(`/api/v1/statuses/${statusId}/favourited_by`).then((response) => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(fetchLikesSuccess(statusId, response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch((error) => {
dispatch(fetchLikesFail(statusId, error))
})
}
const fetchLikesRequest = (statusId) => ({
type: LIKES_FETCH_REQUEST,
statusId,
})
const fetchLikesSuccess = (statusId, accounts, next) => ({
type: LIKES_FETCH_SUCCESS,
statusId,
accounts,
next,
})
const fetchLikesFail = (statusId, error) => ({
type: LIKES_FETCH_FAIL,
statusId,
error,
})
/**
* @description Expand likes for the given statusId and imports paginated accounts
* and sets in user_lists reducer.
* @param {String} statusId
*/
export const expandLikes = (statusId) => (dispatch, getState) => {
if (!me || !statusId) return
const url = getState().getIn(['user_lists', 'liked_by', statusId, 'next'])
const isLoading = getState().getIn(['user_lists', 'liked_by', statusId, 'isLoading'])
if (url === null || isLoading) return
dispatch(expandLikesRequest(statusId))
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next')
dispatch(importFetchedAccounts(response.data))
dispatch(expandLikesSuccess(statusId, response.data, next ? next.uri : null))
dispatch(fetchRelationships(response.data.map(item => item.id)))
}).catch(error => dispatch(expandLikesFail(error)))
}
const expandLikesRequest = (statusId) => ({
type: LIKES_EXPAND_REQUEST,
statusId,
})
const expandLikesSuccess = (statusId, accounts, next) => ({
type: LIKES_EXPAND_SUCCESS,
statusId,
accounts,
next,
})
const expandLikesFail = (statusId, error) => ({
type: LIKES_EXPAND_FAIL,
statusId,
error,
}) })

View File

@ -1,5 +1,6 @@
import api, { getLinks } from '../api' import api, { getLinks } from '../api'
import IntlMessageFormat from 'intl-messageformat' import IntlMessageFormat from 'intl-messageformat'
import noop from 'lodash.noop'
import { fetchRelationships } from './accounts' import { fetchRelationships } from './accounts'
import { import {
importFetchedAccount, importFetchedAccount,
@ -49,8 +50,6 @@ const excludeTypesFromFilter = filter => {
return allTypes.filterNot(item => item === filter).toJS() 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 if (!me) return
const onlyVerified = getState().getIn(['notifications', 'filter', 'onlyVerified']) 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, type: NOTIFICATIONS_EXPAND_REQUEST,
skipLoading: !isLoadingMore, skipLoading: !isLoadingMore,
}) })
export const expandNotificationsSuccess = (notifications, next, isLoadingMore) => ({ const expandNotificationsSuccess = (notifications, next, isLoadingMore) => ({
type: NOTIFICATIONS_EXPAND_SUCCESS, type: NOTIFICATIONS_EXPAND_SUCCESS,
notifications, notifications,
next, next,
skipLoading: !isLoadingMore, skipLoading: !isLoadingMore,
}) })
export const expandNotificationsFail = (error, isLoadingMore) => ({ const expandNotificationsFail = (error, isLoadingMore) => ({
type: NOTIFICATIONS_EXPAND_FAIL, type: NOTIFICATIONS_EXPAND_FAIL,
error, error,
skipLoading: !isLoadingMore, skipLoading: !isLoadingMore,

View File

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

View File

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

View File

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

View File

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

View File

@ -1,107 +1,115 @@
import { Map as ImmutableMap, List as ImmutableList, toJS } from 'immutable'; import { Map as ImmutableMap, List as ImmutableList, toJS } from 'immutable'
import { importFetchedStatus, importFetchedStatuses } from './importer'; import noop from 'lodash.noop'
import api, { getLinks } from '../api'; import { importFetchedStatus, importFetchedStatuses } from './importer'
import api, { getLinks } from '../api'
import { fetchRelationships } from './accounts' import { fetchRelationships } from './accounts'
export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'; export const TIMELINE_UPDATE = 'TIMELINE_UPDATE'
export const TIMELINE_DELETE = 'TIMELINE_DELETE'; export const TIMELINE_DELETE = 'TIMELINE_DELETE'
export const TIMELINE_CLEAR = 'TIMELINE_CLEAR'; export const TIMELINE_CLEAR = 'TIMELINE_CLEAR'
export const TIMELINE_UPDATE_QUEUE = 'TIMELINE_UPDATE_QUEUE'; export const TIMELINE_UPDATE_QUEUE = 'TIMELINE_UPDATE_QUEUE'
export const TIMELINE_DEQUEUE = 'TIMELINE_DEQUEUE'; export const TIMELINE_DEQUEUE = 'TIMELINE_DEQUEUE'
export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'; export const TIMELINE_SCROLL_TOP = 'TIMELINE_SCROLL_TOP'
export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'; export const TIMELINE_EXPAND_REQUEST = 'TIMELINE_EXPAND_REQUEST'
export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'; export const TIMELINE_EXPAND_SUCCESS = 'TIMELINE_EXPAND_SUCCESS'
export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'; export const TIMELINE_EXPAND_FAIL = 'TIMELINE_EXPAND_FAIL'
export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'; export const TIMELINE_CONNECT = 'TIMELINE_CONNECT'
export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'; export const TIMELINE_DISCONNECT = 'TIMELINE_DISCONNECT'
export const MAX_QUEUED_ITEMS = 40; export const MAX_QUEUED_ITEMS = 40
export function updateTimeline(timeline, status, accept) { const parseTags = (tags = {}, mode) => {
return dispatch => { return (tags[mode] || []).map((tag) => tag.value)
if (typeof accept === 'function' && !accept(status)) { }
return;
}
dispatch(importFetchedStatus(status)); /**
*
*/
export const updateTimeline = (timeline, status, accept) => (dispatch) => {
if (typeof accept === 'function' && !accept(status)) return
dispatch(importFetchedStatus(status))
dispatch({ dispatch({
type: TIMELINE_UPDATE, type: TIMELINE_UPDATE,
timeline, timeline,
status, status,
}); })
}; }
};
export function updateTimelineQueue(timeline, status, accept) { /**
return dispatch => { *
if (typeof accept === 'function' && !accept(status)) { */
return; export const updateTimelineQueue = (timeline, status, accept) => (dispatch) => {
} if (typeof accept === 'function' && !accept(status)) return
dispatch({ dispatch({
type: TIMELINE_UPDATE_QUEUE, type: TIMELINE_UPDATE_QUEUE,
timeline, timeline,
status, status,
}); })
} }
};
export function forceDequeueTimeline(timeline) { /**
return (dispatch) => { *
*/
export const forceDequeueTimeline = (timeline) => (dispatch) => {
dispatch({ dispatch({
type: TIMELINE_DEQUEUE, type: TIMELINE_DEQUEUE,
timeline, 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); 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; let shouldDispatchDequeue = true
if (totalQueuedItemsCount === 0) { if (totalQueuedItemsCount === 0) return
return;
} else if (totalQueuedItemsCount > 0 && totalQueuedItemsCount <= MAX_QUEUED_ITEMS) {
queuedItems.forEach(status => { if (totalQueuedItemsCount > 0 && totalQueuedItemsCount <= MAX_QUEUED_ITEMS) {
dispatch(updateTimeline(timeline, status.toJS(), null)); queuedItems.forEach((status) => {
}); dispatch(updateTimeline(timeline, status.toJS(), null))
})
} else { } else {
if (typeof expandFunc === 'function') { if (typeof expandFunc === 'function') {
dispatch(clearTimeline(timeline)); dispatch(clearTimeline(timeline))
expandFunc(); expandFunc()
} else { } else {
if (timeline === 'home') { if (timeline === 'home') {
dispatch(clearTimeline(timeline)); dispatch(clearTimeline(timeline))
dispatch(expandHomeTimeline(optionalExpandArgs)); dispatch(expandHomeTimeline(optionalExpandArgs))
} else if (timeline === 'community') { } else if (timeline === 'community') {
dispatch(clearTimeline(timeline)); dispatch(clearTimeline(timeline))
dispatch(expandCommunityTimeline(optionalExpandArgs)); dispatch(expandCommunityTimeline(optionalExpandArgs))
} else { } else {
shouldDispatchDequeue = false; shouldDispatchDequeue = false
} }
} }
} }
if (!shouldDispatchDequeue) return; if (!shouldDispatchDequeue) return
dispatch({ dispatch({
type: TIMELINE_DEQUEUE, type: TIMELINE_DEQUEUE,
timeline, 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')]); export const deleteFromTimelines = (id) => (dispatch, getState) => {
const reblogOf = getState().getIn(['statuses', id, 'reblog'], null); 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({ dispatch({
type: TIMELINE_DELETE, type: TIMELINE_DELETE,
@ -109,25 +117,23 @@ export function deleteFromTimelines(id) {
accountId, accountId,
references, references,
reblogOf, reblogOf,
}); })
}; }
};
export function clearTimeline(timeline) { /**
return (dispatch) => { *
dispatch({ type: TIMELINE_CLEAR, timeline }); */
}; export const clearTimeline = (timeline) => (dispatch) => {
}; dispatch({
type: TIMELINE_CLEAR,
timeline
})
}
const noOp = () => { }; /**
*
const parseTags = (tags = {}, mode) => { */
return (tags[mode] || []).map((tag) => { export const expandTimeline = (timelineId, path, params = {}, done = noop) => (dispatch, getState) => {
return tag.value;
});
};
export const expandTimeline = (timelineId, path, params = {}, done = noOp) => (dispatch, getState) => {
const timeline = getState().getIn(['timelines', timelineId], ImmutableMap()) const timeline = getState().getIn(['timelines', timelineId], ImmutableMap())
const isLoadingMore = !!params.max_id const isLoadingMore = !!params.max_id
@ -156,37 +162,13 @@ 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); const expandTimelineRequest = (timeline, isLoadingMore) => ({
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) => {
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, type: TIMELINE_EXPAND_REQUEST,
timeline, timeline,
skipLoading: !isLoadingMore, skipLoading: !isLoadingMore,
}; })
};
export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) { const expandTimelineSuccess = (timeline, statuses, next, partial, isLoadingRecent, isLoadingMore) => ({
return {
type: TIMELINE_EXPAND_SUCCESS, type: TIMELINE_EXPAND_SUCCESS,
timeline, timeline,
statuses, statuses,
@ -194,36 +176,165 @@ export function expandTimelineSuccess(timeline, statuses, next, partial, isLoadi
partial, partial,
isLoadingRecent, isLoadingRecent,
skipLoading: !isLoadingMore, skipLoading: !isLoadingMore,
}; })
};
export function expandTimelineFail(timeline, error, isLoadingMore) { const expandTimelineFail = (timeline, error, isLoadingMore) => ({
return {
type: TIMELINE_EXPAND_FAIL, type: TIMELINE_EXPAND_FAIL,
timeline, timeline,
error, error,
skipLoading: !isLoadingMore, skipLoading: !isLoadingMore,
}; })
};
export function connectTimeline(timeline) { /**
return { *
type: TIMELINE_CONNECT, */
timeline, export const scrollTopTimeline = (timeline, top) => ({
};
};
export function disconnectTimeline(timeline) {
return {
type: TIMELINE_DISCONNECT,
timeline,
};
};
export function scrollTopTimeline(timeline, top) {
return {
type: TIMELINE_SCROLL_TOP, type: TIMELINE_SCROLL_TOP,
timeline, timeline,
top, 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)
}

View File

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

View File

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

View File

@ -126,7 +126,7 @@ const messages = defineMessages({
down: { id: 'keyboard_shortcuts.down', defaultMessage: 'move down in the list' }, 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' }, column: { id: 'keyboard_shortcuts.column', defaultMessage: 'focus a status in one of the columns' },
compose: { id: 'keyboard_shortcuts.compose', defaultMessage: 'focus the compose textarea' }, 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' }, back: { id: 'keyboard_shortcuts.back', defaultMessage: 'navigate back' },
search: { id: 'keyboard_shortcuts.search', defaultMessage: 'focus search' }, search: { id: 'keyboard_shortcuts.search', defaultMessage: 'focus search' },
unfocus: { id: 'keyboard_shortcuts.unfocus', defaultMessage: 'un-focus compose textarea/search' }, unfocus: { id: 'keyboard_shortcuts.unfocus', defaultMessage: 'un-focus compose textarea/search' },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -134,6 +134,8 @@ export const GROUP_TIMELINE_SORTING_TYPE_NEWEST = 'newest'
export const GROUP_TIMELINE_SORTING_TYPE_RECENT_ACTIVITY = 'recent' export const GROUP_TIMELINE_SORTING_TYPE_RECENT_ACTIVITY = 'recent'
export const GROUP_TIMELINE_SORTING_TYPE_TOP = 'top' 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_TODAY = 'today'
export const GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_WEEKLY = 'weekly' export const GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_WEEKLY = 'weekly'
export const GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_MONTHLY = 'monthly' export const GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_MONTHLY = 'monthly'

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -77,6 +77,7 @@ export function ListTimelineSettingsModal() { return import(/* webpackChunkName:
export function MediaGallery() { return import(/* webpackChunkName: "components/media_gallery" */'../../../components/media_gallery') } export function 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 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 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 Mutes() { return import(/* webpackChunkName: "features/mutes" */'../../mutes') }
export function MuteModal() { return import(/* webpackChunkName: "modals/mute_modal" */'../../../components/modal/mute_modal') } 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') } export function NavSettingsPopover() { return import(/* webpackChunkName: "modals/nav_settings_popover" */'../../../components/popover/nav_settings_popover') }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -8,7 +8,7 @@ const normalizeRelationships = (state, relationships) => {
state = normalizeRelationship(state, relationship); state = normalizeRelationship(state, relationship);
}); });
return state; return state
}; };
const initialState = ImmutableMap(); const initialState = ImmutableMap();

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -23,8 +23,18 @@ import {
FOLLOW_REQUEST_REJECT_SUCCESS, FOLLOW_REQUEST_REJECT_SUCCESS,
} from '../actions/accounts' } from '../actions/accounts'
import { import {
REPOSTS_FETCH_REQUEST,
REPOSTS_FETCH_SUCCESS, REPOSTS_FETCH_SUCCESS,
REPOSTS_FETCH_FAIL,
REPOSTS_EXPAND_REQUEST,
REPOSTS_EXPAND_SUCCESS,
REPOSTS_EXPAND_FAIL,
LIKES_FETCH_REQUEST,
LIKES_FETCH_SUCCESS, LIKES_FETCH_SUCCESS,
LIKES_FETCH_FAIL,
LIKES_EXPAND_REQUEST,
LIKES_EXPAND_SUCCESS,
LIKES_EXPAND_FAIL,
} from '../actions/interactions' } from '../actions/interactions'
import { import {
BLOCKS_FETCH_REQUEST, BLOCKS_FETCH_REQUEST,
@ -52,6 +62,7 @@ import {
GROUP_JOIN_REQUESTS_EXPAND_SUCCESS, GROUP_JOIN_REQUESTS_EXPAND_SUCCESS,
GROUP_JOIN_REQUESTS_APPROVE_SUCCESS, GROUP_JOIN_REQUESTS_APPROVE_SUCCESS,
GROUP_JOIN_REQUESTS_REJECT_SUCCESS, GROUP_JOIN_REQUESTS_REJECT_SUCCESS,
GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS,
} from '../actions/groups' } from '../actions/groups'
const initialState = ImmutableMap({ const initialState = ImmutableMap({
@ -119,11 +130,27 @@ export default function userLists(state = initialState, action) {
case FOLLOWING_EXPAND_FAIL: case FOLLOWING_EXPAND_FAIL:
return state.setIn(['following', action.id, 'isLoading'], false); 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: 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: 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: case FOLLOW_REQUESTS_FETCH_SUCCESS:
return normalizeList(state, 'follow_requests', me, action.accounts, action.next); return normalizeList(state, 'follow_requests', me, action.accounts, action.next);
@ -162,21 +189,24 @@ export default function userLists(state = initialState, action) {
return setListFailed(state, 'mutes', me) return setListFailed(state, 'mutes', me)
case GROUP_MEMBERS_FETCH_SUCCESS: 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: 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: 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: 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: 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: 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: 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_APPROVE_SUCCESS:
case GROUP_JOIN_REQUESTS_REJECT_SUCCESS: case GROUP_JOIN_REQUESTS_REJECT_SUCCESS:
return state.updateIn(['group_join_requests', action.groupId, 'items'], list => list.filterNot(item => item === action.accountId)); return state.updateIn(['group_join_requests', action.groupId, 'items'], list => list.filterNot(item => item === action.accountId));

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,5 +12,4 @@ class GroupRelationshipsPresenter
@moderator = Group.moderator_map(@group_ids, @current_account_id) @moderator = Group.moderator_map(@group_ids, @current_account_id)
@requested = Group.requested_map(@group_ids, @current_account_id) @requested = Group.requested_map(@group_ids, @current_account_id)
end end
end end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true # frozen_string_literal: true
class StatusRelationshipsPresenter 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) def initialize(statuses, current_account_id = nil, **options)
if current_account_id.nil? if current_account_id.nil?

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,8 +3,8 @@
class GroupPinnedStatusValidator < ActiveModel::Validator class GroupPinnedStatusValidator < ActiveModel::Validator
def validate(groupPin) 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.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.group_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.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 groupPin.errors.add(:base, I18n.t('statuses.group_pin_errors.limit')) if groupPin.group.group_pinned_statuses.count >= 4
end end
end end

View File

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

View File

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

View File

@ -34,10 +34,10 @@ ast:
people_followed_by: Persones a les que sigue %{name} people_followed_by: Persones a les que sigue %{name}
people_who_follow: Persones que siguen a %{name} people_who_follow: Persones que siguen a %{name}
posts: posts:
one: Toot one: Gab
other: Toots other: Gabs
posts_tab_heading: Toots posts_tab_heading: Gabs
posts_with_replies: Toots y rempuestes posts_with_replies: Gabs y rempuestes
reserved_username: El nome d'usuariu ta acutáu reserved_username: El nome d'usuariu ta acutáu
roles: roles:
bot: Robó bot: Robó
@ -165,7 +165,7 @@ ast:
exports: exports:
archive_takeout: archive_takeout:
date: Data 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 request: Solicitar l'archivu
size: Tamañu size: Tamañu
blocks: Xente que bloquiesti blocks: Xente que bloquiesti
@ -238,7 +238,7 @@ ast:
reblog: reblog:
body: "%{name} compartió'l to estáu:" body: "%{name} compartió'l to estáu:"
subject: "%{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: number:
human: human:
decimal_units: decimal_units:
@ -309,13 +309,13 @@ ast:
video: video:
one: "%{count} videu" one: "%{count} videu"
other: "%{count} vídeos" 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 language_detection: Deteutala automáticamente
pin_errors: pin_errors:
limit: Yá fixesti'l númberu máxiumu de toots limit: Yá fixesti'l númberu máxiumu de gabs
ownership: Nun pue fixase'l toot d'otra persona ownership: Nun pue fixase'l gab d'otra persona
private: Nun puen fixase los toots que nun seyan públicos private: Nun puen fixase los gabs que nun seyan públicos
reblog: Nun pue fixase un toot compartíu reblog: Nun pue fixase un gab compartíu
show_more: Amosar más show_more: Amosar más
title: "%{name}: «%{quote}»" title: "%{name}: «%{quote}»"
visibilities: visibilities:

View File

@ -1,7 +1,7 @@
--- ---
ca: ca:
about: 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_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 about_this: Quant a
active_count_after: actiu active_count_after: actiu
@ -59,10 +59,10 @@ ca:
pin_errors: pin_errors:
following: Has d'estar seguint la persona que vulguis avalar following: Has d'estar seguint la persona que vulguis avalar
posts: posts:
one: Toot one: Gab
other: Toots other: Gabs
posts_tab_heading: Toots posts_tab_heading: Gabs
posts_with_replies: Toots i respostes posts_with_replies: Gabs i respostes
reserved_username: El nom d'usuari està reservat reserved_username: El nom d'usuari està reservat
roles: roles:
admin: Administrador admin: Administrador
@ -214,7 +214,7 @@ ca:
unsuspend_account: "%{name} ha llevat la suspensió del compte de %{target}" unsuspend_account: "%{name} ha llevat la suspensió del compte de %{target}"
update_custom_emoji: "%{name} ha actualitzat l'emoji %{target}" update_custom_emoji: "%{name} ha actualitzat l'emoji %{target}"
update_status: "%{name} estat actualitzat per %{target}" update_status: "%{name} estat actualitzat per %{target}"
deleted_status: "(toot suprimit)" deleted_status: "(gab suprimit)"
title: Registre d'auditoria title: Registre d'auditoria
custom_emojis: custom_emojis:
by_domain: Domini by_domain: Domini
@ -339,11 +339,11 @@ ca:
relays: relays:
add_new: Afegiu un nou relay add_new: Afegiu un nou relay
delete: Esborra 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 disable: Inhabilita
disabled: Desactivat disabled: Desactivat
enable: Activat 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 enabled: Activat
inbox_url: URL del Relay inbox_url: URL del Relay
pending: S'està esperant l'aprovació del relay pending: S'està esperant l'aprovació del relay
@ -426,7 +426,7 @@ ca:
open: Qualsevol pot registrar-se open: Qualsevol pot registrar-se
title: Mode de registres title: Mode de registres
show_known_fediverse_at_about_page: 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 title: Mostra el fedivers conegut en vista prèvia de la línia de temps
show_staff_badge: show_staff_badge:
desc_html: Mostra una insígnia de personal en la pàgina d'usuari desc_html: Mostra una insígnia de personal en la pàgina d'usuari
@ -596,7 +596,7 @@ ca:
archive_takeout: archive_takeout:
date: Data date: Data
download: Descarrega larxiu 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... in_progress: Compilant el teu arxiu...
request: Sol·licita el teu arxiu request: Sol·licita el teu arxiu
size: Tamany size: Tamany
@ -652,8 +652,8 @@ ca:
i_am_html: Sóc %{username} a %{service}. i_am_html: Sóc %{username} a %{service}.
identity: Identitat identity: Identitat
inactive: Inactiu inactive: Inactiu
publicize_checkbox: 'I tooteja això:' publicize_checkbox: 'I gab això:'
publicize_toot: 'Està provat! Sóc %{username} a %{service}: %{url}' publicize_gab: 'Està provat! Sóc %{username} a %{service}: %{url}'
status: Estat de verificació status: Estat de verificació
view_proof: Veure la prova view_proof: Veure la prova
imports: imports:
@ -796,20 +796,20 @@ ca:
remote_interaction: remote_interaction:
favourite: favourite:
proceed: Procedir a afavorir proceed: Procedir a afavorir
prompt: 'Vols marcar com a favorit aquest toot:' prompt: 'Vols marcar com a favorit aquest gab:'
reblog: reblog:
proceed: Procedir a impulsar proceed: Procedir a impulsar
prompt: 'Vols impulsar aquest toot:' prompt: 'Vols impulsar aquest gab:'
reply: reply:
proceed: Procedir a respondre proceed: Procedir a respondre
prompt: 'Vols respondre a aquest toot:' prompt: 'Vols respondre a aquest gab:'
remote_unfollow: remote_unfollow:
error: Error error: Error
title: Títol title: Títol
unfollowed: Sense seguir unfollowed: Sense seguir
scheduled_statuses: scheduled_statuses:
over_daily_limit: Has superat el límit de %{limit} toots programats per a aquell dia 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} toots programats over_total_limit: Has superat el limit de %{limit} gabs programats
too_soon: La data programada ha de ser futura too_soon: La data programada ha de ser futura
sessions: sessions:
activity: Última activitat activity: Última activitat
@ -889,9 +889,9 @@ ca:
open_in_web: Obre en la web open_in_web: Obre en la web
over_character_limit: Límit de caràcters de %{max} superat over_character_limit: Límit de caràcters de %{max} superat
pin_errors: pin_errors:
limit: Ja has fixat el màxim nombre de toots limit: Ja has fixat el màxim nombre de gabs
ownership: No es pot fixar el toot d'algú altre ownership: No es pot fixar el gab d'algú altre
private: No es pot fixar el toot no públic private: No es pot fixar el gab no públic
reblog: No es pot fixar un impuls reblog: No es pot fixar un impuls
poll: poll:
total_votes: total_votes:
@ -909,7 +909,7 @@ ca:
unlisted: No llistat unlisted: No llistat
unlisted_long: Tothom ho pot veure, però no es mostra en la història federada unlisted_long: Tothom ho pot veure, però no es mostra en la història federada
stream_entries: stream_entries:
pinned: Toot fixat pinned: Gab fixat
reblogged: ha impulsat reblogged: ha impulsat
sensitive_content: Contingut sensible sensitive_content: Contingut sensible
terms: terms:
@ -919,8 +919,8 @@ ca:
<ul> <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>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>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>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>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> <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> </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>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> <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: 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. 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. 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 review_server_policies: Revisa les polítiques del servidor
subject: subject:
disable: S'ha congelat el teu compte %{acct} disable: S'ha congelat el teu compte %{acct}

View File

@ -653,7 +653,7 @@ co:
identity: Identità identity: Identità
inactive: Inattiva inactive: Inattiva
publicize_checkbox: 'È mandà stu statutu:' 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 status: Statutu di a verificazione
view_proof: Vede a prova view_proof: Vede a prova
imports: imports:

View File

@ -1,7 +1,7 @@
--- ---
cs: cs:
about: 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_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 about_this: O tomto serveru
active_count_after: aktivních active_count_after: aktivních
@ -30,9 +30,9 @@ cs:
server_stats: 'Statistika serveru:' server_stats: 'Statistika serveru:'
source_code: Zdrojový kód source_code: Zdrojový kód
status_count_after: status_count_after:
few: tooty few: gab
one: toot one: Gab
other: tootů other: gab
status_count_before: Kteří napsali 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. 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í terms: Podmínky používání
@ -62,11 +62,11 @@ cs:
pin_errors: pin_errors:
following: Musíte již sledovat osobu, kterou chcete podpořit following: Musíte již sledovat osobu, kterou chcete podpořit
posts: posts:
few: Tooty few: Gabs
one: Toot one: Gab
other: Tootů other: Gabs
posts_tab_heading: Tooty posts_tab_heading: Gabs
posts_with_replies: Tooty a odpovědi posts_with_replies: Gabs a odpovědi
reserved_username: Toto uživatelské jméno je rezervováno reserved_username: Toto uživatelské jméno je rezervováno
roles: roles:
admin: Administrátor admin: Administrátor
@ -175,7 +175,7 @@ cs:
targeted_reports: Nahlášeni ostatními targeted_reports: Nahlášeni ostatními
silence: Utišit silence: Utišit
silenced: Utišen/a silenced: Utišen/a
statuses: Tooty statuses: Gabs
subscribe: Odebírat subscribe: Odebírat
suspended: Pozastaven/a suspended: Pozastaven/a
title: Účty title: Účty
@ -199,7 +199,7 @@ cs:
destroy_custom_emoji: "%{name} zničil/a emoji %{target}" destroy_custom_emoji: "%{name} zničil/a emoji %{target}"
destroy_domain_block: "%{name} odblokoval/a doménu %{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_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_2fa_user: "%{name} vypnul/a dvoufázové ověřování pro uživatele %{target}"
disable_custom_emoji: "%{name} zakázal/a emoji %{target}" disable_custom_emoji: "%{name} zakázal/a emoji %{target}"
disable_user: "%{name} zakázal/a přihlašování pro uživatele %{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}" unsilence_account: "%{name} odtišil/a účet uživatele %{target}"
unsuspend_account: "%{name} zrušil/a pozastavení účtu uživatele %{target}" unsuspend_account: "%{name} zrušil/a pozastavení účtu uživatele %{target}"
update_custom_emoji: "%{name} aktualizoval/a emoji %{target}" update_custom_emoji: "%{name} aktualizoval/a emoji %{target}"
update_status: "%{name} aktualizoval/a toot uživatele %{target}" update_status: "%{name} aktualizoval/a gab uživatele %{target}"
deleted_status: "(smazaný toot)" deleted_status: "(smazaný gab)"
title: Záznam auditu title: Záznam auditu
custom_emojis: custom_emojis:
by_domain: Doména by_domain: Doména
@ -345,11 +345,11 @@ cs:
relays: relays:
add_new: Přidat nový most add_new: Přidat nový most
delete: Smazat 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 disable: Zakázat
disabled: Zakázáno disabled: Zakázáno
enable: Povolit 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 enabled: Povoleno
inbox_url: URL mostu inbox_url: URL mostu
pending: Čekám na souhlas mostu pending: Čekám na souhlas mostu
@ -392,7 +392,7 @@ cs:
updated_at: Aktualizováno updated_at: Aktualizováno
settings: settings:
activity_api_enabled: 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ě title: Publikovat hromadné statistiky o uživatelské aktivitě
bootstrap_timeline_accounts: 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. 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 open: Kdokoliv se může registrovat
title: Režim registrací title: Režim registrací
show_known_fediverse_at_about_page: 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 title: Zobrazit na náhledu časové osy celý známý fedivesmír
show_staff_badge: show_staff_badge:
desc_html: Zobrazit na stránce uživatele odznak člena personálu desc_html: Zobrazit na stránce uživatele odznak člena personálu
@ -467,8 +467,8 @@ cs:
media: media:
title: Média title: Média
no_media: Žádná média no_media: Žádná média
no_status_selected: Nebyly změněny žádné tooty, neboť žádné nebyly vybrány no_status_selected: Nebyly změněny žádné gabs, neboť žádné nebyly vybrány
title: Tooty účtu title: Gabs účtu
with_media: S médii with_media: S médii
subscriptions: subscriptions:
callback_url: Zpáteční URL callback_url: Zpáteční URL
@ -506,7 +506,7 @@ cs:
settings: 'Změnit volby e-mailu: %{link}' settings: 'Změnit volby e-mailu: %{link}'
view: 'Zobrazit:' view: 'Zobrazit:'
view_profile: Zobrazit profil view_profile: Zobrazit profil
view_status: Zobrazit toot view_status: Zobrazit gab
applications: applications:
created: Aplikace úspěšně vytvořena created: Aplikace úspěšně vytvořena
destroyed: Aplikace úspěšně smazána destroyed: Aplikace úspěšně smazána
@ -603,7 +603,7 @@ cs:
archive_takeout: archive_takeout:
date: Datum date: Datum
download: Stáhnout svůj archiv 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… in_progress: Kompiluji váš archiv…
request: Vyžádat svůj archiv request: Vyžádat svůj archiv
size: Velikost size: Velikost
@ -660,8 +660,8 @@ cs:
i_am_html: Na %{service} jsem %{username}. i_am_html: Na %{service} jsem %{username}.
identity: Identita identity: Identita
inactive: Neaktivní inactive: Neaktivní
publicize_checkbox: 'A tootnout tohle:' publicize_checkbox: 'A gab tohle:'
publicize_toot: 'Je to dokázáno! Na %{service} jsem %{username}: %{url}' publicize_gab: 'Je to dokázáno! Na %{service} jsem %{username}: %{url}'
status: Stav ověření status: Stav ověření
view_proof: Zobrazit důkaz view_proof: Zobrazit důkaz
imports: imports:
@ -707,7 +707,7 @@ cs:
limit: Dosáhl/a jste maximálního počtu seznamů limit: Dosáhl/a jste maximálního počtu seznamů
media_attachments: media_attachments:
validations: 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 too_many: Nelze připojit více než 4 soubory
migrations: migrations:
acct: přezdívka@doména nového účtu 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" other: "%{count} nových oznámení od vaší poslední návštěvy"
title: Ve vaší nepřítomnosti… title: Ve vaší nepřítomnosti…
favourite: favourite:
body: 'Váš toot si oblíbil/a %{name}:' body: 'Váš gab si oblíbil/a %{name}:'
subject: "%{name} si oblíbil/a váš toot" subject: "%{name} si oblíbil/a váš gab"
title: Nové oblíbení title: Nové oblíbení
follow: follow:
body: "%{name} vás nyní sleduje!" body: "%{name} vás nyní sleduje!"
@ -749,8 +749,8 @@ cs:
subject: Byl/a jste zmíněn/a uživatelem %{name} subject: Byl/a jste zmíněn/a uživatelem %{name}
title: Nová zmínka title: Nová zmínka
reblog: reblog:
body: 'Váš toot byl repostnutý uživatelem %{name}:' body: 'Váš gab byl repostnutý uživatelem %{name}:'
subject: "%{name} repostnul/a váš toot" subject: "%{name} repostnul/a váš gab"
title: Nový repost title: Nový repost
number: number:
human: human:
@ -807,20 +807,20 @@ cs:
remote_interaction: remote_interaction:
favourite: favourite:
proceed: Pokračovat k oblíbení proceed: Pokračovat k oblíbení
prompt: 'Chcete si oblíbit tento toot:' prompt: 'Chcete si oblíbit tento gab:'
reblog: reblog:
proceed: Pokračovat k repostnutí proceed: Pokračovat k repostnutí
prompt: 'Chcete repostnout tento toot:' prompt: 'Chcete repostnout tento gab:'
reply: reply:
proceed: Pokračovat k odpovězení proceed: Pokračovat k odpovězení
prompt: 'Chcete odpovědět na tento toot:' prompt: 'Chcete odpovědět na tento gab:'
remote_unfollow: remote_unfollow:
error: Chyba error: Chyba
title: Nadpis title: Nadpis
unfollowed: Už nesledujete unfollowed: Už nesledujete
scheduled_statuses: scheduled_statuses:
over_daily_limit: Překročil/a jste limit %{limit} plánovaných tootů pro tento den 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 tootů over_total_limit: Překročil/a jste limit %{limit} plánovaných gab
too_soon: Plánované datum musí být v budoucnosti too_soon: Plánované datum musí být v budoucnosti
sessions: sessions:
activity: Nejnovější aktivita activity: Nejnovější aktivita
@ -903,9 +903,9 @@ cs:
open_in_web: Otevřít na webu open_in_web: Otevřít na webu
over_character_limit: limit %{max} znaků byl překročen over_character_limit: limit %{max} znaků byl překročen
pin_errors: pin_errors:
limit: Už jste si připnul/a maximální počet tootů limit: Už jste si připnul/a maximální počet gab
ownership: Nelze připnout toot někoho jiného ownership: Nelze připnout gab někoho jiného
private: Nelze připnout neveřejné tooty private: Nelze připnout neveřejné gabs
reblog: Nelze připnout repost reblog: Nelze připnout repost
poll: poll:
total_votes: total_votes:
@ -924,7 +924,7 @@ cs:
unlisted: Neuvedené unlisted: Neuvedené
unlisted_long: Všichni mohou vidět, ale nebudou zahrnuty ve veřejných časových osách unlisted_long: Všichni mohou vidět, ale nebudou zahrnuty ve veřejných časových osách
stream_entries: stream_entries:
pinned: Připnutý toot pinned: Připnutý gab
reblogged: repostnul/a reblogged: repostnul/a
sensitive_content: Citlivý obsah sensitive_content: Citlivý obsah
terms: terms:
@ -1042,8 +1042,8 @@ cs:
warning: warning:
explanation: 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. 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. 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 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í. 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 review_server_policies: Posoudit politiku serveru
subject: subject:
disable: Váš účet %{acct} byl zmražen disable: Váš účet %{acct} byl zmražen

View File

@ -653,7 +653,7 @@ de:
identity: Identität identity: Identität
inactive: Inaktiv inactive: Inaktiv
publicize_checkbox: 'Und poste das:' 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 status: Verifizierungsstatus
view_proof: Zeige Nachweis view_proof: Zeige Nachweis
imports: imports:

View File

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

View File

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

View File

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

View File

@ -649,7 +649,7 @@ eo:
identity: Identeco identity: Identeco
inactive: Malaktiva inactive: Malaktiva
publicize_checkbox: 'And gab this:' 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 status: Confirmo statuso
view_proof: Vidi pruvo view_proof: Vidi pruvo
imports: imports:

View File

@ -1,7 +1,7 @@
--- ---
es: es:
about: 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_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 about_this: Acerca de esta instancia
administered_by: 'Administrado por:' administered_by: 'Administrado por:'
@ -48,10 +48,10 @@ es:
pin_errors: pin_errors:
following: Debes estar siguiendo a la persona a la que quieres aprobar following: Debes estar siguiendo a la persona a la que quieres aprobar
posts: posts:
one: Toot one: Gab
other: Toots other: Gabs
posts_tab_heading: Toots posts_tab_heading: Gabs
posts_with_replies: Toots con respuestas posts_with_replies: Gabs con respuestas
reserved_username: El nombre de usuario está reservado reserved_username: El nombre de usuario está reservado
roles: roles:
admin: Administrador admin: Administrador
@ -289,11 +289,11 @@ es:
relays: relays:
add_new: Añadir un nuevo relés add_new: Añadir un nuevo relés
delete: Borrar 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 disable: Deshabilitar
disabled: Deshabilitado disabled: Deshabilitado
enable: Hablitar 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 enabled: Habilitado
inbox_url: URL del relés inbox_url: URL del relés
pending: Esperando la aprobación del relés pending: Esperando la aprobación del relés
@ -367,7 +367,7 @@ es:
disabled: Nadie disabled: Nadie
title: Permitir invitaciones de title: Permitir invitaciones de
show_known_fediverse_at_about_page: 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 title: Mostrar fediverso conocido en la vista previa de la historia
show_staff_badge: show_staff_badge:
desc_html: Mostrar un parche de staff en la página de un usuario desc_html: Mostrar un parche de staff en la página de un usuario
@ -503,7 +503,7 @@ es:
archive_takeout: archive_takeout:
date: Fecha date: Fecha
download: Descargar tu archivo 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... in_progress: Recopilando tu archivo...
request: Solicitar tu archivo request: Solicitar tu archivo
size: Tamaño size: Tamaño
@ -615,8 +615,8 @@ es:
subject: Fuiste mencionado por %{name} subject: Fuiste mencionado por %{name}
title: Nueva mención title: Nueva mención
reblog: reblog:
body: "%{name} ha retooteado tu estado:" body: "%{name} ha reposted tu estado:"
subject: "%{name} ha retooteado tu estado" subject: "%{name} ha reposted tu estado"
title: Nueva difusión title: Nueva difusión
number: number:
human: human:
@ -722,8 +722,8 @@ es:
over_character_limit: Límite de caracteres de %{max} superado over_character_limit: Límite de caracteres de %{max} superado
pin_errors: pin_errors:
limit: Ya has fijado el número máximo de publicaciones limit: Ya has fijado el número máximo de publicaciones
ownership: El toot de alguien más no puede fijarse ownership: El gab de alguien más no puede fijarse
private: Los toots no-públicos no pueden fijarse private: Los gabs no-públicos no pueden fijarse
reblog: Un repost no puede fijarse reblog: Un repost no puede fijarse
show_more: Mostrar más show_more: Mostrar más
sign_in_to_participate: Regístrate para participar en la conversación 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: 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 unlisted_long: Todos pueden ver, pero no está listado en las líneas de tiempo públicas
stream_entries: stream_entries:
pinned: Toot fijado pinned: Gab fijado
reblogged: retooteado reblogged: reposted
sensitive_content: Contenido sensible sensitive_content: Contenido sensible
terms: terms:
title: Términos del Servicio y Políticas de Privacidad de %{instance} title: Términos del Servicio y Políticas de Privacidad de %{instance}

View File

@ -1,7 +1,7 @@
--- ---
eu: eu:
about: 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_gabsocial_html: Gab Social web protokolo ireki eta libreak darabiltzan gizarte sare bat da. E-mail sarea bezala deszentralizatua da.
about_this: Honi buruz about_this: Honi buruz
administered_by: 'Administratzailea(k):' administered_by: 'Administratzailea(k):'
@ -48,10 +48,10 @@ eu:
pin_errors: pin_errors:
following: Onetsi nahi duzun pertsona aurretik jarraitu behar duzu following: Onetsi nahi duzun pertsona aurretik jarraitu behar duzu
posts: posts:
one: Toot one: Gab
other: Toot other: Gab
posts_tab_heading: Tootak posts_tab_heading: Gabs
posts_with_replies: Toot eta erantzunak posts_with_replies: Gabs eta erantzunak
reserved_username: Erabiltzaile-izena erreserbatuta dago reserved_username: Erabiltzaile-izena erreserbatuta dago
roles: roles:
admin: Administratzailea admin: Administratzailea
@ -316,11 +316,11 @@ eu:
relays: relays:
add_new: Gehitu hari berria add_new: Gehitu hari berria
delete: Ezabatu 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 disable: Desgaitu
disabled: Desgaituta disabled: Desgaituta
enable: Gaitu 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 enabled: Gaituta
inbox_url: Errelearen URLa inbox_url: Errelearen URLa
pending: Erreleak onartzearen zain pending: Erreleak onartzearen zain
@ -397,7 +397,7 @@ eu:
disabled: Inor ez disabled: Inor ez
title: Baimendu hauen gobidapenak title: Baimendu hauen gobidapenak
show_known_fediverse_at_about_page: 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 title: Erakutsi fedibertsu ezagun osoko denbora-lerroa aurrebistan
show_staff_badge: show_staff_badge:
desc_html: Erakutsi langile banda erabiltzailearen orrian desc_html: Erakutsi langile banda erabiltzailearen orrian
@ -557,7 +557,7 @@ eu:
archive_takeout: archive_takeout:
date: Data date: Data
download: Deskargatu zure artxiboa 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... in_progress: Zure artxiboa biltzen...
request: Eskatu zure artxiboa request: Eskatu zure artxiboa
size: Tamaina size: Tamaina
@ -712,20 +712,20 @@ eu:
remote_interaction: remote_interaction:
favourite: favourite:
proceed: Bihurtu gogoko proceed: Bihurtu gogoko
prompt: 'Toot hau gogoko bihurtu nahi duzu:' prompt: 'Gab hau gogoko bihurtu nahi duzu:'
reblog: reblog:
proceed: Eman bultzada proceed: Eman bultzada
prompt: 'Toot honi bultzada eman nahi diozu:' prompt: 'Gab honi bultzada eman nahi diozu:'
reply: reply:
proceed: Ekin erantzuteari proceed: Ekin erantzuteari
prompt: 'Toot honi erantzun nahi diozu:' prompt: 'Gab honi erantzun nahi diozu:'
remote_unfollow: remote_unfollow:
error: Errorea error: Errorea
title: Izenburua title: Izenburua
unfollowed: Jarraitzeari utzita unfollowed: Jarraitzeari utzita
scheduled_statuses: scheduled_statuses:
over_daily_limit: Egun horretarako programatutako toot kopuruaren muga gainditu duzu (%{limit}) over_daily_limit: Egun horretarako programatutako gab kopuruaren muga gainditu duzu (%{limit})
over_total_limit: Programatutako toot kopuruaren muga gainditu duzu (%{limit}) over_total_limit: Programatutako gab kopuruaren muga gainditu duzu (%{limit})
too_soon: Programatutako data etorkizunean egon behar du too_soon: Programatutako data etorkizunean egon behar du
sessions: sessions:
activity: Azken jarduera activity: Azken jarduera
@ -798,9 +798,9 @@ eu:
open_in_web: Ireki web-ean open_in_web: Ireki web-ean
over_character_limit: "%{max}eko karaktere muga gaindituta" over_character_limit: "%{max}eko karaktere muga gaindituta"
pin_errors: pin_errors:
limit: Gehienez finkatu daitekeen toot kopurua finkatu duzu jada limit: Gehienez finkatu daitekeen gab kopurua finkatu duzu jada
ownership: Ezin duzu beste norbaiten toot bat finkatu ownership: Ezin duzu beste norbaiten gab bat finkatu
private: Ezin dira publikoak ez diren toot-ak finkatu private: Ezin dira publikoak ez diren gab-ak finkatu
reblog: Bultzada bat ezin da finkatu reblog: Bultzada bat ezin da finkatu
show_more: Erakutsi gehiago show_more: Erakutsi gehiago
sign_in_to_participate: Eman izena elkarrizketan parte hartzeko sign_in_to_participate: Eman izena elkarrizketan parte hartzeko
@ -813,7 +813,7 @@ eu:
unlisted: Zerrendatu gabea unlisted: Zerrendatu gabea
unlisted_long: Edonork ikusi dezake, baina ez da denbora-lerro publikoetan agertzen unlisted_long: Edonork ikusi dezake, baina ez da denbora-lerro publikoetan agertzen
stream_entries: stream_entries:
pinned: Finkatutako toot-a pinned: Finkatutako gab-a
reblogged: "(r)en bultzada" reblogged: "(r)en bultzada"
sensitive_content: 'Kontuz: Eduki hunkigarria' sensitive_content: 'Kontuz: Eduki hunkigarria'
terms: terms:
@ -931,8 +931,8 @@ eu:
warning: warning:
explanation: explanation:
disable: Zure kontua izoztuta dagoen bitartean, zure kontua bere horretan dirau, baina ezin duzu ekintzarik burutu desblokeatzen den arte. 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. 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 toot guztiak eta multimedia fitxategiak behin betiko ezabatu dira zerbitzari honetatik, eta zure jarraitzaileen zerbitzarietatik. 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 review_server_policies: Berrikusi zerbitzariko politikak
subject: subject:
disable: Zure %{acct} kontua izoztu da disable: Zure %{acct} kontua izoztu da

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