Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
TheEssem committed Aug 10, 2024
2 parents 7e9b61d + e6feabf commit f54a613
Show file tree
Hide file tree
Showing 80 changed files with 2,476 additions and 585 deletions.
4 changes: 2 additions & 2 deletions app/controllers/api/v1/notifications/policies_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@ class Api::V1::Notifications::PoliciesController < Api::BaseController
before_action :set_policy

def show
render json: @policy, serializer: REST::NotificationPolicySerializer
render json: @policy, serializer: REST::V1::NotificationPolicySerializer
end

def update
@policy.update!(resource_params)
render json: @policy, serializer: REST::NotificationPolicySerializer
render json: @policy, serializer: REST::V1::NotificationPolicySerializer
end

private
Expand Down
38 changes: 38 additions & 0 deletions app/controllers/api/v2/notifications/policies_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# frozen_string_literal: true

class Api::V2::Notifications::PoliciesController < Api::BaseController
before_action -> { doorkeeper_authorize! :read, :'read:notifications' }, only: :show
before_action -> { doorkeeper_authorize! :write, :'write:notifications' }, only: :update

before_action :require_user!
before_action :set_policy

def show
render json: @policy, serializer: REST::NotificationPolicySerializer
end

def update
@policy.update!(resource_params)
render json: @policy, serializer: REST::NotificationPolicySerializer
end

private

def set_policy
@policy = NotificationPolicy.find_or_initialize_by(account: current_account)

with_read_replica do
@policy.summarize!
end
end

def resource_params
params.permit(
:for_not_following,
:for_not_followers,
:for_new_accounts,
:for_private_mentions,
:for_limited_accounts
)
end
end
64 changes: 64 additions & 0 deletions app/javascript/flavours/glitch/actions/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ export const NOTIFICATION_REQUEST_DISMISS_REQUEST = 'NOTIFICATION_REQUEST_DISMIS
export const NOTIFICATION_REQUEST_DISMISS_SUCCESS = 'NOTIFICATION_REQUEST_DISMISS_SUCCESS';
export const NOTIFICATION_REQUEST_DISMISS_FAIL = 'NOTIFICATION_REQUEST_DISMISS_FAIL';

export const NOTIFICATION_REQUESTS_ACCEPT_REQUEST = 'NOTIFICATION_REQUESTS_ACCEPT_REQUEST';
export const NOTIFICATION_REQUESTS_ACCEPT_SUCCESS = 'NOTIFICATION_REQUESTS_ACCEPT_SUCCESS';
export const NOTIFICATION_REQUESTS_ACCEPT_FAIL = 'NOTIFICATION_REQUESTS_ACCEPT_FAIL';

export const NOTIFICATION_REQUESTS_DISMISS_REQUEST = 'NOTIFICATION_REQUESTS_DISMISS_REQUEST';
export const NOTIFICATION_REQUESTS_DISMISS_SUCCESS = 'NOTIFICATION_REQUESTS_DISMISS_SUCCESS';
export const NOTIFICATION_REQUESTS_DISMISS_FAIL = 'NOTIFICATION_REQUESTS_DISMISS_FAIL';

export const NOTIFICATIONS_FOR_REQUEST_FETCH_REQUEST = 'NOTIFICATIONS_FOR_REQUEST_FETCH_REQUEST';
export const NOTIFICATIONS_FOR_REQUEST_FETCH_SUCCESS = 'NOTIFICATIONS_FOR_REQUEST_FETCH_SUCCESS';
export const NOTIFICATIONS_FOR_REQUEST_FETCH_FAIL = 'NOTIFICATIONS_FOR_REQUEST_FETCH_FAIL';
Expand Down Expand Up @@ -585,6 +593,62 @@ export const dismissNotificationRequestFail = (id, error) => ({
error,
});

export const acceptNotificationRequests = (ids) => (dispatch, getState) => {
const count = ids.reduce((count, id) => count + selectNotificationCountForRequest(getState(), id), 0);
dispatch(acceptNotificationRequestsRequest(ids));

api().post(`/api/v1/notifications/requests/accept`, { id: ids }).then(() => {
dispatch(acceptNotificationRequestsSuccess(ids));
dispatch(decreasePendingNotificationsCount(count));
}).catch(err => {
dispatch(acceptNotificationRequestFail(ids, err));
});
};

export const acceptNotificationRequestsRequest = ids => ({
type: NOTIFICATION_REQUESTS_ACCEPT_REQUEST,
ids,
});

export const acceptNotificationRequestsSuccess = ids => ({
type: NOTIFICATION_REQUESTS_ACCEPT_SUCCESS,
ids,
});

export const acceptNotificationRequestsFail = (ids, error) => ({
type: NOTIFICATION_REQUESTS_ACCEPT_FAIL,
ids,
error,
});

