Skip to content

Commit 9546948

Browse files
committed
Add admin UI for announcements
1 parent f6a2c16 commit 9546948

24 files changed

Lines changed: 318 additions & 48 deletions
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
# frozen_string_literal: true
2+
3+
class Admin::AnnouncementsController < Admin::BaseController
4+
before_action :set_announcements, only: :index
5+
before_action :set_announcement, except: [:index, :new, :create]
6+
7+
def index
8+
authorize :announcement, :index?
9+
end
10+
11+
def new
12+
authorize :announcement, :create?
13+
14+
@announcement = Announcement.new
15+
end
16+
17+
def create
18+
authorize :announcement, :create?
19+
20+
@announcement = Announcement.new(resource_params)
21+
22+
if @announcement.save
23+
log_action :create, @announcement
24+
redirect_to admin_announcements_path
25+
else
26+
render :new
27+
end
28+
end
29+
30+
def edit
31+
authorize :announcement, :update?
32+
end
33+
34+
def update
35+
authorize :announcement, :update?
36+
37+
if @announcement.update(resource_params)
38+
log_action :update, @announcement
39+
redirect_to admin_announcements_path
40+
else
41+
render :edit
42+
end
43+
end
44+
45+
def destroy
46+
authorize :announcement, :destroy?
47+
@announcement.destroy!
48+
log_action :destroy, @announcement
49+
redirect_to admin_announcements_path
50+
end
51+
52+
private
53+
54+
def set_announcements
55+
@announcements = AnnouncementFilter.new(filter_params).results.page(params[:page])
56+
end
57+
58+
def set_announcement
59+
@announcement = Announcement.find(params[:id])
60+
end
61+
62+
def filter_params
63+
params.slice(:published, :unpublished).permit(:published, :unpublished)
64+
end
65+
66+
def resource_params
67+
params.require(:announcement).permit(:text, :scheduled_at, :starts_at, :ends_at, :all_day)
68+
end
69+
end

app/controllers/api/v1/announcements_controller.rb

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,7 @@ def dismiss
1818
private
1919

2020
def set_announcements
21-
@announcements = Announcement.published
22-
@announcements = @announcements.without_muted(current_account) if user_signed_in?
23-
@announcements = @announcements.order(starts_at: :asc)
21+
@announcements = Announcement.published.without_muted(current_account).chronological
2422
end
2523

2624
def set_announcement

app/helpers/admin/action_logs_helper.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ def relevant_log_changes(log)
2222
log.recorded_changes.slice('severity', 'reject_media')
2323
elsif log.target_type == 'Status' && log.action == :update
2424
log.recorded_changes.slice('sensitive')
25+
elsif log.target_type == 'Announcement' && log.action == :update
26+
log.recorded_changes.slice('text', 'starts_at', 'ends_at', 'all_day')
2527
end
2628
end
2729

@@ -52,6 +54,8 @@ def icon_for_log(log)
5254
'pencil'
5355
when 'AccountWarning'
5456
'warning'
57+
when 'Announcement'
58+
'bullhorn'
5559
end
5660
end
5761

@@ -94,6 +98,8 @@ def linkable_log_target(record)
9498
link_to record.account.acct, ActivityPub::TagManager.instance.url_for(record)
9599
when 'AccountWarning'
96100
link_to record.target_account.acct, admin_account_path(record.target_account_id)
101+
when 'Announcement'
102+
link_to "##{record.id}", edit_admin_announcement_path(record.id)
97103
end
98104
end
99105

@@ -111,6 +117,8 @@ def log_target_from_history(type, attributes)
111117
else
112118
I18n.t('admin.action_logs.deleted_status')
113119
end
120+
when 'Announcement'
121+
"##{attributes['id']}"
114122
end
115123
end
116124
end
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# frozen_string_literal: true
2+
3+
module Admin::AnnouncementsHelper
4+
def time_range(announcement)
5+
if announcement.all_day?
6+
safe_join([l(announcement.starts_at.to_date), ' - ', l(announcement.ends_at.to_date)])
7+
else
8+
safe_join([l(announcement.starts_at), ' - ', l(announcement.ends_at)])
9+
end
10+
end
11+
end

app/helpers/admin/filter_helper.rb

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
# frozen_string_literal: true
22

