Merge branch 'group-admin-fix' into 'develop'
Allow admins to delete Group statuses See merge request soapbox-pub/soapbox!2533
This commit is contained in:
commit
ae75c9ac0d
|
@ -1,16 +1,19 @@
|
||||||
{
|
{
|
||||||
"note": "patriots 900000001",
|
|
||||||
"discoverable": true,
|
|
||||||
"id": "109989480368015378",
|
|
||||||
"domain": null,
|
|
||||||
"avatar": "https://media.covfefe.social/groups/avatars/109/989/480/368/015/378/original/50b0d899bc5aae13.jpg",
|
"avatar": "https://media.covfefe.social/groups/avatars/109/989/480/368/015/378/original/50b0d899bc5aae13.jpg",
|
||||||
"avatar_static": "https://media.covfefe.social/groups/avatars/109/989/480/368/015/378/original/50b0d899bc5aae13.jpg",
|
"avatar_static": "https://media.covfefe.social/groups/avatars/109/989/480/368/015/378/original/50b0d899bc5aae13.jpg",
|
||||||
|
"created_at": "2023-03-08T00:00:00.000Z",
|
||||||
|
"discoverable": true,
|
||||||
|
"display_name": "PATRIOT PATRIOTS",
|
||||||
|
"domain": null,
|
||||||
|
"group_visibility": "everyone",
|
||||||
"header": "https://media.covfefe.social/groups/headers/109/989/480/368/015/378/original/c5063b59f919cd4a.png",
|
"header": "https://media.covfefe.social/groups/headers/109/989/480/368/015/378/original/c5063b59f919cd4a.png",
|
||||||
"header_static": "https://media.covfefe.social/groups/headers/109/989/480/368/015/378/original/c5063b59f919cd4a.png",
|
"header_static": "https://media.covfefe.social/groups/headers/109/989/480/368/015/378/original/c5063b59f919cd4a.png",
|
||||||
"group_visibility": "everyone",
|
"id": "109989480368015378",
|
||||||
"created_at": "2023-03-08T00:00:00.000Z",
|
|
||||||
"display_name": "PATRIOT PATRIOTS",
|
|
||||||
"membership_required": true,
|
|
||||||
"members_count": 1,
|
"members_count": 1,
|
||||||
|
"membership_required": true,
|
||||||
|
"note": "patriots 900000001",
|
||||||
|
"owner": {
|
||||||
|
"id": "424023483294040"
|
||||||
|
},
|
||||||
"tags": []
|
"tags": []
|
||||||
}
|
}
|
|
@ -7,10 +7,11 @@ import api from '../api';
|
||||||
|
|
||||||
import { fetchRelationships } from './accounts';
|
import { fetchRelationships } from './accounts';
|
||||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||||
|
import { expandGroupFeaturedTimeline } from './timelines';
|
||||||
|
|
||||||
import type { AxiosError } from 'axios';
|
import type { AxiosError } from 'axios';
|
||||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||||
import type { APIEntity, Status as StatusEntity } from 'soapbox/types/entities';
|
import type { APIEntity, Group, Status as StatusEntity } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const REBLOG_REQUEST = 'REBLOG_REQUEST';
|
const REBLOG_REQUEST = 'REBLOG_REQUEST';
|
||||||
const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
|
const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
|
||||||
|
@ -511,6 +512,20 @@ const pin = (status: StatusEntity) =>
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const pinToGroup = (status: StatusEntity, group: Group) =>
|
||||||
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
|
return api(getState)
|
||||||
|
.post(`/api/v1/groups/${group.id}/statuses/${status.get('id')}/pin`)
|
||||||
|
.then(() => dispatch(expandGroupFeaturedTimeline(group.id)));
|
||||||
|
};
|
||||||
|
|
||||||
|
const unpinFromGroup = (status: StatusEntity, group: Group) =>
|
||||||
|
(dispatch: AppDispatch, getState: () => RootState) => {
|
||||||
|
return api(getState)
|
||||||
|
.post(`/api/v1/groups/${group.id}/statuses/${status.get('id')}/unpin`)
|
||||||
|
.then(() => dispatch(expandGroupFeaturedTimeline(group.id)));
|
||||||
|
};
|
||||||
|
|
||||||
const pinRequest = (status: StatusEntity) => ({
|
const pinRequest = (status: StatusEntity) => ({
|
||||||
type: PIN_REQUEST,
|
type: PIN_REQUEST,
|
||||||
status,
|
status,
|
||||||
|
@ -715,6 +730,8 @@ export {
|
||||||
unpinSuccess,
|
unpinSuccess,
|
||||||
unpinFail,
|
unpinFail,
|
||||||
togglePin,
|
togglePin,
|
||||||
|
pinToGroup,
|
||||||
|
unpinFromGroup,
|
||||||
remoteInteraction,
|
remoteInteraction,
|
||||||
remoteInteractionRequest,
|
remoteInteractionRequest,
|
||||||
remoteInteractionSuccess,
|
remoteInteractionSuccess,
|
||||||
|
|
|
@ -248,6 +248,9 @@ const expandListTimeline = (id: string, { maxId }: Record<string, any> = {}, don
|
||||||
const expandGroupTimeline = (id: string, { maxId }: Record<string, any> = {}, done = noOp) =>
|
const expandGroupTimeline = (id: string, { maxId }: Record<string, any> = {}, done = noOp) =>
|
||||||
expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { max_id: maxId }, done);
|
expandTimeline(`group:${id}`, `/api/v1/timelines/group/${id}`, { max_id: maxId }, done);
|
||||||
|
|
||||||
|
const expandGroupFeaturedTimeline = (id: string) =>
|
||||||
|
expandTimeline(`group:${id}:pinned`, `/api/v1/timelines/group/${id}`, { pinned: true });
|
||||||
|
|
||||||
const expandGroupTimelineFromTag = (id: string, tagName: string, { maxId }: Record<string, any> = {}, done = noOp) =>
|
const expandGroupTimelineFromTag = (id: string, tagName: string, { maxId }: Record<string, any> = {}, done = noOp) =>
|
||||||
expandTimeline(`group:tags:${id}:${tagName}`, `/api/v1/timelines/group/${id}/tags/${tagName}`, { max_id: maxId }, done);
|
expandTimeline(`group:tags:${id}:${tagName}`, `/api/v1/timelines/group/${id}/tags/${tagName}`, { max_id: maxId }, done);
|
||||||
|
|
||||||
|
@ -353,6 +356,7 @@ export {
|
||||||
expandAccountMediaTimeline,
|
expandAccountMediaTimeline,
|
||||||
expandListTimeline,
|
expandListTimeline,
|
||||||
expandGroupTimeline,
|
expandGroupTimeline,
|
||||||
|
expandGroupFeaturedTimeline,
|
||||||
expandGroupTimelineFromTag,
|
expandGroupTimelineFromTag,
|
||||||
expandGroupMediaTimeline,
|
expandGroupMediaTimeline,
|
||||||
expandHashtagTimeline,
|
expandHashtagTimeline,
|
||||||
|
|
|
@ -7,7 +7,7 @@ import { blockAccount } from 'soapbox/actions/accounts';
|
||||||
import { launchChat } from 'soapbox/actions/chats';
|
import { launchChat } from 'soapbox/actions/chats';
|
||||||
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'soapbox/actions/compose';
|
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'soapbox/actions/compose';
|
||||||
import { editEvent } from 'soapbox/actions/events';
|
import { editEvent } from 'soapbox/actions/events';
|
||||||
import { toggleBookmark, toggleDislike, toggleFavourite, togglePin, toggleReblog } from 'soapbox/actions/interactions';
|
import { pinToGroup, toggleBookmark, toggleDislike, toggleFavourite, togglePin, toggleReblog, unpinFromGroup } from 'soapbox/actions/interactions';
|
||||||
import { openModal } from 'soapbox/actions/modals';
|
import { openModal } from 'soapbox/actions/modals';
|
||||||
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
import { deleteStatusModal, toggleStatusSensitivityModal } from 'soapbox/actions/moderation';
|
||||||
import { initMuteModal } from 'soapbox/actions/mutes';
|
import { initMuteModal } from 'soapbox/actions/mutes';
|
||||||
|
@ -32,64 +32,67 @@ import type { Menu } from 'soapbox/components/dropdown-menu';
|
||||||
import type { Account, Group, Status } from 'soapbox/types/entities';
|
import type { Account, Group, Status } from 'soapbox/types/entities';
|
||||||
|
|
||||||
const messages = defineMessages({
|
const messages = defineMessages({
|
||||||
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
|
||||||
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },
|
|
||||||
edit: { id: 'status.edit', defaultMessage: 'Edit' },
|
|
||||||
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
|
|
||||||
chat: { id: 'status.chat', defaultMessage: 'Chat with @{name}' },
|
|
||||||
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
|
|
||||||
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
|
||||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
|
||||||
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
|
||||||
share: { id: 'status.share', defaultMessage: 'Share' },
|
|
||||||
more: { id: 'status.more', defaultMessage: 'More' },
|
|
||||||
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
|
||||||
reblog: { id: 'status.reblog', defaultMessage: 'Repost' },
|
|
||||||
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Repost to original audience' },
|
|
||||||
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
|
|
||||||
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be reposted' },
|
|
||||||
favourite: { id: 'status.favourite', defaultMessage: 'Like' },
|
|
||||||
disfavourite: { id: 'status.disfavourite', defaultMessage: 'Disike' },
|
|
||||||
open: { id: 'status.open', defaultMessage: 'Expand this post' },
|
|
||||||
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
|
||||||
unbookmark: { id: 'status.unbookmark', defaultMessage: 'Remove bookmark' },
|
|
||||||
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
|
||||||
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
|
|
||||||
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
|
|
||||||
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
|
||||||
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
|
||||||
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
|
||||||
adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' },
|
adminAccount: { id: 'status.admin_account', defaultMessage: 'Moderate @{name}' },
|
||||||
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
|
admin_status: { id: 'status.admin_status', defaultMessage: 'Open this post in the moderation interface' },
|
||||||
|
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||||
|
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
||||||
|
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
||||||
|
bookmark: { id: 'status.bookmark', defaultMessage: 'Bookmark' },
|
||||||
|
cancel_reblog_private: { id: 'status.cancel_reblog_private', defaultMessage: 'Un-repost' },
|
||||||
|
cannot_reblog: { id: 'status.cannot_reblog', defaultMessage: 'This post cannot be reposted' },
|
||||||
|
chat: { id: 'status.chat', defaultMessage: 'Chat with @{name}' },
|
||||||
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
|
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
|
||||||
group_remove_account: { id: 'status.remove_account_from_group', defaultMessage: 'Remove account from group' },
|
|
||||||
group_remove_post: { id: 'status.remove_post_from_group', defaultMessage: 'Remove post from group' },
|
|
||||||
external: { id: 'status.external', defaultMessage: 'View post on {domain}' },
|
|
||||||
deactivateUser: { id: 'admin.users.actions.deactivate_user', defaultMessage: 'Deactivate @{name}' },
|
deactivateUser: { id: 'admin.users.actions.deactivate_user', defaultMessage: 'Deactivate @{name}' },
|
||||||
deleteUser: { id: 'admin.users.actions.delete_user', defaultMessage: 'Delete @{name}' },
|
delete: { id: 'status.delete', defaultMessage: 'Delete' },
|
||||||
deleteStatus: { id: 'admin.statuses.actions.delete_status', defaultMessage: 'Delete post' },
|
|
||||||
markStatusSensitive: { id: 'admin.statuses.actions.mark_status_sensitive', defaultMessage: 'Mark post sensitive' },
|
|
||||||
markStatusNotSensitive: { id: 'admin.statuses.actions.mark_status_not_sensitive', defaultMessage: 'Mark post not sensitive' },
|
|
||||||
reactionLike: { id: 'status.reactions.like', defaultMessage: 'Like' },
|
|
||||||
reactionHeart: { id: 'status.reactions.heart', defaultMessage: 'Love' },
|
|
||||||
reactionLaughing: { id: 'status.reactions.laughing', defaultMessage: 'Haha' },
|
|
||||||
reactionOpenMouth: { id: 'status.reactions.open_mouth', defaultMessage: 'Wow' },
|
|
||||||
reactionCry: { id: 'status.reactions.cry', defaultMessage: 'Sad' },
|
|
||||||
reactionWeary: { id: 'status.reactions.weary', defaultMessage: 'Weary' },
|
|
||||||
quotePost: { id: 'status.quote', defaultMessage: 'Quote post' },
|
|
||||||
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
|
||||||
|
deleteFromGroupMessage: { id: 'confirmations.delete_from_group.message', defaultMessage: 'Are you sure you want to delete @{name}\'s post?' },
|
||||||
deleteHeading: { id: 'confirmations.delete.heading', defaultMessage: 'Delete post' },
|
deleteHeading: { id: 'confirmations.delete.heading', defaultMessage: 'Delete post' },
|
||||||
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this post?' },
|
deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this post?' },
|
||||||
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
deleteStatus: { id: 'admin.statuses.actions.delete_status', defaultMessage: 'Delete post' },
|
||||||
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this post and re-draft it? Favorites and reposts will be lost, and replies to the original post will be orphaned.' },
|
deleteUser: { id: 'admin.users.actions.delete_user', defaultMessage: 'Delete @{name}' },
|
||||||
blockConfirm: { id: 'confirmations.block.confirm', defaultMessage: 'Block' },
|
direct: { id: 'status.direct', defaultMessage: 'Direct message @{name}' },
|
||||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
disfavourite: { id: 'status.disfavourite', defaultMessage: 'Disike' },
|
||||||
redraftHeading: { id: 'confirmations.redraft.heading', defaultMessage: 'Delete & redraft' },
|
edit: { id: 'status.edit', defaultMessage: 'Edit' },
|
||||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
embed: { id: 'status.embed', defaultMessage: 'Embed' },
|
||||||
blockAndReport: { id: 'confirmations.block.block_and_report', defaultMessage: 'Block & Report' },
|
external: { id: 'status.external', defaultMessage: 'View post on {domain}' },
|
||||||
replies_disabled_group: { id: 'status.disabled_replies.group_membership', defaultMessage: 'Only group members can reply' },
|
favourite: { id: 'status.favourite', defaultMessage: 'Like' },
|
||||||
groupModDelete: { id: 'status.group_mod_delete', defaultMessage: 'Delete post from group' },
|
groupModDelete: { id: 'status.group_mod_delete', defaultMessage: 'Delete post from group' },
|
||||||
deleteFromGroupMessage: { id: 'confirmations.delete_from_group.message', defaultMessage: 'Are you sure you want to delete @{name}\'s post?' },
|
group_remove_account: { id: 'status.remove_account_from_group', defaultMessage: 'Remove account from group' },
|
||||||
|
group_remove_post: { id: 'status.remove_post_from_group', defaultMessage: 'Remove post from group' },
|
||||||
|
markStatusNotSensitive: { id: 'admin.statuses.actions.mark_status_not_sensitive', defaultMessage: 'Mark post not sensitive' },
|
||||||
|
markStatusSensitive: { id: 'admin.statuses.actions.mark_status_sensitive', defaultMessage: 'Mark post sensitive' },
|
||||||
|
mention: { id: 'status.mention', defaultMessage: 'Mention @{name}' },
|
||||||
|
more: { id: 'status.more', defaultMessage: 'More' },
|
||||||
|
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
|
||||||
|
muteConversation: { id: 'status.mute_conversation', defaultMessage: 'Mute conversation' },
|
||||||
|
open: { id: 'status.open', defaultMessage: 'Expand this post' },
|
||||||
|
pin: { id: 'status.pin', defaultMessage: 'Pin on profile' },
|
||||||
|
pinToGroup: { id: 'status.pin_to_group', defaultMessage: 'Pin to Group' },
|
||||||
|
pinToGroupSuccess: { id: 'status.pin_to_group.success', defaultMessage: 'Pinned to Group!' },
|
||||||
|
unpinFromGroup: { id: 'status.unpin_to_group', defaultMessage: 'Unpin from Group' },
|
||||||
|
quotePost: { id: 'status.quote', defaultMessage: 'Quote post' },
|
||||||
|
reactionCry: { id: 'status.reactions.cry', defaultMessage: 'Sad' },
|
||||||
|
reactionHeart: { id: 'status.reactions.heart', defaultMessage: 'Love' },
|
||||||
|
reactionLaughing: { id: 'status.reactions.laughing', defaultMessage: 'Haha' },
|
||||||
|
reactionLike: { id: 'status.reactions.like', defaultMessage: 'Like' },
|
||||||
|
reactionOpenMouth: { id: 'status.reactions.open_mouth', defaultMessage: 'Wow' },
|
||||||
|
reactionWeary: { id: 'status.reactions.weary', defaultMessage: 'Weary' },
|
||||||
|
reblog: { id: 'status.reblog', defaultMessage: 'Repost' },
|
||||||
|
reblog_private: { id: 'status.reblog_private', defaultMessage: 'Repost to original audience' },
|
||||||
|
redraft: { id: 'status.redraft', defaultMessage: 'Delete & re-draft' },
|
||||||
|
redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
|
||||||
|
redraftHeading: { id: 'confirmations.redraft.heading', defaultMessage: 'Delete & redraft' },
|
||||||
|
redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this post and re-draft it? Favorites and reposts will be lost, and replies to the original post will be orphaned.' },
|
||||||
|
replies_disabled_group: { id: 'status.disabled_replies.group_membership', defaultMessage: 'Only group members can reply' },
|
||||||
|
reply: { id: 'status.reply', defaultMessage: 'Reply' },
|
||||||
|
replyAll: { id: 'status.replyAll', defaultMessage: 'Reply to thread' },
|
||||||
|
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||||
|
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||||
|
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
|
||||||
|
share: { id: 'status.share', defaultMessage: 'Share' },
|
||||||
|
unbookmark: { id: 'status.unbookmark', defaultMessage: 'Remove bookmark' },
|
||||||
|
unmuteConversation: { id: 'status.unmute_conversation', defaultMessage: 'Unmute conversation' },
|
||||||
|
unpin: { id: 'status.unpin', defaultMessage: 'Unpin from profile' },
|
||||||
});
|
});
|
||||||
|
|
||||||
interface IStatusActionBar {
|
interface IStatusActionBar {
|
||||||
|
@ -232,6 +235,18 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
dispatch(togglePin(status));
|
dispatch(togglePin(status));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleGroupPinClick: React.EventHandler<React.MouseEvent> = () => {
|
||||||
|
const group = status.group as Group;
|
||||||
|
|
||||||
|
if (status.pinned) {
|
||||||
|
dispatch(unpinFromGroup(status, group));
|
||||||
|
} else {
|
||||||
|
dispatch(pinToGroup(status, group))
|
||||||
|
.then(() => toast.success(intl.formatMessage(messages.pinToGroupSuccess)))
|
||||||
|
.catch(() => null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleMentionClick: React.EventHandler<React.MouseEvent> = (e) => {
|
const handleMentionClick: React.EventHandler<React.MouseEvent> = (e) => {
|
||||||
dispatch(mentionCompose(status.account as Account));
|
dispatch(mentionCompose(status.account as Account));
|
||||||
};
|
};
|
||||||
|
@ -358,6 +373,19 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
return menu;
|
return menu;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isGroupStatus = typeof status.group === 'object';
|
||||||
|
if (isGroupStatus && !!status.group) {
|
||||||
|
const isGroupOwner = groupRelationship?.role === GroupRoles.OWNER;
|
||||||
|
|
||||||
|
if (isGroupOwner) {
|
||||||
|
menu.push({
|
||||||
|
text: intl.formatMessage(status.pinned ? messages.unpinFromGroup : messages.pinToGroup),
|
||||||
|
action: handleGroupPinClick,
|
||||||
|
icon: status.pinned ? require('@tabler/icons/pinned-off.svg') : require('@tabler/icons/pin.svg'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (features.bookmarks) {
|
if (features.bookmarks) {
|
||||||
menu.push({
|
menu.push({
|
||||||
text: intl.formatMessage(status.bookmarked ? messages.unbookmark : messages.bookmark),
|
text: intl.formatMessage(status.bookmarked ? messages.unbookmark : messages.bookmark),
|
||||||
|
@ -460,18 +488,23 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status.group &&
|
if (isGroupStatus && !!status.group) {
|
||||||
groupRelationship?.role &&
|
const group = status.group as Group;
|
||||||
[GroupRoles.OWNER].includes(groupRelationship.role) &&
|
const account = status.account as Account;
|
||||||
!ownAccount
|
const isGroupOwner = groupRelationship?.role === GroupRoles.OWNER;
|
||||||
) {
|
const isGroupAdmin = groupRelationship?.role === GroupRoles.ADMIN;
|
||||||
menu.push(null);
|
const isStatusFromOwner = group.owner.id === account.id;
|
||||||
menu.push({
|
const canDeleteStatus = !ownAccount && (isGroupOwner || (isGroupAdmin && !isStatusFromOwner));
|
||||||
text: intl.formatMessage(messages.groupModDelete),
|
|
||||||
action: handleDeleteFromGroup,
|
if (canDeleteStatus) {
|
||||||
icon: require('@tabler/icons/trash.svg'),
|
menu.push(null);
|
||||||
destructive: true,
|
menu.push({
|
||||||
});
|
text: intl.formatMessage(messages.groupModDelete),
|
||||||
|
action: handleDeleteFromGroup,
|
||||||
|
icon: require('@tabler/icons/trash.svg'),
|
||||||
|
destructive: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStaff) {
|
if (isStaff) {
|
||||||
|
|
|
@ -5,11 +5,12 @@ import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
import { groupCompose, setGroupTimelineVisible, uploadCompose } from 'soapbox/actions/compose';
|
import { groupCompose, setGroupTimelineVisible, uploadCompose } from 'soapbox/actions/compose';
|
||||||
import { connectGroupStream } from 'soapbox/actions/streaming';
|
import { connectGroupStream } from 'soapbox/actions/streaming';
|
||||||
import { expandGroupTimeline } from 'soapbox/actions/timelines';
|
import { expandGroupFeaturedTimeline, expandGroupTimeline } from 'soapbox/actions/timelines';
|
||||||
import { useGroup } from 'soapbox/api/hooks';
|
import { useGroup } from 'soapbox/api/hooks';
|
||||||
import { Avatar, HStack, Icon, Stack, Text, Toggle } from 'soapbox/components/ui';
|
import { Avatar, HStack, Icon, Stack, Text, Toggle } from 'soapbox/components/ui';
|
||||||
import ComposeForm from 'soapbox/features/compose/components/compose-form';
|
import ComposeForm from 'soapbox/features/compose/components/compose-form';
|
||||||
import { useAppDispatch, useAppSelector, useDraggedFiles, useOwnAccount } from 'soapbox/hooks';
|
import { useAppDispatch, useAppSelector, useDraggedFiles, useOwnAccount } from 'soapbox/hooks';
|
||||||
|
import { makeGetStatusIds } from 'soapbox/selectors';
|
||||||
|
|
||||||
import Timeline from '../ui/components/timeline';
|
import Timeline from '../ui/components/timeline';
|
||||||
|
|
||||||
|
@ -19,6 +20,8 @@ interface IGroupTimeline {
|
||||||
params: RouteParams
|
params: RouteParams
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getStatusIds = makeGetStatusIds();
|
||||||
|
|
||||||
const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
|
const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
const account = useOwnAccount();
|
const account = useOwnAccount();
|
||||||
|
@ -32,6 +35,7 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
|
||||||
const composeId = `group:${groupId}`;
|
const composeId = `group:${groupId}`;
|
||||||
const canComposeGroupStatus = !!account && group?.relationship?.member;
|
const canComposeGroupStatus = !!account && group?.relationship?.member;
|
||||||
const groupTimelineVisible = useAppSelector((state) => !!state.compose.get(composeId)?.group_timeline_visible);
|
const groupTimelineVisible = useAppSelector((state) => !!state.compose.get(composeId)?.group_timeline_visible);
|
||||||
|
const featuredStatusIds = useAppSelector((state) => getStatusIds(state, { type: `group:${group?.id}:pinned` }));
|
||||||
|
|
||||||
const { isDragging, isDraggedOver } = useDraggedFiles(composer, (files) => {
|
const { isDragging, isDraggedOver } = useDraggedFiles(composer, (files) => {
|
||||||
dispatch(uploadCompose(composeId, files, intl));
|
dispatch(uploadCompose(composeId, files, intl));
|
||||||
|
@ -47,6 +51,7 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
dispatch(expandGroupTimeline(groupId));
|
dispatch(expandGroupTimeline(groupId));
|
||||||
|
dispatch(expandGroupFeaturedTimeline(groupId));
|
||||||
dispatch(groupCompose(composeId, groupId));
|
dispatch(groupCompose(composeId, groupId));
|
||||||
|
|
||||||
const disconnect = dispatch(connectGroupStream(groupId));
|
const disconnect = dispatch(connectGroupStream(groupId));
|
||||||
|
@ -123,6 +128,7 @@ const GroupTimeline: React.FC<IGroupTimeline> = (props) => {
|
||||||
emptyMessageCard={false}
|
emptyMessageCard={false}
|
||||||
divideType='border'
|
divideType='border'
|
||||||
showGroup={false}
|
showGroup={false}
|
||||||
|
featuredStatusIds={featuredStatusIds}
|
||||||
/>
|
/>
|
||||||
</Stack>
|
</Stack>
|
||||||
);
|
);
|
||||||
|
|
|
@ -40,6 +40,9 @@ function buildCard(props: Partial<Card> = {}): Card {
|
||||||
function buildGroup(props: Partial<Group> = {}): Group {
|
function buildGroup(props: Partial<Group> = {}): Group {
|
||||||
return groupSchema.parse(Object.assign({
|
return groupSchema.parse(Object.assign({
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
|
owner: {
|
||||||
|
id: uuidv4(),
|
||||||
|
},
|
||||||
}, props));
|
}, props));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1466,6 +1466,8 @@
|
||||||
"status.mute_conversation": "Mute Conversation",
|
"status.mute_conversation": "Mute Conversation",
|
||||||
"status.open": "Show Post Details",
|
"status.open": "Show Post Details",
|
||||||
"status.pin": "Pin on profile",
|
"status.pin": "Pin on profile",
|
||||||
|
"status.pin_to_group": "Pin to Group",
|
||||||
|
"status.pin_to_group.success": "Pinned to Group!",
|
||||||
"status.pinned": "Pinned post",
|
"status.pinned": "Pinned post",
|
||||||
"status.quote": "Quote post",
|
"status.quote": "Quote post",
|
||||||
"status.reactions.cry": "Sad",
|
"status.reactions.cry": "Sad",
|
||||||
|
@ -1502,6 +1504,7 @@
|
||||||
"status.unbookmarked": "Bookmark removed.",
|
"status.unbookmarked": "Bookmark removed.",
|
||||||
"status.unmute_conversation": "Unmute Conversation",
|
"status.unmute_conversation": "Unmute Conversation",
|
||||||
"status.unpin": "Unpin from profile",
|
"status.unpin": "Unpin from profile",
|
||||||
|
"status.unpin_to_group": "Unpin from Group",
|
||||||
"status_list.queue_label": "Click to see {count} new {count, plural, one {post} other {posts}}",
|
"status_list.queue_label": "Click to see {count} new {count, plural, one {post} other {posts}}",
|
||||||
"statuses.quote_tombstone": "Post is unavailable.",
|
"statuses.quote_tombstone": "Post is unavailable.",
|
||||||
"statuses.tombstone": "One or more posts are unavailable.",
|
"statuses.tombstone": "One or more posts are unavailable.",
|
||||||
|
|
|
@ -32,6 +32,9 @@ export const GroupRecord = ImmutableRecord({
|
||||||
locked: false,
|
locked: false,
|
||||||
membership_required: false,
|
membership_required: false,
|
||||||
members_count: 0,
|
members_count: 0,
|
||||||
|
owner: {
|
||||||
|
id: '',
|
||||||
|
},
|
||||||
note: '',
|
note: '',
|
||||||
statuses_visibility: 'public',
|
statuses_visibility: 'public',
|
||||||
slug: '',
|
slug: '',
|
||||||
|
|
|
@ -27,6 +27,7 @@ const groupSchema = z.object({
|
||||||
locked: z.boolean().catch(false),
|
locked: z.boolean().catch(false),
|
||||||
membership_required: z.boolean().catch(false),
|
membership_required: z.boolean().catch(false),
|
||||||
members_count: z.number().catch(0),
|
members_count: z.number().catch(0),
|
||||||
|
owner: z.object({ id: z.string() }),
|
||||||
note: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''),
|
note: z.string().transform(note => note === '<p></p>' ? '' : note).catch(''),
|
||||||
relationship: groupRelationshipSchema.nullable().catch(null), // Dummy field to be overwritten later
|
relationship: groupRelationshipSchema.nullable().catch(null), // Dummy field to be overwritten later
|
||||||
slug: z.string().catch(''), // TruthSocial
|
slug: z.string().catch(''), // TruthSocial
|
||||||
|
|
Loading…
Reference in New Issue