export const dismissNotificationRequests = (ids) => (dispatch, getState) => {
const count = ids.reduce((count, id) => count + selectNotificationCountForRequest(getState(), id), 0);
dispatch(acceptNotificationRequestsRequest(ids));

api().post(`/api/v1/notifications/requests/dismiss`, { id: ids }).then(() => {
dispatch(dismissNotificationRequestsSuccess(ids));
dispatch(decreasePendingNotificationsCount(count));
}).catch(err => {
dispatch(dismissNotificationRequestFail(ids, err));
});
};

export const dismissNotificationRequestsRequest = ids => ({
type: NOTIFICATION_REQUESTS_DISMISS_REQUEST,
ids,
});

export const dismissNotificationRequestsSuccess = ids => ({
type: NOTIFICATION_REQUESTS_DISMISS_SUCCESS,
ids,
});

export const dismissNotificationRequestsFail = (ids, error) => ({
type: NOTIFICATION_REQUESTS_DISMISS_FAIL,
ids,
error,
});

export const fetchNotificationsForRequest = accountId => (dispatch, getState) => {
const current = getState().getIn(['notificationRequests', 'current']);
const params = { account_id: accountId };
Expand Down
4 changes: 2 additions & 2 deletions app/javascript/flavours/glitch/api/notification_policies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import { apiRequestGet, apiRequestPut } from 'flavours/glitch/api';
import type { NotificationPolicyJSON } from 'flavours/glitch/api_types/notification_policies';

export const apiGetNotificationPolicy = () =>
apiRequestGet<NotificationPolicyJSON>('/v1/notifications/policy');
apiRequestGet<NotificationPolicyJSON>('/v2/notifications/policy');

export const apiUpdateNotificationsPolicy = (
policy: Partial<NotificationPolicyJSON>,
) => apiRequestPut<NotificationPolicyJSON>('/v1/notifications/policy', policy);
) => apiRequestPut<NotificationPolicyJSON>('/v2/notifications/policy', policy);
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
// See app/serializers/rest/notification_policy_serializer.rb

export type NotificationPolicyValue = 'accept' | 'filter' | 'drop';

