mirror of
https://github.com/discourse/discourse.git
synced 2024-11-22 22:21:55 +08:00
FEATURE: Implement edit functionality for post notices (#11140)
All post notice related custom fields were moved to a single one.
This commit is contained in:
parent
84e2915e71
commit
ab314218d3
|
@ -1,63 +0,0 @@
|
||||||
import discourseComputed from "discourse-common/utils/decorators";
|
|
||||||
import { isEmpty } from "@ember/utils";
|
|
||||||
import Controller from "@ember/controller";
|
|
||||||
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
|
||||||
import { cookAsync } from "discourse/lib/text";
|
|
||||||
|
|
||||||
export default Controller.extend(ModalFunctionality, {
|
|
||||||
post: null,
|
|
||||||
resolve: null,
|
|
||||||
reject: null,
|
|
||||||
|
|
||||||
notice: null,
|
|
||||||
saving: false,
|
|
||||||
|
|
||||||
@discourseComputed("saving", "notice")
|
|
||||||
disabled(saving, notice) {
|
|
||||||
return saving || isEmpty(notice);
|
|
||||||
},
|
|
||||||
|
|
||||||
onShow() {
|
|
||||||
this.setProperties({
|
|
||||||
notice: "",
|
|
||||||
saving: false,
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
onClose() {
|
|
||||||
const reject = this.reject;
|
|
||||||
if (reject) {
|
|
||||||
reject();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
setNotice() {
|
|
||||||
this.set("saving", true);
|
|
||||||
|
|
||||||
const post = this.post;
|
|
||||||
const resolve = this.resolve;
|
|
||||||
const reject = this.reject;
|
|
||||||
const notice = this.notice;
|
|
||||||
|
|
||||||
// Let `updatePostField` handle state.
|
|
||||||
this.setProperties({ resolve: null, reject: null });
|
|
||||||
|
|
||||||
post
|
|
||||||
.updatePostField("notice", notice)
|
|
||||||
.then(() => cookAsync(notice, { features: { onebox: false } }))
|
|
||||||
.then((cookedNotice) => {
|
|
||||||
post.setProperties({
|
|
||||||
notice_type: "custom",
|
|
||||||
notice_args: cookedNotice.string,
|
|
||||||
});
|
|
||||||
resolve();
|
|
||||||
this.send("closeModal");
|
|
||||||
})
|
|
||||||
.catch(() => {
|
|
||||||
reject();
|
|
||||||
this.send("closeModal");
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
|
@ -0,0 +1,58 @@
|
||||||
|
import Controller from "@ember/controller";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { isEmpty } from "@ember/utils";
|
||||||
|
import discourseComputed from "discourse-common/utils/decorators";
|
||||||
|
import { cookAsync } from "discourse/lib/text";
|
||||||
|
import ModalFunctionality from "discourse/mixins/modal-functionality";
|
||||||
|
|
||||||
|
export default Controller.extend(ModalFunctionality, {
|
||||||
|
post: null,
|
||||||
|
resolve: null,
|
||||||
|
reject: null,
|
||||||
|
|
||||||
|
notice: null,
|
||||||
|
saving: false,
|
||||||
|
|
||||||
|
@discourseComputed("saving", "notice")
|
||||||
|
disabled(saving, notice) {
|
||||||
|
return saving || isEmpty(notice);
|
||||||
|
},
|
||||||
|
|
||||||
|
onShow() {
|
||||||
|
this.setProperties({ notice: "", saving: false });
|
||||||
|
},
|
||||||
|
|
||||||
|
onClose() {
|
||||||
|
if (this.reject) {
|
||||||
|
this.reject();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
@action
|
||||||
|
setNotice(notice) {
|
||||||
|
const { resolve, reject } = this;
|
||||||
|
this.setProperties({ saving: true, resolve: null, reject: null });
|
||||||
|
|
||||||
|
this.model
|
||||||
|
.updatePostField("notice", notice)
|
||||||
|
.then(() => {
|
||||||
|
if (notice) {
|
||||||
|
return cookAsync(notice, { features: { onebox: false } });
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((cooked) =>
|
||||||
|
this.model.set(
|
||||||
|
"notice",
|
||||||
|
cooked
|
||||||
|
? {
|
||||||
|
type: "custom",
|
||||||
|
raw: notice,
|
||||||
|
cooked: cooked.string,
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.then(resolve, reject)
|
||||||
|
.finally(() => this.send("closeModal"));
|
||||||
|
},
|
||||||
|
});
|
|
@ -857,20 +857,15 @@ export default Controller.extend(bufferedProperty("model"), {
|
||||||
this.send("showGrantBadgeModal");
|
this.send("showGrantBadgeModal");
|
||||||
},
|
},
|
||||||
|
|
||||||
addNotice(post) {
|
changeNotice(post) {
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise(function (resolve, reject) {
|
||||||
const modal = showModal("add-post-notice");
|
const modal = showModal("change-post-notice", { model: post });
|
||||||
modal.setProperties({ post, resolve, reject });
|
modal.setProperties({
|
||||||
|
resolve,
|
||||||
|
reject,
|
||||||
|
notice: post.notice ? post.notice.raw : "",
|
||||||
|
});
|
||||||
});
|
});
|
||||||
},
|
|
||||||
|
|
||||||
removeNotice(post) {
|
|
||||||
return post.updatePostField("notice", null).then(() =>
|
|
||||||
post.setProperties({
|
|
||||||
notice_type: null,
|
|
||||||
notice_args: null,
|
|
||||||
})
|
|
||||||
);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleParticipant(user) {
|
toggleParticipant(user) {
|
||||||
|
|
|
@ -140,12 +140,10 @@ export default function transformPost(
|
||||||
postAtts.topicUrl = topic.get("url");
|
postAtts.topicUrl = topic.get("url");
|
||||||
postAtts.isSaving = post.isSaving;
|
postAtts.isSaving = post.isSaving;
|
||||||
|
|
||||||
if (post.notice_type) {
|
if (post.notice) {
|
||||||
postAtts.noticeType = post.notice_type;
|
postAtts.notice = post.notice;
|
||||||
if (postAtts.noticeType === "custom") {
|
if (postAtts.notice.type === "returning_user") {
|
||||||
postAtts.noticeMessage = post.notice_args;
|
postAtts.notice.lastPostedAt = new Date(post.notice.last_posted_at);
|
||||||
} else if (postAtts.noticeType === "returning_user") {
|
|
||||||
postAtts.noticeTime = new Date(post.notice_args);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
{{#d-modal-body title="post.controls.add_post_notice"}}
|
|
||||||
<form>{{textarea value=notice}}</form>
|
|
||||||
{{/d-modal-body}}
|
|
||||||
|
|
||||||
<div class="modal-footer">
|
|
||||||
{{d-button
|
|
||||||
class="btn-primary"
|
|
||||||
action=(action "setNotice")
|
|
||||||
disabled=disabled
|
|
||||||
label=(if saving "saving" "save")}}
|
|
||||||
{{d-modal-cancel close=(route-action "closeModal")}}
|
|
||||||
</div>
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
{{#d-modal-body title=(if model.notice "post.controls.change_post_notice" "post.controls.add_post_notice")}}
|
||||||
|
<form>{{textarea value=notice}}</form>
|
||||||
|
{{/d-modal-body}}
|
||||||
|
|
||||||
|
<div class="modal-footer">
|
||||||
|
{{d-button class="btn-primary" label=(if saving "saving" "save") action=(action "setNotice" notice) disabled=disabled}}
|
||||||
|
{{#if model.notice}}
|
||||||
|
{{d-button class="btn-danger" label="post.controls.delete_post_notice" action=(action "setNotice") disabled=saving}}
|
||||||
|
{{/if}}
|
||||||
|
{{d-modal-cancel close=(route-action "closeModal")}}
|
||||||
|
</div>
|
|
@ -215,8 +215,7 @@
|
||||||
rebakePost=(action "rebakePost")
|
rebakePost=(action "rebakePost")
|
||||||
changePostOwner=(action "changePostOwner")
|
changePostOwner=(action "changePostOwner")
|
||||||
grantBadge=(action "grantBadge")
|
grantBadge=(action "grantBadge")
|
||||||
addNotice=(action "addNotice")
|
changeNotice=(action "changeNotice")
|
||||||
removeNotice=(action "removeNotice")
|
|
||||||
lockPost=(action "lockPost")
|
lockPost=(action "lockPost")
|
||||||
unlockPost=(action "unlockPost")
|
unlockPost=(action "unlockPost")
|
||||||
unhidePost=(action "unhidePost")
|
unhidePost=(action "unhidePost")
|
||||||
|
|
|
@ -53,21 +53,16 @@ export function buildManageButtons(attrs, currentUser, siteSettings) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs.canEditStaffNotes) {
|
if (attrs.canEditStaffNotes) {
|
||||||
if (attrs.noticeType) {
|
|
||||||
contents.push({
|
contents.push({
|
||||||
icon: "user-shield",
|
icon: "user-shield",
|
||||||
label: "post.controls.remove_post_notice",
|
label: attrs.notice
|
||||||
action: "removeNotice",
|
? "post.controls.change_post_notice"
|
||||||
className: "popup-menu-button remove-notice",
|
: "post.controls.add_post_notice",
|
||||||
|
action: "changeNotice",
|
||||||
|
className: attrs.notice
|
||||||
|
? "popup-menu-button change-notice"
|
||||||
|
: "popup-menu-button add-notice",
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
contents.push({
|
|
||||||
icon: "user-shield",
|
|
||||||
label: "post.controls.add_post_notice",
|
|
||||||
action: "addNotice",
|
|
||||||
className: "popup-menu-button add-notice",
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs.canManage && attrs.hidden) {
|
if (attrs.canManage && attrs.hidden) {
|
||||||
|
|
|
@ -471,7 +471,7 @@ createWidget("post-notice", {
|
||||||
tagName: "div.post-notice",
|
tagName: "div.post-notice",
|
||||||
|
|
||||||
buildClasses(attrs) {
|
buildClasses(attrs) {
|
||||||
const classes = [attrs.noticeType.replace(/_/g, "-")];
|
const classes = [attrs.notice.type.replace(/_/g, "-")];
|
||||||
|
|
||||||
if (
|
if (
|
||||||
new Date() - new Date(attrs.created_at) >
|
new Date() - new Date(attrs.created_at) >
|
||||||
|
@ -484,30 +484,33 @@ createWidget("post-notice", {
|
||||||
},
|
},
|
||||||
|
|
||||||
html(attrs) {
|
html(attrs) {
|
||||||
|
if (attrs.notice.type === "custom") {
|
||||||
|
return [
|
||||||
|
iconNode("user-shield"),
|
||||||
|
new RawHtml({ html: `<div>${attrs.notice.cooked}</div>` }),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
const user =
|
const user =
|
||||||
this.siteSettings.display_name_on_posts && prioritizeNameInUx(attrs.name)
|
this.siteSettings.display_name_on_posts && prioritizeNameInUx(attrs.name)
|
||||||
? attrs.name
|
? attrs.name
|
||||||
: attrs.username;
|
: attrs.username;
|
||||||
let text, icon;
|
|
||||||
if (attrs.noticeType === "custom") {
|
if (attrs.notice.type === "new_user") {
|
||||||
icon = "user-shield";
|
return [
|
||||||
text = new RawHtml({ html: `<div>${attrs.noticeMessage}</div>` });
|
iconNode("hands-helping"),
|
||||||
} else if (attrs.noticeType === "new_user") {
|
h("p", I18n.t("post.notice.new_user", { user })),
|
||||||
icon = "hands-helping";
|
];
|
||||||
text = h("p", I18n.t("post.notice.new_user", { user }));
|
|
||||||
} else if (attrs.noticeType === "returning_user") {
|
|
||||||
icon = "far-smile";
|
|
||||||
const distance = (new Date() - new Date(attrs.noticeTime)) / 1000;
|
|
||||||
text = h(
|
|
||||||
"p",
|
|
||||||
I18n.t("post.notice.returning_user", {
|
|
||||||
user,
|
|
||||||
time: relativeAgeMediumSpan(distance, true),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return [iconNode(icon), text];
|
if (attrs.notice.type === "returning_user") {
|
||||||
|
const timeAgo = (new Date() - new Date(attrs.notice.lastPostedAt)) / 1000;
|
||||||
|
const time = relativeAgeMediumSpan(timeAgo, true);
|
||||||
|
return [
|
||||||
|
iconNode("far-smile"),
|
||||||
|
h("p", I18n.t("post.notice.returning_user", { user, time })),
|
||||||
|
];
|
||||||
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -593,7 +596,7 @@ createWidget("post-article", {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (attrs.noticeType) {
|
if (!attrs.deleted_at && attrs.notice) {
|
||||||
rows.push(h("div.row", [this.attach("post-notice", attrs)]));
|
rows.push(h("div.row", [this.attach("post-notice", attrs)]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5174,7 +5174,7 @@ export default {
|
||||||
edit_reason: null,
|
edit_reason: null,
|
||||||
can_view_edit_history: true,
|
can_view_edit_history: true,
|
||||||
wiki: false,
|
wiki: false,
|
||||||
notice_type: "new-user",
|
notice: { type: "new_user" }
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
stream: [25, 26, 27],
|
stream: [25, 26, 27],
|
||||||
|
|
|
@ -934,11 +934,13 @@ widgetTest("post notice - with username", {
|
||||||
this.siteSettings.prioritize_username_in_ux = true;
|
this.siteSettings.prioritize_username_in_ux = true;
|
||||||
this.siteSettings.old_post_notice_days = 14;
|
this.siteSettings.old_post_notice_days = 14;
|
||||||
this.set("args", {
|
this.set("args", {
|
||||||
noticeType: "returning_user",
|
|
||||||
noticeTime: twoDaysAgo,
|
|
||||||
username: "codinghorror",
|
username: "codinghorror",
|
||||||
name: "Jeff",
|
name: "Jeff",
|
||||||
created_at: new Date(),
|
created_at: new Date(),
|
||||||
|
notice: {
|
||||||
|
type: "returning_user",
|
||||||
|
lastPostedAt: twoDaysAgo,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
test(assert) {
|
test(assert) {
|
||||||
|
@ -959,10 +961,10 @@ widgetTest("post notice - with name", {
|
||||||
this.siteSettings.prioritize_username_in_ux = false;
|
this.siteSettings.prioritize_username_in_ux = false;
|
||||||
this.siteSettings.old_post_notice_days = 14;
|
this.siteSettings.old_post_notice_days = 14;
|
||||||
this.set("args", {
|
this.set("args", {
|
||||||
noticeType: "new_user",
|
|
||||||
username: "codinghorror",
|
username: "codinghorror",
|
||||||
name: "Jeff",
|
name: "Jeff",
|
||||||
created_at: new Date(2019, 0, 1),
|
created_at: new Date(2019, 0, 1),
|
||||||
|
notice: { type: "new_user" },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
test(assert) {
|
test(assert) {
|
||||||
|
|
|
@ -95,3 +95,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.change-post-notice-modal {
|
||||||
|
.modal-body {
|
||||||
|
min-width: 450px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -477,18 +477,25 @@ class PostsController < ApplicationController
|
||||||
post = find_post_from_params
|
post = find_post_from_params
|
||||||
raise Discourse::NotFound unless guardian.can_edit_staff_notes?(post.topic)
|
raise Discourse::NotFound unless guardian.can_edit_staff_notes?(post.topic)
|
||||||
|
|
||||||
previous_notice = post.custom_fields[Post::NOTICE_ARGS]
|
old_notice = post.custom_fields[Post::NOTICE]
|
||||||
|
|
||||||
if params[:notice].present?
|
if params[:notice].present?
|
||||||
post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:custom]
|
post.custom_fields[Post::NOTICE] = {
|
||||||
post.custom_fields[Post::NOTICE_ARGS] = PrettyText.cook(params[:notice], features: { onebox: false })
|
type: Post.notices[:custom],
|
||||||
post.save_custom_fields
|
raw: params[:notice],
|
||||||
|
cooked: PrettyText.cook(params[:notice], features: { onebox: false })
|
||||||
|
}
|
||||||
else
|
else
|
||||||
post.delete_post_notices
|
post.custom_fields.delete(Post::NOTICE)
|
||||||
end
|
end
|
||||||
|
|
||||||
details = { new_raw_value: params[:notice], old_value: previous_notice }
|
post.save_custom_fields
|
||||||
StaffActionLogger.new(current_user).log_post_staff_note(post, details)
|
|
||||||
|
StaffActionLogger.new(current_user).log_post_staff_note(
|
||||||
|
post,
|
||||||
|
old_value: old_notice&.[]("raw"),
|
||||||
|
new_value: params[:notice]
|
||||||
|
)
|
||||||
|
|
||||||
render body: nil
|
render body: nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -67,8 +67,7 @@ class Post < ActiveRecord::Base
|
||||||
DOWNLOADED_IMAGES ||= "downloaded_images"
|
DOWNLOADED_IMAGES ||= "downloaded_images"
|
||||||
MISSING_UPLOADS ||= "missing uploads"
|
MISSING_UPLOADS ||= "missing uploads"
|
||||||
MISSING_UPLOADS_IGNORED ||= "missing uploads ignored"
|
MISSING_UPLOADS_IGNORED ||= "missing uploads ignored"
|
||||||
NOTICE_TYPE ||= "notice_type"
|
NOTICE ||= "notice"
|
||||||
NOTICE_ARGS ||= "notice_args"
|
|
||||||
|
|
||||||
SHORT_POST_CHARS ||= 1200
|
SHORT_POST_CHARS ||= 1200
|
||||||
|
|
||||||
|
@ -79,6 +78,8 @@ class Post < ActiveRecord::Base
|
||||||
register_custom_field_type(MISSING_UPLOADS, :json)
|
register_custom_field_type(MISSING_UPLOADS, :json)
|
||||||
register_custom_field_type(MISSING_UPLOADS_IGNORED, :boolean)
|
register_custom_field_type(MISSING_UPLOADS_IGNORED, :boolean)
|
||||||
|
|
||||||
|
register_custom_field_type(NOTICE, :json)
|
||||||
|
|
||||||
scope :private_posts_for_user, ->(user) {
|
scope :private_posts_for_user, ->(user) {
|
||||||
where("posts.topic_id IN (#{Topic::PRIVATE_MESSAGES_SQL})", user_id: user.id)
|
where("posts.topic_id IN (#{Topic::PRIVATE_MESSAGES_SQL})", user_id: user.id)
|
||||||
}
|
}
|
||||||
|
@ -225,7 +226,7 @@ class Post < ActiveRecord::Base
|
||||||
|
|
||||||
def trash!(trashed_by = nil)
|
def trash!(trashed_by = nil)
|
||||||
self.topic_links.each(&:destroy)
|
self.topic_links.each(&:destroy)
|
||||||
self.delete_post_notices
|
self.save_custom_fields if self.custom_fields.delete(Post::NOTICE)
|
||||||
super(trashed_by)
|
super(trashed_by)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -428,8 +429,7 @@ class Post < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_post_notices
|
def delete_post_notices
|
||||||
self.custom_fields.delete(Post::NOTICE_TYPE)
|
self.custom_fields.delete(Post::NOTICE)
|
||||||
self.custom_fields.delete(Post::NOTICE_ARGS)
|
|
||||||
self.save_custom_fields
|
self.save_custom_fields
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -78,8 +78,7 @@ class PostSerializer < BasicPostSerializer
|
||||||
:is_auto_generated,
|
:is_auto_generated,
|
||||||
:action_code,
|
:action_code,
|
||||||
:action_code_who,
|
:action_code_who,
|
||||||
:notice_type,
|
:notice,
|
||||||
:notice_args,
|
|
||||||
:last_wiki_edit,
|
:last_wiki_edit,
|
||||||
:locked,
|
:locked,
|
||||||
:excerpt,
|
:excerpt,
|
||||||
|
@ -438,12 +437,14 @@ class PostSerializer < BasicPostSerializer
|
||||||
include_action_code? && action_code_who.present?
|
include_action_code? && action_code_who.present?
|
||||||
end
|
end
|
||||||
|
|
||||||
def notice_type
|
def notice
|
||||||
post_custom_fields[Post::NOTICE_TYPE]
|
post_custom_fields[Post::NOTICE]
|
||||||
end
|
end
|
||||||
|
|
||||||
def include_notice_type?
|
def include_notice?
|
||||||
case notice_type
|
return false if notice.blank?
|
||||||
|
|
||||||
|
case notice["type"]
|
||||||
when Post.notices[:custom]
|
when Post.notices[:custom]
|
||||||
return true
|
return true
|
||||||
when Post.notices[:new_user]
|
when Post.notices[:new_user]
|
||||||
|
@ -454,17 +455,7 @@ class PostSerializer < BasicPostSerializer
|
||||||
return false
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
scope.user && scope.user.id && object.user &&
|
scope.user && scope.user.id != object.user_id && scope.user.has_trust_level?(min_trust_level)
|
||||||
scope.user.id != object.user_id &&
|
|
||||||
scope.user.has_trust_level?(min_trust_level)
|
|
||||||
end
|
|
||||||
|
|
||||||
def notice_args
|
|
||||||
post_custom_fields[Post::NOTICE_ARGS]
|
|
||||||
end
|
|
||||||
|
|
||||||
def include_notice_args?
|
|
||||||
notice_args.present? && include_notice_type?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def locked
|
def locked
|
||||||
|
|
|
@ -31,8 +31,7 @@ class WebHookPostSerializer < PostSerializer
|
||||||
primary_group_flair_url
|
primary_group_flair_url
|
||||||
primary_group_flair_bg_color
|
primary_group_flair_bg_color
|
||||||
primary_group_flair_color
|
primary_group_flair_color
|
||||||
notice_args
|
notice
|
||||||
notice_type
|
|
||||||
}.each do |attr|
|
}.each do |attr|
|
||||||
define_method("include_#{attr}?") do
|
define_method("include_#{attr}?") do
|
||||||
false
|
false
|
||||||
|
|
|
@ -168,10 +168,10 @@ class StaffActionLogger
|
||||||
raise Discourse::InvalidParameters.new(:post) unless post && post.is_a?(Post)
|
raise Discourse::InvalidParameters.new(:post) unless post && post.is_a?(Post)
|
||||||
|
|
||||||
args = params(opts).merge(
|
args = params(opts).merge(
|
||||||
action: UserHistory.actions[opts[:new_raw_value].present? ? :post_staff_note_create : :post_staff_note_destroy],
|
action: UserHistory.actions[opts[:new_value].present? ? :post_staff_note_create : :post_staff_note_destroy],
|
||||||
post_id: post.id
|
post_id: post.id
|
||||||
)
|
)
|
||||||
args[:new_value] = opts[:new_raw_value] if opts[:new_raw_value].present?
|
args[:new_value] = opts[:new_value] if opts[:new_value].present?
|
||||||
args[:previous_value] = opts[:old_value] if opts[:old_value].present?
|
args[:previous_value] = opts[:old_value] if opts[:old_value].present?
|
||||||
|
|
||||||
UserHistory.create!(params(opts).merge(args))
|
UserHistory.create!(params(opts).merge(args))
|
||||||
|
|
|
@ -2823,7 +2823,8 @@ en:
|
||||||
delete_topic_error: "An error occurred while deleting this topic"
|
delete_topic_error: "An error occurred while deleting this topic"
|
||||||
delete_topic: "delete topic"
|
delete_topic: "delete topic"
|
||||||
add_post_notice: "Add Staff Notice"
|
add_post_notice: "Add Staff Notice"
|
||||||
remove_post_notice: "Remove Staff Notice"
|
change_post_notice: "Change Staff Notice"
|
||||||
|
delete_post_notice: "Delete Staff Notice"
|
||||||
remove_timer: "remove timer"
|
remove_timer: "remove timer"
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
|
|
52
db/migrate/20201105190351_move_post_notices_to_json.rb
Normal file
52
db/migrate/20201105190351_move_post_notices_to_json.rb
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MovePostNoticesToJson < ActiveRecord::Migration[6.0]
|
||||||
|
def up
|
||||||
|
execute <<~SQL
|
||||||
|
INSERT INTO post_custom_fields(post_id, name, value, created_at, updated_at)
|
||||||
|
SELECT
|
||||||
|
posts.id,
|
||||||
|
'notice',
|
||||||
|
CASE
|
||||||
|
WHEN pcf_type.value = 'custom' THEN json_build_object('type', pcf_type.value, 'raw', pcf_args.value, 'cooked', pcf_args.value)
|
||||||
|
WHEN pcf_type.value = 'new_user' THEN json_build_object('type', pcf_type.value)
|
||||||
|
WHEN pcf_type.value = 'returning_user' THEN json_build_object('type', pcf_type.value, 'last_posted_at', pcf_args.value)
|
||||||
|
END,
|
||||||
|
pcf_type.created_at created_at,
|
||||||
|
pcf_type.updated_at updated_at
|
||||||
|
FROM posts
|
||||||
|
JOIN post_custom_fields pcf_type ON posts.id = pcf_type.post_id AND pcf_type.name = 'notice_type'
|
||||||
|
LEFT JOIN post_custom_fields pcf_args ON posts.id = pcf_args.post_id AND pcf_args.name = 'notice_args'
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute "DELETE FROM post_custom_fields WHERE name = 'notice_type' OR name = 'notice_args'"
|
||||||
|
|
||||||
|
add_index :post_custom_fields, :post_id, unique: true, name: "index_post_custom_fields_on_notice", where: "name = 'notice'"
|
||||||
|
|
||||||
|
remove_index :post_custom_fields, name: "index_post_custom_fields_on_notice_type"
|
||||||
|
remove_index :post_custom_fields, name: "index_post_custom_fields_on_notice_args"
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
execute <<~SQL
|
||||||
|
INSERT INTO post_custom_fields(post_id, name, value, created_at, updated_at)
|
||||||
|
SELECT post_id, 'notice_type', value::json->>'type', created_at, updated_at
|
||||||
|
FROM post_custom_fields
|
||||||
|
WHERE name = 'notice'
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute <<~SQL
|
||||||
|
INSERT INTO post_custom_fields(post_id, name, value, created_at, updated_at)
|
||||||
|
SELECT post_id, 'notice_args', COALESCE(value::json->>'cooked', value::json->>'last_posted_at'), created_at, updated_at
|
||||||
|
FROM post_custom_fields
|
||||||
|
WHERE name = 'notice'
|
||||||
|
SQL
|
||||||
|
|
||||||
|
execute "DELETE FROM post_custom_fields WHERE name = 'notice'"
|
||||||
|
|
||||||
|
add_index :post_custom_fields, :post_id, unique: true, name: "index_post_custom_fields_on_notice_type", where: "name = 'notice_type'"
|
||||||
|
add_index :post_custom_fields, :post_id, unique: true, name: "index_post_custom_fields_on_notice_args", where: "name = 'notice_args'"
|
||||||
|
|
||||||
|
remove_index :index_post_custom_fields_on_notice
|
||||||
|
end
|
||||||
|
end
|
|
@ -616,10 +616,12 @@ class PostCreator
|
||||||
.first
|
.first
|
||||||
|
|
||||||
if !last_post_time
|
if !last_post_time
|
||||||
@post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:new_user]
|
@post.custom_fields[Post::NOTICE] = { type: Post.notices[:new_user] }
|
||||||
elsif SiteSetting.returning_users_days > 0 && last_post_time < SiteSetting.returning_users_days.days.ago
|
elsif SiteSetting.returning_users_days > 0 && last_post_time < SiteSetting.returning_users_days.days.ago
|
||||||
@post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:returning_user]
|
@post.custom_fields[Post::NOTICE] = {
|
||||||
@post.custom_fields[Post::NOTICE_ARGS] = last_post_time.iso8601
|
type: Post.notices[:returning_user],
|
||||||
|
last_posted_at: last_post_time.iso8601
|
||||||
|
}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -35,7 +35,7 @@ class TopicView
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.default_post_custom_fields
|
def self.default_post_custom_fields
|
||||||
@default_post_custom_fields ||= [Post::NOTICE_TYPE, Post::NOTICE_ARGS, "action_code_who"]
|
@default_post_custom_fields ||= [Post::NOTICE, "action_code_who"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.post_custom_fields_allowlisters
|
def self.post_custom_fields_allowlisters
|
||||||
|
|
|
@ -1627,10 +1627,10 @@ describe PostCreator do
|
||||||
|
|
||||||
it "generates post notices for new users" do
|
it "generates post notices for new users" do
|
||||||
post = PostCreator.create!(user, title: "one of my first topics", raw: "one of my first posts")
|
post = PostCreator.create!(user, title: "one of my first topics", raw: "one of my first posts")
|
||||||
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:new_user])
|
expect(post.custom_fields[Post::NOTICE]).to eq("type" => Post.notices[:new_user])
|
||||||
|
|
||||||
post = PostCreator.create!(user, title: "another one of my first topics", raw: "another one of my first posts")
|
post = PostCreator.create!(user, title: "another one of my first topics", raw: "another one of my first posts")
|
||||||
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(nil)
|
expect(post.custom_fields[Post::NOTICE]).to eq(nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "generates post notices for returning users" do
|
it "generates post notices for returning users" do
|
||||||
|
@ -1638,12 +1638,10 @@ describe PostCreator do
|
||||||
old_post = Fabricate(:post, user: user, created_at: 31.days.ago)
|
old_post = Fabricate(:post, user: user, created_at: 31.days.ago)
|
||||||
|
|
||||||
post = PostCreator.create!(user, title: "this is a returning topic", raw: "this is a post")
|
post = PostCreator.create!(user, title: "this is a returning topic", raw: "this is a post")
|
||||||
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:returning_user])
|
expect(post.custom_fields[Post::NOTICE]).to eq("type" => Post.notices[:returning_user], "last_posted_at" => old_post.created_at.iso8601)
|
||||||
expect(post.custom_fields[Post::NOTICE_ARGS]).to eq(old_post.created_at.iso8601)
|
|
||||||
|
|
||||||
post = PostCreator.create!(user, title: "this is another topic", raw: "this is my another post")
|
post = PostCreator.create!(user, title: "this is another topic", raw: "this is my another post")
|
||||||
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(nil)
|
expect(post.custom_fields[Post::NOTICE]).to eq(nil)
|
||||||
expect(post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not generate for non-human, staged or anonymous users" do
|
it "does not generate for non-human, staged or anonymous users" do
|
||||||
|
@ -1652,8 +1650,7 @@ describe PostCreator do
|
||||||
[anonymous, Discourse.system_user, staged].each do |user|
|
[anonymous, Discourse.system_user, staged].each do |user|
|
||||||
expect(user.posts.size).to eq(0)
|
expect(user.posts.size).to eq(0)
|
||||||
post = PostCreator.create!(user, title: "#{user.username}'s first topic", raw: "#{user.name}'s first post")
|
post = PostCreator.create!(user, title: "#{user.username}'s first topic", raw: "#{user.name}'s first post")
|
||||||
expect(post.custom_fields[Post::NOTICE_TYPE]).to eq(nil)
|
expect(post.custom_fields[Post::NOTICE]).to eq(nil)
|
||||||
expect(post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -136,24 +136,18 @@ describe Post do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'a post with notices' do
|
context 'a post with notices' do
|
||||||
let(:post) {
|
let(:post) do
|
||||||
post = Fabricate(:post, post_args)
|
post = Fabricate(:post, post_args)
|
||||||
post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:returning_user]
|
post.upsert_custom_fields(Post::NOTICE => { type: Post.notices[:returning_user], last_posted_at: 1.day.ago })
|
||||||
post.custom_fields[Post::NOTICE_ARGS] = 1.day.ago
|
|
||||||
post.save_custom_fields
|
|
||||||
post
|
post
|
||||||
}
|
|
||||||
|
|
||||||
describe 'recovery' do
|
|
||||||
it 'deletes notices' do
|
|
||||||
expect { post.trash! }
|
|
||||||
.to change { post.custom_fields.length }.from(2).to(0)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it 'will have its notice cleared when post is trashed' do
|
||||||
|
expect { post.trash! }.to change { post.custom_fields }.to({})
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "with_secure_media?" do
|
describe "with_secure_media?" do
|
||||||
|
|
|
@ -463,7 +463,7 @@ describe 'posts' do
|
||||||
edit_reason: { type: :string, nullable: true },
|
edit_reason: { type: :string, nullable: true },
|
||||||
can_view_edit_history: { type: :boolean },
|
can_view_edit_history: { type: :boolean },
|
||||||
wiki: { type: :boolean },
|
wiki: { type: :boolean },
|
||||||
notice_type: { type: :string },
|
notice: { type: :object },
|
||||||
reviewable_id: { type: :string, nullable: true },
|
reviewable_id: { type: :string, nullable: true },
|
||||||
reviewable_score_count: { type: :integer },
|
reviewable_score_count: { type: :integer },
|
||||||
reviewable_score_pending_count: { type: :integer },
|
reviewable_score_pending_count: { type: :integer },
|
||||||
|
|
|
@ -1914,21 +1914,17 @@ describe PostsController do
|
||||||
it 'can create and remove notices as a moderator' do
|
it 'can create and remove notices as a moderator' do
|
||||||
sign_in(moderator)
|
sign_in(moderator)
|
||||||
|
|
||||||
put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello *world*!\n\nhttps://github.com/discourse/discourse" }
|
raw_notice = "Hello *world*!\n\nhttps://github.com/discourse/discourse"
|
||||||
|
put "/posts/#{public_post.id}/notice.json", params: { notice: raw_notice }
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
public_post.reload
|
expect(public_post.reload.custom_fields[Post::NOTICE]).to eq("type" => Post.notices[:custom], "raw" => raw_notice, "cooked" => PrettyText.cook(raw_notice, features: { onebox: false }))
|
||||||
expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:custom])
|
|
||||||
expect(public_post.custom_fields[Post::NOTICE_ARGS]).to include('<p>Hello <em>world</em>!</p>')
|
|
||||||
expect(public_post.custom_fields[Post::NOTICE_ARGS]).not_to include('onebox')
|
|
||||||
expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_create]).count).to eq(1)
|
expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_create]).count).to eq(1)
|
||||||
|
|
||||||
put "/posts/#{public_post.id}/notice.json", params: { notice: nil }
|
put "/posts/#{public_post.id}/notice.json", params: { notice: nil }
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
public_post.reload
|
expect(public_post.reload.custom_fields[Post::NOTICE]).to eq(nil)
|
||||||
expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(nil)
|
|
||||||
expect(public_post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
|
|
||||||
expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_destroy]).count).to eq(1)
|
expect(UserHistory.where(action: UserHistory.actions[:post_staff_note_destroy]).count).to eq(1)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1945,20 +1941,16 @@ describe PostsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'can create and remove notices as a group moderator' do
|
it 'can create and remove notices as a group moderator' do
|
||||||
put "/posts/#{public_post.id}/notice.json", params: { notice: "Hello *world*!\n\nhttps://github.com/discourse/discourse" }
|
raw_notice = "Hello *world*!\n\nhttps://github.com/discourse/discourse"
|
||||||
|
put "/posts/#{public_post.id}/notice.json", params: { notice: raw_notice }
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
public_post.reload
|
expect(public_post.reload.custom_fields[Post::NOTICE]).to eq("type" => Post.notices[:custom], "raw" => raw_notice, "cooked" => PrettyText.cook(raw_notice, features: { onebox: false }))
|
||||||
expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(Post.notices[:custom])
|
|
||||||
expect(public_post.custom_fields[Post::NOTICE_ARGS]).to include('<p>Hello <em>world</em>!</p>')
|
|
||||||
expect(public_post.custom_fields[Post::NOTICE_ARGS]).not_to include('onebox')
|
|
||||||
|
|
||||||
put "/posts/#{public_post.id}/notice.json", params: { notice: nil }
|
put "/posts/#{public_post.id}/notice.json", params: { notice: nil }
|
||||||
|
|
||||||
expect(response.status).to eq(200)
|
expect(response.status).to eq(200)
|
||||||
public_post.reload
|
expect(public_post.reload.custom_fields[Post::NOTICE]).to eq(nil)
|
||||||
expect(public_post.custom_fields[Post::NOTICE_TYPE]).to eq(nil)
|
|
||||||
expect(public_post.custom_fields[Post::NOTICE_ARGS]).to eq(nil)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'prevents a group moderator from altering notes outside of their category' do
|
it 'prevents a group moderator from altering notes outside of their category' do
|
||||||
|
|
|
@ -201,8 +201,7 @@ describe PostSerializer do
|
||||||
|
|
||||||
let(:post) {
|
let(:post) {
|
||||||
post = Fabricate(:post, user: user)
|
post = Fabricate(:post, user: user)
|
||||||
post.custom_fields[Post::NOTICE_TYPE] = Post.notices[:returning_user]
|
post.custom_fields[Post::NOTICE] = { type: Post.notices[:returning_user], last_posted_at: 1.day.ago }
|
||||||
post.custom_fields[Post::NOTICE_ARGS] = 1.day.ago
|
|
||||||
post.save_custom_fields
|
post.save_custom_fields
|
||||||
post
|
post
|
||||||
}
|
}
|
||||||
|
@ -212,16 +211,16 @@ describe PostSerializer do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "is visible for TL2+ users (except poster)" do
|
it "is visible for TL2+ users (except poster)" do
|
||||||
expect(json_for_user(nil)[:notice_type]).to eq(nil)
|
expect(json_for_user(nil)[:notice]).to eq(nil)
|
||||||
expect(json_for_user(user)[:notice_type]).to eq(nil)
|
expect(json_for_user(user)[:notice]).to eq(nil)
|
||||||
|
|
||||||
SiteSetting.returning_user_notice_tl = 2
|
SiteSetting.returning_user_notice_tl = 2
|
||||||
expect(json_for_user(user_tl1)[:notice_type]).to eq(nil)
|
expect(json_for_user(user_tl1)[:notice]).to eq(nil)
|
||||||
expect(json_for_user(user_tl2)[:notice_type]).to eq(Post.notices[:returning_user])
|
expect(json_for_user(user_tl2)[:notice][:type]).to eq(Post.notices[:returning_user])
|
||||||
|
|
||||||
SiteSetting.returning_user_notice_tl = 1
|
SiteSetting.returning_user_notice_tl = 1
|
||||||
expect(json_for_user(user_tl1)[:notice_type]).to eq(Post.notices[:returning_user])
|
expect(json_for_user(user_tl1)[:notice][:type]).to eq(Post.notices[:returning_user])
|
||||||
expect(json_for_user(user_tl2)[:notice_type]).to eq(Post.notices[:returning_user])
|
expect(json_for_user(user_tl2)[:notice][:type]).to eq(Post.notices[:returning_user])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -581,13 +581,13 @@ describe StaffActionLogger do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a new UserHistory record" do
|
it "creates a new UserHistory record" do
|
||||||
expect { logger.log_post_staff_note(post, { new_raw_value: 'my note', old_value: nil }) }.to change { UserHistory.count }.by(1)
|
expect { logger.log_post_staff_note(post, { new_value: 'my note', old_value: nil }) }.to change { UserHistory.count }.by(1)
|
||||||
user_history = UserHistory.last
|
user_history = UserHistory.last
|
||||||
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_create])
|
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_create])
|
||||||
expect(user_history.new_value).to eq('my note')
|
expect(user_history.new_value).to eq('my note')
|
||||||
expect(user_history.previous_value).to eq(nil)
|
expect(user_history.previous_value).to eq(nil)
|
||||||
|
|
||||||
expect { logger.log_post_staff_note(post, { new_raw_value: '', old_value: 'my note' }) }.to change { UserHistory.count }.by(1)
|
expect { logger.log_post_staff_note(post, { new_value: '', old_value: 'my note' }) }.to change { UserHistory.count }.by(1)
|
||||||
user_history = UserHistory.last
|
user_history = UserHistory.last
|
||||||
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_destroy])
|
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_destroy])
|
||||||
expect(user_history.new_value).to eq(nil)
|
expect(user_history.new_value).to eq(nil)
|
||||||
|
@ -603,13 +603,13 @@ describe StaffActionLogger do
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates a new UserHistory record" do
|
it "creates a new UserHistory record" do
|
||||||
expect { logger.log_post_staff_note(post, { new_raw_value: 'my note', old_value: nil }) }.to change { UserHistory.count }.by(1)
|
expect { logger.log_post_staff_note(post, { new_value: 'my note', old_value: nil }) }.to change { UserHistory.count }.by(1)
|
||||||
user_history = UserHistory.last
|
user_history = UserHistory.last
|
||||||
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_create])
|
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_create])
|
||||||
expect(user_history.new_value).to eq('my note')
|
expect(user_history.new_value).to eq('my note')
|
||||||
expect(user_history.previous_value).to eq(nil)
|
expect(user_history.previous_value).to eq(nil)
|
||||||
|
|
||||||
expect { logger.log_post_staff_note(post, { new_raw_value: nil, old_value: 'my note' }) }.to change { UserHistory.count }.by(1)
|
expect { logger.log_post_staff_note(post, { new_value: nil, old_value: 'my note' }) }.to change { UserHistory.count }.by(1)
|
||||||
user_history = UserHistory.last
|
user_history = UserHistory.last
|
||||||
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_destroy])
|
expect(user_history.action).to eq(UserHistory.actions[:post_staff_note_destroy])
|
||||||
expect(user_history.new_value).to eq(nil)
|
expect(user_history.new_value).to eq(nil)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user