FEATURE: Modal for profile featured topic & admin wrench refactor (#8545)

This commit is contained in:
Mark VanLandingham 2019-12-16 08:41:34 -08:00 committed by GitHub
parent 7b6bafc651
commit 8c4ffaea1b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 525 additions and 255 deletions

View File

@ -4,6 +4,7 @@ import Component from "@ember/component";
import discourseDebounce from "discourse/lib/debounce";
import { searchForTerm } from "discourse/lib/search";
import { observes } from "discourse-common/utils/decorators";
import discourseComputed from "discourse-common/utils/decorators";
export default Component.extend({
loading: null,
@ -11,7 +12,33 @@ export default Component.extend({
topics: null,
selectedTopicId: null,
currentTopicId: null,
additionalFilters: null,
topicTitle: null,
label: null,
loadOnInit: false,
topicChangedCallback: null,
init() {
this._super(...arguments);
this.additionalFilters = this.additionalFilters || "";
this.topicTitle = this.topicTitle || "";
if (this.loadOnInit && !isEmpty(this.additionalFilters)) {
searchForTerm(this.additionalFilters, {}).then(results => {
if (results && results.posts && results.posts.length > 0) {
this.set(
"topics",
results.posts
.mapBy("topic")
.filter(t => t.id !== this.currentTopicId)
);
} else {
this.setProperties({ topics: null, loading: false });
}
});
}
},
@observes("topicTitle")
topicTitleChanged() {
@ -24,6 +51,11 @@ export default Component.extend({
this.search(this.topicTitle);
},
@discourseComputed("label")
labelText(label) {
return label || "choose_topic.title.search";
},
@observes("topics")
topicsChanged() {
if (this.topics) {
@ -38,18 +70,21 @@ export default Component.extend({
return;
}
const currentTopicId = this.currentTopicId;
if (isEmpty(title)) {
if (isEmpty(title) && isEmpty(this.additionalFilters)) {
this.setProperties({ topics: null, loading: false });
return;
}
searchForTerm(title, {
typeFilter: "topic",
searchForId: true,
restrictToArchetype: "regular"
}).then(results => {
const currentTopicId = this.currentTopicId;
const titleWithFilters = `${title} ${this.additionalFilters}`;
let searchParams = {};
if (!isEmpty(title)) {
searchParams.typeFilter = "topic";
searchParams.restrictToArchetype = "regular";
}
searchForTerm(titleWithFilters, searchParams).then(results => {
if (results && results.posts && results.posts.length > 0) {
this.set(
"topics",
@ -67,7 +102,7 @@ export default Component.extend({
next(() => {
document.getElementById(`choose-topic-${topic.id}`).checked = true;
});
return false;
if (this.topicChangedCallback) this.topicChangedCallback(topic);
}
}
});

View File

@ -20,7 +20,7 @@ const REGEXP_MIN_POST_COUNT_PREFIX = /^min_post_count:/gi;
const REGEXP_POST_TIME_PREFIX = /^(before|after):/gi;
const REGEXP_TAGS_REPLACE = /(^(tags?:|#(?=[a-z0-9\-]+::tag))|::tag\s?$)/gi;
const REGEXP_IN_MATCH = /^(in|with):(posted|watching|tracking|bookmarks|first|pinned|unpinned|wiki|unseen|image)/gi;
const REGEXP_IN_MATCH = /^(in|with):(posted|created|watching|tracking|bookmarks|first|pinned|unpinned|wiki|unseen|image)/gi;
const REGEXP_SPECIAL_IN_LIKES_MATCH = /^in:likes/gi;
const REGEXP_SPECIAL_IN_TITLE_MATCH = /^in:title/gi;
const REGEXP_SPECIAL_IN_PERSONAL_MATCH = /^in:personal/gi;
@ -41,6 +41,7 @@ export default Component.extend({
this.inOptionsForUsers = [
{ name: I18n.t("search.advanced.filters.unseen"), value: "unseen" },
{ name: I18n.t("search.advanced.filters.posted"), value: "posted" },
{ name: I18n.t("search.advanced.filters.created"), value: "created" },
{ name: I18n.t("search.advanced.filters.watching"), value: "watching" },
{ name: I18n.t("search.advanced.filters.tracking"), value: "tracking" },
{ name: I18n.t("search.advanced.filters.bookmarks"), value: "bookmarks" }
@ -57,6 +58,7 @@ export default Component.extend({
this.statusOptions = [
{ name: I18n.t("search.advanced.statuses.open"), value: "open" },
{ name: I18n.t("search.advanced.statuses.closed"), value: "closed" },
{ name: I18n.t("search.advanced.statuses.public"), value: "public" },
{ name: I18n.t("search.advanced.statuses.archived"), value: "archived" },
{
name: I18n.t("search.advanced.statuses.noreplies"),

View File

@ -1,6 +1,5 @@
import discourseComputed from "discourse-common/utils/decorators";
import { alias, or, and } from "@ember/object/computed";
import { propertyEqual } from "discourse/lib/computed";
import Component from "@ember/component";
import { getTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
@ -10,11 +9,6 @@ export default Component.extend({
// Allow us to extend it
layoutName: "components/topic-footer-buttons",
topicFeaturedOnProfile: propertyEqual(
"topic.id",
"currentUser.featured_topic.id"
),
@discourseComputed("topic.isPrivateMessage")
canArchive(isPM) {
return this.siteSettings.enable_personal_messages && isPM;
@ -44,15 +38,6 @@ export default Component.extend({
inviteDisabled: or("topic.archived", "topic.closed", "topic.deleted"),
@discourseComputed
showAdminButton() {
return (
!this.site.mobileView &&
this.currentUser &&
this.currentUser.get("canManageTopic")
);
},
showEditOnFooter: and("topic.isPrivateMessage", "site.can_tag_pms"),
@discourseComputed("topic.message_archived")
@ -64,19 +49,5 @@ export default Component.extend({
@discourseComputed("topic.message_archived")
archiveLabel: archived =>
archived ? "topic.move_to_inbox.title" : "topic.archive_message.title",
@discourseComputed(
"topic.user_id",
"topic.isPrivateMessage",
"topic.category.read_restricted"
)
showToggleFeatureOnProfileButton(userId, isPm, restricted) {
return (
this.siteSettings.allow_featured_topic_on_user_profiles &&
userId === this.currentUser.get("id") &&
!restricted &&
!isPm
);
}
archived ? "topic.move_to_inbox.title" : "topic.archive_message.title"
});

View File

@ -0,0 +1,33 @@
import Controller from "@ember/controller";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { none } from "@ember/object/computed";
export default Controller.extend(ModalFunctionality, {
newFeaturedTopic: null,
saving: false,
noTopicSelected: none("newFeaturedTopic"),
onClose() {
this.set("newFeaturedTopic", null);
},
actions: {
save() {
return ajax(`/u/${this.model.username}/feature-topic`, {
type: "PUT",
data: { topic_id: this.newFeaturedTopic.id }
})
.then(() => {
this.model.set("featured_topic", this.newFeaturedTopic);
this.send("closeModal");
})
.catch(popupAjaxError);
},
newTopicSelected(topic) {
this.set("newFeaturedTopic", topic);
}
}
});

View File

@ -5,6 +5,8 @@ import { default as discourseComputed } from "discourse-common/utils/decorators"
import PreferencesTabController from "discourse/mixins/preferences-tab-controller";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { cookAsync } from "discourse/lib/text";
import { ajax } from "discourse/lib/ajax";
import showModal from "discourse/lib/show-modal";
export default Controller.extend(PreferencesTabController, {
init() {
@ -48,6 +50,30 @@ export default Controller.extend(PreferencesTabController, {
},
actions: {
showFeaturedTopicModal() {
showModal("feature-topic-on-profile", {
model: this.model,
title: "user.feature_topic_on_profile.title"
});
},
clearFeaturedTopicFromProfile() {
bootbox.confirm(
I18n.t("user.feature_topic_on_profile.clear.warning"),
result => {
if (result) {
ajax(`/u/${this.model.username}/clear-featured-topic`, {
type: "PUT"
})
.then(() => {
this.model.set("featured_topic", null);
})
.catch(popupAjaxError);
}
}
);
},
save() {
this.set("saved", false);

View File

@ -166,34 +166,5 @@ export default {
return this.site.mobileView;
}
});
registerTopicFooterButton({
dependentKeys: ["currentUser.featured_topic"],
id: "toggle-feature-on-profile",
icon: "id-card",
priority: 300,
label() {
return this.topicFeaturedOnProfile
? "topic.remove_from_profile.title"
: "topic.feature_on_profile.title";
},
title() {
return this.topicFeaturedOnProfile
? "topic.remove_from_profile.help"
: "topic.feature_on_profile.help";
},
classNames() {
return this.topicFeaturedOnProfile
? ["feature-on-profile", "featured-on-profile"]
: ["feature-on-profile"];
},
action: "toggleFeaturedOnProfile",
displayed() {
return this.showToggleFeatureOnProfileButton;
},
dropdown() {
return this.site.mobileView;
}
});
}
};

View File

@ -1,4 +1,4 @@
<label for='choose-topic-title'>{{i18n 'choose_topic.title.search'}}</label>
<label for='choose-topic-title'>{{i18n labelText}}</label>
{{text-field value=topicTitle placeholderKey="choose_topic.title.placeholder" id="choose-topic-title"}}

View File

@ -1,21 +1,21 @@
<div class='topic-footer-main-buttons'>
{{#if showAdminButton}}
{{topic-admin-menu-button
topic=topic
openUpwards="true"
toggleMultiSelect=toggleMultiSelect
deleteTopic=deleteTopic
recoverTopic=recoverTopic
toggleClosed=toggleClosed
toggleArchived=toggleArchived
toggleVisibility=toggleVisibility
showTopicStatusUpdate=showTopicStatusUpdate
showFeatureTopic=showFeatureTopic
showChangeTimestamp=showChangeTimestamp
resetBumpDate=resetBumpDate
convertToPublicTopic=convertToPublicTopic
convertToPrivateMessage=convertToPrivateMessage}}
{{/if}}
{{topic-admin-menu-button
topic=topic
openUpwards="true"
toggleMultiSelect=toggleMultiSelect
deleteTopic=deleteTopic
recoverTopic=recoverTopic
toggleFeaturedOnProfile=toggleFeaturedOnProfile
toggleClosed=toggleClosed
toggleArchived=toggleArchived
toggleVisibility=toggleVisibility
showTopicStatusUpdate=showTopicStatusUpdate
showFeatureTopic=showFeatureTopic
showChangeTimestamp=showChangeTimestamp
resetBumpDate=resetBumpDate
convertToPublicTopic=convertToPublicTopic
convertToPrivateMessage=convertToPrivateMessage
}}
{{#if site.mobileView}}
{{topic-footer-mobile-dropdown topic=topic content=dropdownButtons}}

View File

@ -144,7 +144,7 @@
<div class="card-row">
<div class="featured-topic">
<span class="desc">{{i18n 'user.featured_topic'}}</span>
{{#link-to "topic" user.featured_topic.slug user.featured_topic.id }}{{user.featured_topic.fancy_title}}{{/link-to}}
{{#link-to "topic" user.featured_topic.slug user.featured_topic.id }}{{{user.featured_topic.fancy_title}}}{{/link-to}}
</div>
</div>
{{/if}}

View File

@ -0,0 +1,13 @@
{{#d-modal-body class="feature-topic-on-profile"}}
{{choose-topic currentTopicId=model.featured_topic.id
selectedTopicId=newFeaturedTopicId
additionalFilters="in:created status:public"
label="user.feature_topic_on_profile.search_label"
topicChangedCallback=(action "newTopicSelected")
loadOnInit=true
}}
{{d-button action=(action "save")
class="btn-primary save-featured-topic-on-profile"
disabled=noTopicSelected
label="user.feature_topic_on_profile.save"}}
{{/d-modal-body}}

View File

@ -57,18 +57,32 @@
</div>
{{/if}}
{{#if model.featured_topic}}
{{#if siteSettings.allow_featured_topic_on_user_profiles}}
<div class="control-group">
<label class="control-label">{{i18n 'user.featured_topic'}}</label>
<label class="control-label">
{{#link-to "topic" model.featured_topic.slug model.featured_topic.id}}{{model.featured_topic.fancy_title}}{{/link-to}}
</label>
{{#if model.featured_topic}}
<label class="featured-topic-link">
{{#link-to "topic" model.featured_topic.slug model.featured_topic.id}}{{{model.featured_topic.fancy_title}}}{{/link-to}}
</label>
{{/if}}
<div>
{{d-button action=(action "showFeaturedTopicModal")
class="btn-primary feature-topic-on-profile-btn"
label="user.feature_topic_on_profile.open_search"}}
{{#if model.featured_topic}}
{{d-button action=(action "clearFeaturedTopicFromProfile")
class="btn-danger clear-feature-topic-on-profile-btn"
label="user.feature_topic_on_profile.clear.title"}}
{{/if}}
</div>
<div class='instructions'>
{{i18n 'user.change_featured_topic.instructions'}}
</div>
</div>
{{/if}}
{{plugin-outlet name="user-preferences-profile" args=(hash model=model save=(action "save"))}}
{{plugin-outlet name="user-custom-preferences" args=(hash model=model)}}

View File

@ -103,6 +103,7 @@
toggleClosed=(action "toggleClosed")
toggleArchived=(action "toggleArchived")
toggleVisibility=(action "toggleVisibility")
toggleFeaturedOnProfile=(action "toggleFeaturedOnProfile")
showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
showFeatureTopic=(route-action "showFeatureTopic")
showChangeTimestamp=(route-action "showChangeTimestamp")
@ -303,6 +304,7 @@
toggleClosed=(action "toggleClosed")
toggleArchived=(action "toggleArchived")
toggleVisibility=(action "toggleVisibility")
toggleFeaturedOnProfile=(action "toggleFeaturedOnProfile")
showTopicStatusUpdate=(route-action "showTopicStatusUpdate")
showFeatureTopic=(route-action "showFeatureTopic")
showChangeTimestamp=(route-action "showChangeTimestamp")
@ -314,8 +316,7 @@
toggleArchiveMessage=(action "toggleArchiveMessage")
editFirstPost=(action "editFirstPost")
deferTopic=(action "deferTopic")
replyToPost=(action "replyToPost")
toggleFeaturedOnProfile=(action "toggleFeaturedOnProfile")}}
replyToPost=(action "replyToPost")}}
{{else}}
<div id="topic-footer-buttons">
{{d-button icon="reply" class="btn-primary pull-right" action=(route-action "showLogin") label="topic.reply.title"}}

View File

@ -32,14 +32,22 @@ createWidget("topic-admin-menu-button", {
},
html(attrs, state) {
if (!this.currentUser || !this.currentUser.get("canManageTopic")) {
return;
}
const result = [];
const menu = this.attach("topic-admin-menu", {
position: state.position,
fixed: attrs.fixed,
topic: attrs.topic,
openUpwards: attrs.openUpwards,
rightSide: attrs.rightSide,
actionButtons: []
});
// We don't show the button when expanded on the right side
if (!(attrs.rightSide && state.expanded)) {
if (
menu.attrs.actionButtons.length &&
!(attrs.rightSide && state.expanded)
) {
result.push(
this.attach("button", {
className:
@ -54,15 +62,7 @@ createWidget("topic-admin-menu-button", {
}
if (state.expanded) {
result.push(
this.attach("topic-admin-menu", {
position: state.position,
fixed: attrs.fixed,
topic: attrs.topic,
openUpwards: attrs.openUpwards,
rightSide: attrs.rightSide
})
);
result.push(menu);
}
return result;
@ -102,6 +102,160 @@ export default createWidget("topic-admin-menu", {
}
},
init(attrs) {
const topic = attrs.topic;
const details = topic.get("details");
const isPrivateMessage = topic.get("isPrivateMessage");
const featured = topic.get("pinned_at") || topic.get("isBanner");
const visible = topic.get("visible");
if (
this.siteSettings.allow_featured_topic_on_user_profiles &&
topic.user_id === this.currentUser.get("id") &&
!topic.isPrivateMessage &&
!topic.category.read_restricted
) {
let topicFeaturedOnProfile =
topic.id === this.currentUser.get("featured_topic.id");
this.addActionButton({
className: "topic-action-feature-on-profile",
buttonClass: topicFeaturedOnProfile ? "btn-primary" : "btn-default",
action: "toggleFeaturedOnProfile",
icon: "id-card",
fullLabel: topicFeaturedOnProfile
? "topic.remove_from_profile.title"
: "topic.feature_on_profile.title"
});
}
// Admin actions
if (this.currentUser && this.currentUser.get("canManageTopic")) {
this.addActionButton({
className: "topic-admin-multi-select",
buttonClass: "btn-default",
action: "toggleMultiSelect",
icon: "tasks",
label: "actions.multi_select"
});
if (details.get("can_delete")) {
this.addActionButton({
className: "topic-admin-delete",
buttonClass: "btn-danger",
action: "deleteTopic",
icon: "far-trash-alt",
label: "actions.delete"
});
}
if (topic.get("deleted") && details.get("can_recover")) {
this.addActionButton({
className: "topic-admin-recover",
buttonClass: "btn-default",
action: "recoverTopic",
icon: "undo",
label: "actions.recover"
});
}
if (topic.get("closed")) {
this.addActionButton({
className: "topic-admin-open",
buttonClass: "btn-default",
action: "toggleClosed",
icon: "unlock",
label: "actions.open"
});
} else {
this.addActionButton({
className: "topic-admin-close",
buttonClass: "btn-default",
action: "toggleClosed",
icon: "lock",
label: "actions.close"
});
}
this.addActionButton({
className: "topic-admin-status-update",
buttonClass: "btn-default",
action: "showTopicStatusUpdate",
icon: "far-clock",
label: "actions.timed_update"
});
if (!isPrivateMessage && (topic.get("visible") || featured)) {
this.addActionButton({
className: "topic-admin-pin",
buttonClass: "btn-default",
action: "showFeatureTopic",
icon: "thumbtack",
label: featured ? "actions.unpin" : "actions.pin"
});
}
if (this.currentUser.get("staff")) {
this.addActionButton({
className: "topic-admin-change-timestamp",
buttonClass: "btn-default",
action: "showChangeTimestamp",
icon: "calendar-alt",
label: "change_timestamp.title"
});
}
this.addActionButton({
className: "topic-admin-reset-bump-date",
buttonClass: "btn-default",
action: "resetBumpDate",
icon: "anchor",
label: "actions.reset_bump_date"
});
if (!isPrivateMessage) {
this.addActionButton({
className: "topic-admin-archive",
buttonClass: "btn-default",
action: "toggleArchived",
icon: "folder",
label: topic.get("archived") ? "actions.unarchive" : "actions.archive"
});
}
this.addActionButton({
className: "topic-admin-visible",
buttonClass: "btn-default",
action: "toggleVisibility",
icon: visible ? "far-eye-slash" : "far-eye",
label: visible ? "actions.invisible" : "actions.visible"
});
if (details.get("can_convert_topic")) {
this.addActionButton({
className: "topic-admin-convert",
buttonClass: "btn-default",
action: isPrivateMessage
? "convertToPublicTopic"
: "convertToPrivateMessage",
icon: isPrivateMessage ? "comment" : "envelope",
label: isPrivateMessage
? "actions.make_public"
: "actions.make_private"
});
}
if (this.currentUser.get("staff")) {
this.addActionButton({
icon: "list",
buttonClass: "btn-default",
fullLabel: "review.moderation_history",
url: `/review?topic_id=${topic.id}&status=all`
});
}
}
},
buildAttributes(attrs) {
let { top, left, outerHeight } = attrs.position;
const position = attrs.fixed ? "fixed" : "absolute";
@ -129,148 +283,22 @@ export default createWidget("topic-admin-menu", {
}
},
addActionButton(button) {
this.attrs.actionButtons.push(button);
},
html(attrs) {
const buttons = [];
buttons.push({
className: "topic-admin-multi-select",
buttonClass: "btn-default",
action: "toggleMultiSelect",
icon: "tasks",
label: "actions.multi_select"
});
const topic = attrs.topic;
const details = topic.get("details");
if (details.get("can_delete")) {
buttons.push({
className: "topic-admin-delete",
buttonClass: "btn-danger",
action: "deleteTopic",
icon: "far-trash-alt",
label: "actions.delete"
});
}
if (topic.get("deleted") && details.get("can_recover")) {
buttons.push({
className: "topic-admin-recover",
buttonClass: "btn-default",
action: "recoverTopic",
icon: "undo",
label: "actions.recover"
});
}
if (topic.get("closed")) {
buttons.push({
className: "topic-admin-open",
buttonClass: "btn-default",
action: "toggleClosed",
icon: "unlock",
label: "actions.open"
});
} else {
buttons.push({
className: "topic-admin-close",
buttonClass: "btn-default",
action: "toggleClosed",
icon: "lock",
label: "actions.close"
});
}
buttons.push({
className: "topic-admin-status-update",
buttonClass: "btn-default",
action: "showTopicStatusUpdate",
icon: "far-clock",
label: "actions.timed_update"
});
const isPrivateMessage = topic.get("isPrivateMessage");
const featured = topic.get("pinned_at") || topic.get("isBanner");
if (!isPrivateMessage && (topic.get("visible") || featured)) {
buttons.push({
className: "topic-admin-pin",
buttonClass: "btn-default",
action: "showFeatureTopic",
icon: "thumbtack",
label: featured ? "actions.unpin" : "actions.pin"
});
}
if (this.currentUser.get("staff")) {
buttons.push({
className: "topic-admin-change-timestamp",
buttonClass: "btn-default",
action: "showChangeTimestamp",
icon: "calendar-alt",
label: "change_timestamp.title"
});
}
buttons.push({
className: "topic-admin-reset-bump-date",
buttonClass: "btn-default",
action: "resetBumpDate",
icon: "anchor",
label: "actions.reset_bump_date"
});
if (!isPrivateMessage) {
buttons.push({
className: "topic-admin-archive",
buttonClass: "btn-default",
action: "toggleArchived",
icon: "folder",
label: topic.get("archived") ? "actions.unarchive" : "actions.archive"
});
}
const visible = topic.get("visible");
buttons.push({
className: "topic-admin-visible",
buttonClass: "btn-default",
action: "toggleVisibility",
icon: visible ? "far-eye-slash" : "far-eye",
label: visible ? "actions.invisible" : "actions.visible"
});
if (details.get("can_convert_topic")) {
buttons.push({
className: "topic-admin-convert",
buttonClass: "btn-default",
action: isPrivateMessage
? "convertToPublicTopic"
: "convertToPrivateMessage",
icon: isPrivateMessage ? "comment" : "envelope",
label: isPrivateMessage ? "actions.make_public" : "actions.make_private"
});
}
if (this.currentUser.get("staff")) {
buttons.push({
icon: "list",
buttonClass: "btn-default",
fullLabel: "review.moderation_history",
url: `/review?topic_id=${topic.id}&status=all`
});
}
const extraButtons = applyDecorators(
this,
"adminMenuButtons",
this.attrs,
this.state
);
return [
h("h3", I18n.t("admin_title")),
h("h3", I18n.t("topic.actions.title")),
h(
"ul",
buttons
attrs.actionButtons
.concat(extraButtons)
.filter(Boolean)
.map(b => this.attach("admin-menu-button", b))

View File

@ -321,7 +321,7 @@ createWidget("timeline-controls", {
const controls = [];
const { fullScreen, currentUser, topic } = attrs;
if (!fullScreen && currentUser && currentUser.get("canManageTopic")) {
if (!fullScreen && currentUser) {
controls.push(this.attach("topic-admin-menu-button", { topic }));
}

View File

@ -694,6 +694,10 @@
padding: 8px;
}
}
.featured-topic-link {
padding: 5px 0;
font-weight: bold;
}
}
.paginated-topics-list {

View File

@ -5,10 +5,5 @@
color: $tertiary;
}
}
&.featured-on-profile {
.d-icon {
color: $tertiary;
}
}
}
}

View File

@ -187,7 +187,7 @@ en:
enabled: "made this a banner %{when}. It will appear at the top of every page until it is dismissed by the user."
disabled: "removed this banner %{when}. It will no longer appear at the top of every page."
topic_admin_menu: "topic admin actions"
topic_admin_menu: "topic actions"
wizard_required: "Welcome to your new Discourse! Lets get started with <a href='%{url}' data-auto-route='true'>the setup wizard</a> ✨"
emails_are_disabled: "All outgoing email has been globally disabled by an administrator. No email notifications of any kind will be sent."
@ -831,6 +831,14 @@ en:
normal_option_title: "You will be notified if this user replies to you, quotes you, or mentions you."
activity_stream: "Activity"
preferences: "Preferences"
feature_topic_on_profile:
open_search: "Select a New Topic"
title: "Select a Topic"
search_label: "Search for Topic by title"
save: "Save"
clear:
title: "Clear"
warning: "Are you sure you want to clear your featured topic?"
profile_hidden: "This user's public profile is hidden."
expand_profile: "Expand"
collapse_profile: "Collapse"
@ -1063,7 +1071,7 @@ en:
change_featured_topic:
title: "Featured Topic"
instructions: "To change this, either navigate to the topic to remove it as the featured topic, or feature a different topic."
instructions: "A link to this topic will be on your user card, and profile."
email:
title: "Email"
@ -1910,6 +1918,7 @@ en:
title: Matching in title only
likes: I liked
posted: I posted in
created: I created
watching: I'm watching
tracking: I'm tracking
private: In my messages
@ -1926,6 +1935,7 @@ en:
label: Where topics
open: are open
closed: are closed
public: are public
archived: are archived
noreplies: have zero replies
single_user: contain a single user
@ -2217,6 +2227,7 @@ en:
description: "You will never be notified of anything about this topic, and it will not appear in latest."
actions:
title: "Actions"
recover: "Un-Delete Topic"
delete: "Delete Topic"
open: "Open Topic"

View File

@ -303,6 +303,14 @@ class Search
posts.where('topics.closed')
end
advanced_filter(/^status:public$/) do |posts|
category_ids = Category
.where(read_restricted: false)
.pluck(:id)
posts.where("topics.category_id in (?)", category_ids)
end
advanced_filter(/^status:archived$/) do |posts|
posts.where('topics.archived')
end
@ -370,6 +378,10 @@ class Search
posts.where("posts.user_id = #{@guardian.user.id}") if @guardian.user
end
advanced_filter(/^in:created$/) do |posts|
posts.where(user_id: @guardian.user.id, post_number: 1) if @guardian.user
end
advanced_filter(/^in:(watching|tracking)$/) do |posts, match|
if @guardian.user
level = TopicUser.notification_levels[match.to_sym]

View File

@ -1006,24 +1006,34 @@ describe Search do
end
it 'can find by status' do
public_category = Fabricate(:category, read_restricted: false)
post = Fabricate(:post, raw: 'hi this is a test 123 123')
topic = post.topic
topic.update(category: public_category)
private_category = Fabricate(:category, read_restricted: true)
post2 = Fabricate(:post, raw: 'hi this is another test 123 123')
second_topic = post2.topic
second_topic.update(category: private_category)
post3 = Fabricate(:post, raw: "another test!", user: topic.user, topic: second_topic)
expect(Search.execute('test status:public').posts.length).to eq(1)
expect(Search.execute('test status:closed').posts.length).to eq(0)
expect(Search.execute('test status:open').posts.length).to eq(1)
expect(Search.execute('test posts_count:1').posts.length).to eq(1)
expect(Search.execute('test min_post_count:1').posts.length).to eq(1)
topic.closed = true
topic.save
topic.update(closed: true)
second_topic.update(category: public_category)
expect(Search.execute('test status:public').posts.length).to eq(2)
expect(Search.execute('test status:closed').posts.length).to eq(1)
expect(Search.execute('status:closed').posts.length).to eq(1)
expect(Search.execute('test status:open').posts.length).to eq(0)
expect(Search.execute('test status:open').posts.length).to eq(1)
topic.archived = true
topic.closed = false
topic.save
topic.update(archived: true, closed: false)
second_topic.update(closed: true)
expect(Search.execute('test status:archived').posts.length).to eq(1)
expect(Search.execute('test status:open').posts.length).to eq(0)
@ -1032,7 +1042,9 @@ describe Search do
expect(Search.execute('test in:likes', guardian: Guardian.new(topic.user)).posts.length).to eq(0)
expect(Search.execute('test in:posted', guardian: Guardian.new(topic.user)).posts.length).to eq(1)
expect(Search.execute('test in:posted', guardian: Guardian.new(topic.user)).posts.length).to eq(2)
expect(Search.execute('test in:created', guardian: Guardian.new(topic.user)).posts.length).to eq(1)
TopicUser.change(topic.user.id, topic.id, notification_level: TopicUser.notification_levels[:tracking])
expect(Search.execute('test in:watching', guardian: Guardian.new(topic.user)).posts.length).to eq(0)

View File

@ -363,3 +363,54 @@ QUnit.test("recently connected devices", async assert => {
"it should highlight password preferences"
);
});
acceptance(
"User can select a topic to feature on profile if site setting in enabled",
{
loggedIn: true,
settings: { allow_featured_topic_on_user_profiles: true },
pretend(server, helper) {
server.put("/u/eviltrout/feature-topic", () => {
return helper.response({
success: true
});
});
}
}
);
QUnit.test("setting featured topic on profile", async assert => {
await visit("/u/eviltrout/preferences/profile");
assert.ok(
!exists(".featured-topic-link"),
"no featured topic link to present"
);
assert.ok(
!exists(".clear-feature-topic-on-profile-btn"),
"clear button not present"
);
const selectTopicBtn = find(".feature-topic-on-profile-btn:first");
assert.ok(exists(selectTopicBtn), "feature topic button is present");
await click(selectTopicBtn);
assert.ok(exists(".feature-topic-on-profile"), "topic picker modal is open");
const topicRadioBtn = find('input[name="choose_topic_id"]:first');
assert.ok(exists(topicRadioBtn), "Topic options are prefilled");
await click(topicRadioBtn);
await click(".save-featured-topic-on-profile");
assert.ok(
exists(".featured-topic-link"),
"link to featured topic is present"
);
assert.ok(
exists(".clear-feature-topic-on-profile-btn"),
"clear button is present"
);
});

View File

@ -0,0 +1,91 @@
import { moduleForWidget, widgetTest } from "helpers/widget-test";
import Topic from "discourse/models/topic";
import Category from "discourse/models/category";
moduleForWidget("topic-admin-menu-button");
const createArgs = topic => {
return {
topic: topic,
openUpwards: "true",
toggleMultiSelect: () => {},
deleteTopic: () => {},
recoverTopic: () => {},
toggleFeaturedOnProfile: () => {},
toggleClosed: () => {},
toggleArchived: () => {},
toggleVisibility: () => {},
showTopicStatusUpdate: () => {},
showFeatureTopic: () => {},
showChangeTimestamp: () => {},
resetBumpDate: () => {},
convertToPublicTopic: () => {},
convertToPrivateMessage: () => {}
};
};
widgetTest("topic-admin-menu-button is present for admin/moderators", {
template: '{{mount-widget widget="topic-admin-menu-button" args=args}}',
beforeEach() {
this.currentUser.setProperties({
admin: true,
moderator: true,
id: 123
});
const topic = Topic.create({ user_id: this.currentUser.id });
topic.category = Category.create({ read_restricted: true });
this.siteSettings.allow_featured_topic_on_user_profiles = true;
this.set("args", createArgs(topic));
},
test(assert) {
assert.ok(exists(".toggle-admin-menu"), "admin wrench is present");
}
});
widgetTest(
"topic-admin-menu-button shows for non-admin when the use can feature the topic",
{
template: '{{mount-widget widget="topic-admin-menu-button" args=args}}',
beforeEach() {
this.currentUser.setProperties({
admin: false,
moderator: false,
id: 123
});
const topic = Topic.create({ user_id: this.currentUser.id });
topic.category = Category.create({ read_restricted: false });
this.siteSettings.allow_featured_topic_on_user_profiles = true;
this.set("args", createArgs(topic));
},
test(assert) {
assert.ok(exists(".toggle-admin-menu"), "admin wrench is present");
}
}
);
widgetTest(
"topic-admin-menu-button hides for non-admin when there is no action",
{
template: '{{mount-widget widget="topic-admin-menu-button" args=args}}',
beforeEach() {
this.currentUser.setProperties({
admin: false,
moderator: false,
id: 123
});
const topic = Topic.create({ user_id: this.currentUser.id });
topic.category = Category.create({ read_restricted: true });
this.siteSettings.allow_featured_topic_on_user_profiles = true;
this.set("args", createArgs(topic));
},
test(assert) {
assert.ok(!exists(".toggle-admin-menu"), "admin wrench is present");
}
}
);