export interface NotificationPolicyJSON {
filter_not_following: boolean;
filter_not_followers: boolean;
filter_new_accounts: boolean;
filter_private_mentions: boolean;
for_not_following: NotificationPolicyValue;
for_not_followers: NotificationPolicyValue;
for_new_accounts: NotificationPolicyValue;
for_private_mentions: NotificationPolicyValue;
for_limited_accounts: NotificationPolicyValue;
summary: {
pending_requests_count: number;
pending_notifications_count: number;
Expand Down
13 changes: 11 additions & 2 deletions app/javascript/flavours/glitch/components/check_box.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import classNames from 'classnames';

import CheckIndeterminateSmallIcon from '@/material-icons/400-24px/check_indeterminate_small.svg?react';
import DoneIcon from '@/material-icons/400-24px/done.svg?react';

import { Icon } from './icon';

interface Props {
value: string;
checked: boolean;
indeterminate: boolean;
name: string;
onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
label: React.ReactNode;
Expand All @@ -16,6 +18,7 @@ export const CheckBox: React.FC<Props> = ({
name,
value,
checked,
indeterminate,
onChange,
label,
}) => {
Expand All @@ -29,8 +32,14 @@ export const CheckBox: React.FC<Props> = ({
onChange={onChange}
/>

<span className={classNames('check-box__input', { checked })}>
{checked && <Icon id='check' icon={DoneIcon} />}
<span
className={classNames('check-box__input', { checked, indeterminate })}
>
{indeterminate ? (
<Icon id='indeterminate' icon={CheckIndeterminateSmallIcon} />
) : (
checked && <Icon id='check' icon={DoneIcon} />
)}
</span>

<span>{label}</span>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ const listenerOptions = supportsPassiveEvents
? { passive: true, capture: true }
: true;

interface SelectItem {
export interface SelectItem {
value: string;
icon?: string;
iconComponent?: IconProp;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,21 @@ import { useCallback } from 'react';

import { defineMessages, useIntl } from 'react-intl';

import { Link } from 'react-router-dom';
import classNames from 'classnames';
import { Link, useHistory } from 'react-router-dom';

import { useSelector, useDispatch } from 'react-redux';

import DeleteIcon from '@/material-icons/400-24px/delete.svg?react';
import DoneIcon from '@/material-icons/400-24px/done.svg?react';
import MoreHorizIcon from '@/material-icons/400-24px/more_horiz.svg?react';
import { initBlockModal } from 'flavours/glitch/actions/blocks';
import { initMuteModal } from 'flavours/glitch/actions/mutes';
import { acceptNotificationRequest, dismissNotificationRequest } from 'flavours/glitch/actions/notifications';
import { initReport } from 'flavours/glitch/actions/reports';
import { Avatar } from 'flavours/glitch/components/avatar';
import { CheckBox } from 'flavours/glitch/components/check_box';
import { IconButton } from 'flavours/glitch/components/icon_button';
import DropdownMenuContainer from 'flavours/glitch/containers/dropdown_menu_container';
import { makeGetAccount } from 'flavours/glitch/selectors';
import { toCappedNumber } from 'flavours/glitch/utils/numbers';

Expand All @@ -20,12 +26,18 @@ const getAccount = makeGetAccount();
const messages = defineMessages({
accept: { id: 'notification_requests.accept', defaultMessage: 'Accept' },
dismiss: { id: 'notification_requests.dismiss', defaultMessage: 'Dismiss' },
view: { id: 'notification_requests.view', defaultMessage: 'View notifications' },
mute: { id: 'account.mute', defaultMessage: 'Mute @{name}' },
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
report: { id: 'status.report', defaultMessage: 'Report @{name}' },
more: { id: 'status.more', defaultMessage: 'More' },
});

export const NotificationRequest = ({ id, accountId, notificationsCount }) => {
export const NotificationRequest = ({ id, accountId, notificationsCount, checked, showCheckbox, toggleCheck }) => {
const dispatch = useDispatch();
const account = useSelector(state => getAccount(state, accountId));
const intl = useIntl();
const { push: historyPush } = useHistory();

const handleDismiss = useCallback(() => {
dispatch(dismissNotificationRequest(id));
Expand All @@ -35,9 +47,51 @@ export const NotificationRequest = ({ id, accountId, notificationsCount }) => {
dispatch(acceptNotificationRequest(id));
}, [dispatch, id]);

const handleMute = useCallback(() => {
dispatch(initMuteModal(account));
}, [dispatch, account]);

const handleBlock = useCallback(() => {
dispatch(initBlockModal(account));
}, [dispatch, account]);

const handleReport = useCallback(() => {
dispatch(initReport(account));
}, [dispatch, account]);

const handleView = useCallback(() => {
historyPush(`/notifications/requests/${id}`);
}, [historyPush, id]);

const menu = [
{ text: intl.formatMessage(messages.view), action: handleView },
null,
{ text: intl.formatMessage(messages.accept), action: handleAccept },
null,
{ text: intl.formatMessage(messages.mute, { name: account.username }), action: handleMute, dangerous: true },
{ text: intl.formatMessage(messages.block, { name: account.username }), action: handleBlock, dangerous: true },
{ text: intl.formatMessage(messages.report, { name: account.username }), action: handleReport, dangerous: true },
];

const handleCheck = useCallback(() => {
toggleCheck(id);
}, [toggleCheck, id]);

const handleClick = useCallback((e) => {
if (showCheckbox) {
toggleCheck(id);
e.preventDefault();
e.stopPropagation();
}
}, [toggleCheck, id, showCheckbox]);

return (
<div className='notification-request'>
<Link to={`/notifications/requests/${id}`} className='notification-request__link'>
/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- this is just a minor affordance, but we will need a comprehensive accessibility pass */
<div className={classNames('notification-request', showCheckbox && 'notification-request--forced-checkbox')} onClick={handleClick}>
<div className='notification-request__checkbox' aria-hidden={!showCheckbox}>
<CheckBox checked={checked} onChange={handleCheck} />
</div>
<Link to={`/notifications/requests/${id}`} className='notification-request__link' onClick={handleClick} title={account?.acct}>
<Avatar account={account} size={40} counter={toCappedNumber(notificationsCount)} />

<div className='notification-request__name'>
Expand All @@ -51,7 +105,13 @@ export const NotificationRequest = ({ id, accountId, notificationsCount }) => {

<div className='notification-request__actions'>
<IconButton iconComponent={DeleteIcon} onClick={handleDismiss} title={intl.formatMessage(messages.dismiss)} />
<IconButton iconComponent={DoneIcon} onClick={handleAccept} title={intl.formatMessage(messages.accept)} />
<DropdownMenuContainer
items={menu}
icons='ellipsis-h'
iconComponent={MoreHorizIcon}
direction='right'
title={intl.formatMessage(messages.more)}
/>
</div>
</div>
);
Expand All @@ -61,4 +121,7 @@ NotificationRequest.propTypes = {
id: PropTypes.string.isRequired,
accountId: PropTypes.string.isRequired,
notificationsCount: PropTypes.string.isRequired,
checked: PropTypes.bool,
showCheckbox: PropTypes.bool,
toggleCheck: PropTypes.func,
};
Loading

0 comments on commit f54a613

Please sign in to comment.