FEATURE: allow category group moderators to pin/unpin topics (#12325)

* FEATURE: allow category group moderators to pin/unpin topics

Category group moderators should be able to pin/unpin any topics within a category where they have appropraite category group moderator permissions.
This commit is contained in:
jbrw 2021-03-09 16:05:11 -05:00 committed by GitHub
parent 4430bc153d
commit ac7773a30d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 122 additions and 48 deletions

View File

@ -47,6 +47,11 @@ export default Controller.extend(ModalFunctionality, {
return I18n.t(name, { categoryLink, until });
},
@discourseComputed("model.details.can_pin_unpin_topic")
canPinGlobally(canPinUnpinTopic) {
return this.currentUser.isElder && canPinUnpinTopic;
},
@discourseComputed("categoryLink")
pinMessage(categoryLink) {
return I18n.t("topic.feature_topic.pin", { categoryLink });

View File

@ -71,39 +71,26 @@
</p>
</div>
</div>
<div class="feature-section">
<div class="desc">
<p>
{{#conditional-loading-spinner size="small" condition=loading}}
{{#if pinnedGloballyCount}}
{{html-safe (i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount)}}
{{else}}
{{html-safe (i18n "topic.feature_topic.not_pinned_globally")}}
{{/if}}
{{/conditional-loading-spinner}}
</p>
<p>
{{i18n "topic.feature_topic.global_pin_note"}}
</p>
{{#if site.isMobileDevice}}
{{#if canPinGlobally}}
<div class="feature-section">
<div class="desc">
<p>
{{i18n "topic.feature_topic.pin_globally"}}
{{#conditional-loading-spinner size="small" condition=loading}}
{{#if pinnedGloballyCount}}
{{html-safe (i18n "topic.feature_topic.already_pinned_globally" count=pinnedGloballyCount)}}
{{else}}
{{html-safe (i18n "topic.feature_topic.not_pinned_globally")}}
{{/if}}
{{/conditional-loading-spinner}}
</p>
<p class="with-validation">
{{future-date-input
class="pin-until"
includeFarFuture=true
clearable=true
input=model.pinnedGloballyUntil
onChangeInput=(action (mut model.pinnedGloballyUntil))
}}
{{popup-input-tip validation=pinGloballyValidation shownAt=pinGloballyTipShownAt}}
<p>
{{i18n "topic.feature_topic.global_pin_note"}}
</p>
{{else}}
<p class="with-validation">
{{i18n "topic.feature_topic.pin_globally"}}
<span>
{{d-icon "far-clock"}}
{{#if site.isMobileDevice}}
<p>
{{i18n "topic.feature_topic.pin_globally"}}
</p>
<p class="with-validation">
{{future-date-input
class="pin-until"
includeFarFuture=true
@ -112,14 +99,29 @@
onChangeInput=(action (mut model.pinnedGloballyUntil))
}}
{{popup-input-tip validation=pinGloballyValidation shownAt=pinGloballyTipShownAt}}
</span>
</p>
{{else}}
<p class="with-validation">
{{i18n "topic.feature_topic.pin_globally"}}
<span>
{{d-icon "far-clock"}}
{{future-date-input
class="pin-until"
includeFarFuture=true
clearable=true
input=model.pinnedGloballyUntil
onChangeInput=(action (mut model.pinnedGloballyUntil))
}}
{{popup-input-tip validation=pinGloballyValidation shownAt=pinGloballyTipShownAt}}
</span>
</p>
{{/if}}
<p>
{{d-button action=(action "pinGlobally") icon="thumbtack" label="topic.feature.pin_globally" class="btn-primary"}}
</p>
{{/if}}
<p>
{{d-button action=(action "pinGlobally") icon="thumbtack" label="topic.feature.pin_globally" class="btn-primary"}}
</p>
</div>
</div>
</div>
{{/if}}
{{/if}}
{{#if currentUser.staff}}
<div class="feature-section">

View File

@ -206,17 +206,23 @@ export default createWidget("topic-admin-menu", {
icon: "far-clock",
label: "actions.timed_update",
});
}
if (!isPrivateMessage && (topic.get("visible") || featured)) {
this.addActionButton({
className: "topic-admin-pin",
buttonClass: "popup-menu-btn",
action: "showFeatureTopic",
icon: "thumbtack",
label: featured ? "actions.unpin" : "actions.pin",
});
}
if (
details.get("can_pin_unpin_topic") &&
!isPrivateMessage &&
(topic.get("visible") || featured)
) {
this.addActionButton({
className: "topic-admin-pin",
buttonClass: "popup-menu-btn",
action: "showFeatureTopic",
icon: "thumbtack",
label: featured ? "actions.unpin" : "actions.pin",
});
}
if (this.get("currentUser.canManageTopic")) {
if (this.currentUser.get("staff")) {
this.addActionButton({
className: "topic-admin-change-timestamp",

View File

@ -459,3 +459,45 @@ acceptance("Topic with title decorated", function (needs) {
);
});
});
acceptance("Topic pinning/unpinning as an admin", function (needs) {
needs.user({ admin: true });
test("Admin pinning topic", async function (assert) {
await visit("/t/topic-for-group-moderators/2480");
await click(".toggle-admin-menu");
await click(".topic-admin-pin .btn");
assert.ok(
exists(".feature-topic .btn-primary"),
"it should show the 'Pin Topic' button"
);
assert.ok(
exists(".make-banner"),
"it should show the 'Banner Topic' button"
);
});
});
acceptance("Topic pinning/unpinning as a group moderator", function (needs) {
needs.user({ moderator: false, admin: false, trust_level: 1 });
test("Group category moderator pinning topic", async function (assert) {
await visit("/t/topic-for-group-moderators/2480");
await click(".toggle-admin-menu");
await click(".topic-admin-pin .btn");
assert.ok(
exists(".feature-topic .btn-primary"),
"it should show the 'Pin Topic' button"
);
assert.ok(
!exists(".make-banner"),
"it should not show the 'Banner Topic' button"
);
});
});

View File

@ -2206,6 +2206,7 @@ export default {
can_publish_page: true,
can_invite_via_email: true,
can_toggle_topic_visibility: true,
can_pin_unpin_topic: true,
auto_close_at: null,
auto_close_hours: null,
auto_close_based_on_last_post: false,
@ -5597,6 +5598,7 @@ export default {
can_toggle_topic_visibility: true,
can_split_merge_topic: true,
can_edit_staff_notes: true,
can_pin_unpin_topic: true,
can_moderate_category: true,
participants: [
{

View File

@ -429,6 +429,8 @@ class TopicsController < ApplicationController
guardian.ensure_can_archive_topic!(@topic)
when 'visible'
guardian.ensure_can_toggle_topic_visibility!(@topic)
when 'pinned'
guardian.ensure_can_pin_unpin_topic!(@topic)
else
guardian.ensure_can_moderate!(@topic)
end

View File

@ -21,6 +21,7 @@ class TopicViewDetailsSerializer < ApplicationSerializer
:can_split_merge_topic,
:can_edit_staff_notes,
:can_toggle_topic_visibility,
:can_pin_unpin_topic,
:can_moderate_category]
end
@ -149,6 +150,10 @@ class TopicViewDetailsSerializer < ApplicationSerializer
scope.can_toggle_topic_visibility?(object.topic)
end
def include_can_pin_unpin_topic?
scope.can_pin_unpin_topic?(object.topic)
end
def can_perform_action_available_to_group_moderators?
@can_perform_action_available_to_group_moderators ||= scope.can_perform_action_available_to_group_moderators?(object.topic)
end

View File

@ -239,6 +239,7 @@ module TopicGuardian
alias :can_open_topic? :can_perform_action_available_to_group_moderators?
alias :can_split_merge_topic? :can_perform_action_available_to_group_moderators?
alias :can_edit_staff_notes? :can_perform_action_available_to_group_moderators?
alias :can_pin_unpin_topic? :can_perform_action_available_to_group_moderators?
def can_move_posts?(topic)
return false if is_silenced?

View File

@ -940,12 +940,21 @@ RSpec.describe TopicsController do
expect(topic.posts.last.action_code).to eq('archived.disabled')
end
it 'should not allow a group moderator to pin a topic' do
it 'should allow a group moderator to pin a topic' do
put "/t/#{topic.id}/status.json", params: {
status: 'pinned', enabled: 'true'
status: 'pinned', enabled: 'true', until: 2.weeks.from_now
}
expect(response.status).to eq(403)
expect(response.status).to eq(200)
expect(topic.reload.pinned_at).to_not eq(nil)
end
it 'should allow a group moderator to unpin a topic' do
put "/t/#{topic.id}/status.json", params: {
status: 'pinned', enabled: 'false'
}
expect(response.status).to eq(200)
expect(topic.reload.pinned_at).to eq(nil)
end