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:
parent
4430bc153d
commit
ac7773a30d
|
@ -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 });
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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: [
|
||||
{
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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?
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
Loading…
Reference in New Issue