Commiting
This commit is contained in:
43
app/javascript/gabsocial/reducers/chat_compose.js
Normal file
43
app/javascript/gabsocial/reducers/chat_compose.js
Normal 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;
|
||||
}
|
||||
}
|
||||
84
app/javascript/gabsocial/reducers/chat_messages.js
Normal file
84
app/javascript/gabsocial/reducers/chat_messages.js
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -21,11 +21,10 @@ import {
|
||||
GROUP_TIMELINE_SORTING_TYPE_TOP,
|
||||
GROUP_TIMELINE_SORTING_TYPE_NEWEST,
|
||||
GROUP_TIMELINE_SORTING_TYPE_TOP_OPTION_TODAY,
|
||||
ACCEPTED_GROUP_TABS,
|
||||
} from '../constants'
|
||||
import slugify from '../utils/slugify'
|
||||
|
||||
const tabs = ['new', 'featured', 'member', 'admin']
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
sortByValue: GROUP_TIMELINE_SORTING_TYPE_NEWEST,
|
||||
sortByTopValue: '',
|
||||
|
||||
@@ -4,11 +4,11 @@ import { Map as ImmutableMap, fromJS } from 'immutable';
|
||||
const normalizeRelationship = (state, relationship) => state.set(relationship.id, fromJS(relationship));
|
||||
|
||||
const normalizeRelationships = (state, relationships) => {
|
||||
relationships.forEach(relationship => {
|
||||
state = normalizeRelationship(state, relationship);
|
||||
});
|
||||
relationships.forEach(relationship => {
|
||||
state = normalizeRelationship(state, relationship);
|
||||
});
|
||||
|
||||
return state;
|
||||
return state
|
||||
};
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function groups(state = initialState, action) {
|
||||
case GROUPS_FETCH_SUCCESS:
|
||||
return normalizeGroups(state, action.groups)
|
||||
case GROUP_FETCH_FAIL:
|
||||
return state.set(action.id, false)
|
||||
return state.set(action.groupId, false)
|
||||
default:
|
||||
return state
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@ import { combineReducers } from 'redux-immutable'
|
||||
import { loadingBarReducer } from 'react-redux-loading-bar'
|
||||
import accounts from './accounts'
|
||||
import accounts_counters from './accounts_counters'
|
||||
import chat_compose from './chat_compose'
|
||||
import chat_conversations from './chat_conversations'
|
||||
import chat_messages from './chat_messages'
|
||||
import compose from './compose'
|
||||
import contexts from './contexts'
|
||||
import custom_emojis from './custom_emojis'
|
||||
@@ -46,6 +49,8 @@ import user_lists from './user_lists'
|
||||
const reducers = {
|
||||
accounts,
|
||||
accounts_counters,
|
||||
chat_conversations,
|
||||
chat_messages,
|
||||
compose,
|
||||
contexts,
|
||||
custom_emojis,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
|
||||
import {
|
||||
STATUS_REVISIONS_LOAD,
|
||||
STATUS_REVISIONS_LOAD_REQUEST,
|
||||
STATUS_REVISIONS_LOAD_SUCCESS,
|
||||
STATUS_REVISIONS_LOAD_FAIL
|
||||
} from '../actions/status_revisions'
|
||||
@@ -13,15 +13,17 @@ const initialState = ImmutableMap({
|
||||
|
||||
export default function statusRevisions(state = initialState, action) {
|
||||
switch (action.type) {
|
||||
case STATUS_REVISIONS_LOAD:
|
||||
return initialState
|
||||
case STATUS_REVISIONS_LOAD_REQUEST:
|
||||
return state.withMutations((mutable) => {
|
||||
mutable.set('loading', true)
|
||||
})
|
||||
case STATUS_REVISIONS_LOAD_SUCCESS:
|
||||
return state.withMutations(mutable => {
|
||||
return state.withMutations((mutable) => {
|
||||
mutable.set('loading', false)
|
||||
mutable.set('revisions', fromJS(action.revisions).reverse())
|
||||
})
|
||||
case STATUS_REVISIONS_LOAD_FAIL:
|
||||
return state.withMutations(mutable => {
|
||||
return state.withMutations((mutable) => {
|
||||
mutable.set('loading', false)
|
||||
mutable.set('error', action.error)
|
||||
})
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import {
|
||||
REPOST_REQUEST,
|
||||
UNREPOST_REQUEST,
|
||||
REPOST_FAIL,
|
||||
FAVORITE_REQUEST,
|
||||
FAVORITE_FAIL,
|
||||
@@ -43,10 +44,12 @@ export default function statuses(state = initialState, action) {
|
||||
return state.setIn([action.status.get('id'), 'favourited'], false);
|
||||
case REPOST_REQUEST:
|
||||
return state.setIn([action.status.get('id'), 'reblogged'], true);
|
||||
case UNREPOST_REQUEST:
|
||||
return state.setIn([action.status.get('id'), 'reblogged'], false);
|
||||
case REPOST_FAIL:
|
||||
return state.get(action.status.get('id')) === undefined ? state : state.setIn([action.status.get('id'), 'reblogged'], false);
|
||||
case STATUS_REVEAL:
|
||||
return state.withMutations(map => {
|
||||
return state.withMutations((map) => {
|
||||
action.ids.forEach(id => {
|
||||
if (!(state.get(id) === undefined)) {
|
||||
map.setIn([id, 'hidden'], false);
|
||||
@@ -54,7 +57,7 @@ export default function statuses(state = initialState, action) {
|
||||
});
|
||||
});
|
||||
case STATUS_HIDE:
|
||||
return state.withMutations(map => {
|
||||
return state.withMutations((map) => {
|
||||
action.ids.forEach(id => {
|
||||
if (!(state.get(id) === undefined)) {
|
||||
map.setIn([id, 'hidden'], true);
|
||||
@@ -64,8 +67,17 @@ export default function statuses(state = initialState, action) {
|
||||
case TIMELINE_DELETE:
|
||||
return deleteStatus(state, action.id, action.references);
|
||||
case UPDATE_STATUS_STATS:
|
||||
// : todo :
|
||||
return state;
|
||||
const { status_id } = action.data
|
||||
return state.withMutations((map) => {
|
||||
if (action.data.favourited !== undefined) map.setIn([status_id, 'favourited'], action.data.favourited)
|
||||
if (action.data.favourites_count !== undefined) map.setIn([status_id, 'favourites_count'], action.data.favourites_count)
|
||||
if (action.data.reblogged !== undefined) map.setIn([status_id, 'reblogged'], action.data.reblogged)
|
||||
if (action.data.reblogs_count !== undefined) map.setIn([status_id, 'reblogs_count'], action.data.reblogs_count)
|
||||
if (action.data.replies_count !== undefined) map.setIn([status_id, 'replies_count'], action.data.replies_count)
|
||||
if (action.data.pinned !== undefined) map.setIn([status_id, 'pinned'], action.data.pinned)
|
||||
if (action.data.pinned_by_group !== undefined) map.setIn([status_id, 'pinned_by_group'], action.data.pinned_by_group)
|
||||
if (action.data.bookmarked !== undefined) map.setIn([status_id, 'bookmarked'], action.data.bookmarked)
|
||||
})
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable'
|
||||
import compareId from '../utils/compare_id'
|
||||
import {
|
||||
TIMELINE_UPDATE,
|
||||
TIMELINE_DELETE,
|
||||
@@ -11,17 +13,19 @@ import {
|
||||
TIMELINE_DEQUEUE,
|
||||
MAX_QUEUED_ITEMS,
|
||||
TIMELINE_SCROLL_TOP,
|
||||
} from '../actions/timelines';
|
||||
} from '../actions/timelines'
|
||||
import {
|
||||
ACCOUNT_BLOCK_SUCCESS,
|
||||
ACCOUNT_MUTE_SUCCESS,
|
||||
ACCOUNT_UNFOLLOW_SUCCESS,
|
||||
} from '../actions/accounts';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
import compareId from '../utils/compare_id';
|
||||
import { GROUP_REMOVE_STATUS_SUCCESS } from '../actions/groups';
|
||||
} from '../actions/accounts'
|
||||
import {
|
||||
GROUP_REMOVE_STATUS_SUCCESS,
|
||||
GROUP_UNPIN_STATUS_SUCCESS,
|
||||
} from '../actions/groups'
|
||||
import { UNPIN_SUCCESS } from '../actions/interactions'
|
||||
|
||||
const initialState = ImmutableMap();
|
||||
const initialState = ImmutableMap()
|
||||
|
||||
const initialTimeline = ImmutableMap({
|
||||
unread: 0,
|
||||
@@ -33,14 +37,14 @@ const initialTimeline = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
queuedItems: ImmutableList(), //max= MAX_QUEUED_ITEMS
|
||||
totalQueuedItemsCount: 0, //used for queuedItems overflow for MAX_QUEUED_ITEMS+
|
||||
});
|
||||
})
|
||||
|
||||
const expandNormalizedTimeline = (state, timeline, statuses, next, isPartial, isLoadingRecent) => {
|
||||
return state.update(timeline, initialTimeline, map => map.withMutations(mMap => {
|
||||
mMap.set('isLoading', false);
|
||||
mMap.set('isPartial', isPartial);
|
||||
return state.update(timeline, initialTimeline, map => map.withMutations((mMap) => {
|
||||
mMap.set('isLoading', false)
|
||||
mMap.set('isPartial', isPartial)
|
||||
|
||||
if (!next && !isLoadingRecent) mMap.set('hasMore', false);
|
||||
if (!next && !isLoadingRecent) mMap.set('hasMore', false)
|
||||
|
||||
if (!statuses.isEmpty()) {
|
||||
mMap.update('items', ImmutableList(), oldIds => {
|
||||
@@ -160,8 +164,16 @@ const filterTimeline = (timeline, state, relationship, statuses) =>
|
||||
));
|
||||
|
||||
const removeStatusFromGroup = (state, groupId, statusId) => {
|
||||
return state.updateIn([`group:${groupId}`, 'items'], list => list.filterNot(item => item === statusId));
|
||||
};
|
||||
return state.updateIn([`group:${groupId}`, 'items'], list => list.filterNot(item => item === statusId))
|
||||
}
|
||||
|
||||
const removeStatusFromGroupPins = (state, groupId, statusId) => {
|
||||
return state.updateIn([`group:${groupId}:pinned`, 'items'], list => list.filterNot(item => item === statusId))
|
||||
}
|
||||
|
||||
const removeStatusFromAccountPins = (state, accountId, statusId) => {
|
||||
return state.updateIn([`account:${accountId}:pinned`, 'items'], list => list.filterNot(item => item === statusId))
|
||||
}
|
||||
|
||||
export default function timelines(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
@@ -201,10 +213,14 @@ export default function timelines(state = initialState, action) {
|
||||
action.timeline,
|
||||
initialTimeline,
|
||||
map => map.set('online', false).update('items', items => items.first() ? items.unshift(null) : items)
|
||||
);
|
||||
)
|
||||
case UNPIN_SUCCESS:
|
||||
return removeStatusFromAccountPins(state, action.accountId, action.status.get('id'))
|
||||
case GROUP_REMOVE_STATUS_SUCCESS:
|
||||
return removeStatusFromGroup(state, action.groupId, action.id)
|
||||
return removeStatusFromGroup(state, action.groupId, action.statusId)
|
||||
case GROUP_UNPIN_STATUS_SUCCESS:
|
||||
return removeStatusFromGroupPins(state, action.groupId, action.statusId)
|
||||
default:
|
||||
return state;
|
||||
return state
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -23,8 +23,18 @@ import {
|
||||
FOLLOW_REQUEST_REJECT_SUCCESS,
|
||||
} from '../actions/accounts'
|
||||
import {
|
||||
REPOSTS_FETCH_REQUEST,
|
||||
REPOSTS_FETCH_SUCCESS,
|
||||
REPOSTS_FETCH_FAIL,
|
||||
REPOSTS_EXPAND_REQUEST,
|
||||
REPOSTS_EXPAND_SUCCESS,
|
||||
REPOSTS_EXPAND_FAIL,
|
||||
LIKES_FETCH_REQUEST,
|
||||
LIKES_FETCH_SUCCESS,
|
||||
LIKES_FETCH_FAIL,
|
||||
LIKES_EXPAND_REQUEST,
|
||||
LIKES_EXPAND_SUCCESS,
|
||||
LIKES_EXPAND_FAIL,
|
||||
} from '../actions/interactions'
|
||||
import {
|
||||
BLOCKS_FETCH_REQUEST,
|
||||
@@ -52,6 +62,7 @@ import {
|
||||
GROUP_JOIN_REQUESTS_EXPAND_SUCCESS,
|
||||
GROUP_JOIN_REQUESTS_APPROVE_SUCCESS,
|
||||
GROUP_JOIN_REQUESTS_REJECT_SUCCESS,
|
||||
GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS,
|
||||
} from '../actions/groups'
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
@@ -119,12 +130,28 @@ export default function userLists(state = initialState, action) {
|
||||
case FOLLOWING_EXPAND_FAIL:
|
||||
return state.setIn(['following', action.id, 'isLoading'], false);
|
||||
|
||||
case REPOSTS_FETCH_REQUEST:
|
||||
case REPOSTS_EXPAND_REQUEST:
|
||||
return state.setIn(['reblogged_by', action.statusId, 'isLoading'], true)
|
||||
case REPOSTS_FETCH_SUCCESS:
|
||||
return state.setIn(['reblogged_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
|
||||
return normalizeList(state, 'reblogged_by', action.statusId, action.accounts, action.next)
|
||||
case REPOSTS_EXPAND_SUCCESS:
|
||||
return appendToList(state, 'reblogged_by', action.statusId, action.accounts, action.next)
|
||||
case REPOSTS_FETCH_FAIL:
|
||||
case REPOSTS_EXPAND_FAIL:
|
||||
return setListFailed(state, 'reblogged_by', action.statusId)
|
||||
|
||||
case LIKES_FETCH_REQUEST:
|
||||
case LIKES_EXPAND_REQUEST:
|
||||
return state.setIn(['liked_by', action.statusId, 'isLoading'], true)
|
||||
case LIKES_FETCH_SUCCESS:
|
||||
return state.setIn(['liked_by', action.id], ImmutableList(action.accounts.map(item => item.id)));
|
||||
|
||||
return normalizeList(state, 'liked_by', action.statusId, action.accounts, action.next)
|
||||
case LIKES_EXPAND_SUCCESS:
|
||||
return appendToList(state, 'liked_by', action.statusId, action.accounts, action.next)
|
||||
case LIKES_FETCH_FAIL:
|
||||
case LIKES_EXPAND_FAIL:
|
||||
return setListFailed(state, 'liked_by', action.statusId)
|
||||
|
||||
case FOLLOW_REQUESTS_FETCH_SUCCESS:
|
||||
return normalizeList(state, 'follow_requests', me, action.accounts, action.next);
|
||||
case FOLLOW_REQUESTS_EXPAND_SUCCESS:
|
||||
@@ -162,21 +189,24 @@ export default function userLists(state = initialState, action) {
|
||||
return setListFailed(state, 'mutes', me)
|
||||
|
||||
case GROUP_MEMBERS_FETCH_SUCCESS:
|
||||
return normalizeList(state, 'groups', action.id, action.accounts, action.next);
|
||||
return normalizeList(state, 'groups', action.groupId, action.accounts, action.next);
|
||||
case GROUP_MEMBERS_EXPAND_SUCCESS:
|
||||
return appendToList(state, 'groups', action.id, action.accounts, action.next);
|
||||
return appendToList(state, 'groups', action.groupId, action.accounts, action.next);
|
||||
|
||||
case GROUP_REMOVED_ACCOUNTS_CREATE_SUCCESS:
|
||||
return state.updateIn(['groups', action.groupId, 'items'], list => list.filterNot(item => item === action.accountId));
|
||||
|
||||
case GROUP_REMOVED_ACCOUNTS_FETCH_SUCCESS:
|
||||
return normalizeList(state, 'group_removed_accounts', action.id, action.accounts, action.next);
|
||||
return normalizeList(state, 'group_removed_accounts', action.groupId, action.accounts, action.next);
|
||||
case GROUP_REMOVED_ACCOUNTS_EXPAND_SUCCESS:
|
||||
return appendToList(state, 'group_removed_accounts', action.id, action.accounts, action.next);
|
||||
return appendToList(state, 'group_removed_accounts', action.groupId, action.accounts, action.next);
|
||||
case GROUP_REMOVED_ACCOUNTS_REMOVE_SUCCESS:
|
||||
return state.updateIn(['group_removed_accounts', action.groupId, 'items'], list => list.filterNot(item => item === action.id));
|
||||
return state.updateIn(['group_removed_accounts', action.groupId, 'items'], list => list.filterNot(item => item === action.accountId));
|
||||
|
||||
case GROUP_JOIN_REQUESTS_FETCH_SUCCESS:
|
||||
return normalizeList(state, 'group_join_requests', action.id, action.accounts, action.next);
|
||||
return normalizeList(state, 'group_join_requests', action.groupId, action.accounts, action.next);
|
||||
case GROUP_JOIN_REQUESTS_EXPAND_SUCCESS:
|
||||
return appendToList(state, 'group_join_requests', action.id, action.accounts, action.next);
|
||||
return appendToList(state, 'group_join_requests', action.groupId, action.accounts, action.next);
|
||||
case GROUP_JOIN_REQUESTS_APPROVE_SUCCESS:
|
||||
case GROUP_JOIN_REQUESTS_REJECT_SUCCESS:
|
||||
return state.updateIn(['group_join_requests', action.groupId, 'items'], list => list.filterNot(item => item === action.accountId));
|
||||
|
||||
Reference in New Issue
Block a user