33
module Admin::FilterHelper
4-
ACCOUNT_FILTERS = %i(local remote by_domain active pending silenced suspended username display_name email ip staff).freeze
5-
REPORT_FILTERS = %i(resolved account_id target_account_id by_target_domain).freeze
6-
INVITE_FILTER = %i(available expired).freeze
7-
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
8-
TAGS_FILTERS = %i(directory reviewed unreviewed pending_review popular active name).freeze
9-
INSTANCES_FILTERS = %i(limited by_domain).freeze
10-
FOLLOWERS_FILTERS = %i(relationship status by_domain activity order).freeze
11-
12-
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS + FOLLOWERS_FILTERS
4+
ACCOUNT_FILTERS = %i(local remote by_domain active pending silenced suspended username display_name email ip staff).freeze
5+
REPORT_FILTERS = %i(resolved account_id target_account_id by_target_domain).freeze
6+
INVITE_FILTER = %i(available expired).freeze
7+
CUSTOM_EMOJI_FILTERS = %i(local remote by_domain shortcode).freeze
8+
TAGS_FILTERS = %i(directory reviewed unreviewed pending_review popular active name).freeze
9+
INSTANCES_FILTERS = %i(limited by_domain).freeze
10+
FOLLOWERS_FILTERS = %i(relationship status by_domain activity order).freeze
11+
ANNOUNCEMENTS_FILTERS = %i(published unpublished).freeze
12+
13+
FILTERS = ACCOUNT_FILTERS + REPORT_FILTERS + INVITE_FILTER + CUSTOM_EMOJI_FILTERS + TAGS_FILTERS + INSTANCES_FILTERS + FOLLOWERS_FILTERS + ANNOUNCEMENTS_FILTERS
1314

1415
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
1516
new_url = filtered_url_for(link_to_params)

app/javascript/mastodon/actions/announcements.js

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,17 @@ export const ANNOUNCEMENTS_REACTION_REMOVE_FAIL = 'ANNOUNCEMENTS_REACTION_REM
1717

1818
export const ANNOUNCEMENTS_REACTION_UPDATE = 'ANNOUNCEMENTS_REACTION_UPDATE';
1919

20-
export const fetchAnnouncements = () => (dispatch, getState) => {
20+
const noOp = () => {};
21+
22+
export const fetchAnnouncements = (done = noOp) => (dispatch, getState) => {
2123
dispatch(fetchAnnouncementsRequest());
2224

2325
api(getState).get('/api/v1/announcements').then(response => {
2426
dispatch(fetchAnnouncementsSuccess(response.data.map(x => normalizeAnnouncement(x))));
2527
}).catch(error => {
2628
dispatch(fetchAnnouncementsFail(error));
29+
}).finally(() => {
30+
done();
2731
});
2832
};
2933

@@ -42,6 +46,7 @@ export const fetchAnnouncementsFail= error => ({
4246
type: ANNOUNCEMENTS_FETCH_FAIL,
4347
error,
4448
skipLoading: true,
49+
skipAlert: true,
4550
});
4651

4752
export const updateAnnouncements = announcement => ({

app/javascript/mastodon/actions/notifications.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,9 +157,9 @@ export function expandNotifications({ maxId } = {}, done = noOp) {
157157

158158
dispatch(expandNotificationsSuccess(response.data, next ? next.uri : null, isLoadingMore, isLoadingRecent, isLoadingRecent && preferPendingItems));
159159
fetchRelatedRelationships(dispatch, response.data);
160-
done();
161160
}).catch(error => {
162161
dispatch(expandNotificationsFail(error, isLoadingMore));
162+
}).finally(() => {
163163
done();
164164
});
165165
};
@@ -188,6 +188,7 @@ export function expandNotificationsFail(error, isLoadingMore) {
188188
type: NOTIFICATIONS_EXPAND_FAIL,
189189
error,
190190
skipLoading: !isLoadingMore,
191+
skipAlert: !isLoadingMore,
191192
};
192193
};
193194

app/javascript/mastodon/actions/streaming.js

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import {
88
} from './timelines';
99
import { updateNotifications, expandNotifications } from './notifications';
1010
import { updateConversations } from './conversations';
11-
import { updateAnnouncements, updateReaction as updateAnnouncementsReaction } from './announcements';
11+
import { fetchAnnouncements, updateAnnouncements, updateReaction as updateAnnouncementsReaction } from './announcements';
1212
import { fetchFilters } from './filters';
1313
import { getLocale } from '../locales';
1414

@@ -58,7 +58,9 @@ export function connectTimelineStream (timelineId, path, pollingRefresh = null,
5858
}
5959

6060
const refreshHomeTimelineAndNotification = (dispatch, done) => {
61-
dispatch(expandHomeTimeline({}, () => dispatch(expandNotifications({}, done))));
61+
dispatch(expandHomeTimeline({}, () =>
62+
dispatch(expandNotifications({}, () =>
63+
dispatch(fetchAnnouncements(done))))));
6264
};
6365

6466
export const connectUserStream = () => connectTimelineStream('home', 'user', refreshHomeTimelineAndNotification);

app/javascript/mastodon/actions/timelines.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,9 @@ export function expandTimeline(timelineId, path, params = {}, done = noOp) {
9898
const next = getLinks(response).refs.find(link => link.rel === 'next');
9999
dispatch(importFetchedStatuses(response.data));
100100
dispatch(expandTimelineSuccess(timelineId, response.data, next ? next.uri : null, response.status === 206, isLoadingRecent, isLoadingMore, isLoadingRecent && preferPendingItems));
101-
done();
102101
}).catch(error => {
103102
dispatch(expandTimelineFail(timelineId, error, isLoadingMore));
103+
}).finally(() => {
104104
done();
105105
});
106106
};

app/javascript/mastodon/features/getting_started/components/announcements.js

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ class Emoji extends React.PureComponent {
145145

146146
static propTypes = {
147147
emoji: PropTypes.string.isRequired,
148-
emojiMap: PropTypes.object.isRequired,
148+
emojiMap: ImmutablePropTypes.map.isRequired,
149149
hovered: PropTypes.bool.isRequired,
150150
};
151151

@@ -165,14 +165,14 @@ class Emoji extends React.PureComponent {
165165
src={`${assetHost}/emoji/${filename}.svg`}
166166
/>
167167
);
168-
} else if (emojiMap[emoji]) {
169-
const filename = (autoPlayGif || hovered) ? emojiMap[emoji].url : emojiMap[emoji].static_url;
168+
} else if (emojiMap.get(emoji)) {
169+
const filename = (autoPlayGif || hovered) ? emojiMap.getIn([emoji, 'url']) : emojiMap.getIn([emoji, 'static_url']);
170170
const shortCode = `:${emoji}:`;
171171

172172
return (
173173
<img
174174
draggable='false'
175-
class='emojione custom-emoji'
175+
className='emojione custom-emoji'
176176
alt={shortCode}
177177
title={shortCode}
178178
src={filename}
@@ -192,7 +192,7 @@ class Reaction extends ImmutablePureComponent {
192192
reaction: ImmutablePropTypes.map.isRequired,
193193
addReaction: PropTypes.func.isRequired,
194194
removeReaction: PropTypes.func.isRequired,
195-
emojiMap: PropTypes.object.isRequired,
195+
emojiMap: ImmutablePropTypes.map.isRequired,
196196
};
197197

198198
state = {
@@ -239,12 +239,12 @@ class ReactionsBar extends ImmutablePureComponent {
239239
reactions: ImmutablePropTypes.list.isRequired,
240240
addReaction: PropTypes.func.isRequired,
241241
removeReaction: PropTypes.func.isRequired,
242-
emojiMap: PropTypes.object.isRequired,
242+
emojiMap: ImmutablePropTypes.map.isRequired,
243243
};
244244

245245
handleEmojiPick = data => {
246246
const { addReaction, announcementId } = this.props;
247-
addReaction(announcementId, data.native);
247+
addReaction(announcementId, data.native.replace(/:/g, ''));
248248
}
249249

250250
render () {
@@ -275,7 +275,7 @@ class Announcement extends ImmutablePureComponent {
275275

276276
static propTypes = {
277277
announcement: ImmutablePropTypes.map.isRequired,
278-
emojiMap: PropTypes.object.isRequired,
278+
emojiMap: ImmutablePropTypes.map.isRequired,
279279
dismissAnnouncement: PropTypes.func.isRequired,
280280
addReaction: PropTypes.func.isRequired,
281281
removeReaction: PropTypes.func.isRequired,
@@ -297,17 +297,12 @@ class Announcement extends ImmutablePureComponent {
297297
const skipEndDate = hasTimeRange && startsAt.getDate() === endsAt.getDate() && startsAt.getMonth() === endsAt.getMonth() && startsAt.getFullYear() === endsAt.getFullYear();
298298
const skipTime = announcement.get('all_day');
299299

300-
let title;
301-
302-
if (hasTimeRange) {
303-
title = <strong className='announcements__item__range'><FormattedDate value={startsAt} hour12={false} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} hour12={false} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} second={skipTime ? undefined : ''} /></strong>;
304-
} else {
305-
title = <strong className='announcements__item__range'><FormattedMessage id='announcement.generic_update' defaultMessage='Update' /></strong>;
306-
}
307-
308300
return (
309301
<div className='announcements__item'>
310-
{title}
302+
<strong className='announcements__item__range'>
303+
<FormattedMessage id='announcement.generic_update' defaultMessage='Update' />
304+
{hasTimeRange && <span> · <FormattedDate value={startsAt} hour12={false} year={(skipYear || startsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month='short' day='2-digit' hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} /> - <FormattedDate value={endsAt} hour12={false} year={(skipYear || endsAt.getFullYear() === now.getFullYear()) ? undefined : 'numeric'} month={skipEndDate ? undefined : 'short'} day={skipEndDate ? undefined : '2-digit'} hour={skipTime ? undefined : '2-digit'} minute={skipTime ? undefined : '2-digit'} second={skipTime ? undefined : ''} /></span>}
305+
</strong>
311306

312307
<Content announcement={announcement} />
313308

@@ -331,7 +326,7 @@ class Announcements extends ImmutablePureComponent {
331326

332327
static propTypes = {
333328
announcements: ImmutablePropTypes.list,
334-
emojiMap: PropTypes.object,
329+
emojiMap: ImmutablePropTypes.map.isRequired,
335330
fetchAnnouncements: PropTypes.func.isRequired,
336331
dismissAnnouncement: PropTypes.func.isRequired,
337332
addReaction: PropTypes.func.isRequired,

0 commit comments

Comments
 (0)