mirror of
https://github.com/discourse/discourse.git
synced 2024-11-29 17:45:03 +08:00
FEATURE: adds an API to register topic footer buttons
This commit is contained in:
parent
92c52c0724
commit
6c195640b9
|
@ -39,7 +39,12 @@ export default Ember.Component.extend({
|
||||||
click() {
|
click() {
|
||||||
if (typeof this.get("action") === "string") {
|
if (typeof this.get("action") === "string") {
|
||||||
this.sendAction("action", this.get("actionParam"));
|
this.sendAction("action", this.get("actionParam"));
|
||||||
} else {
|
} else if (
|
||||||
|
typeof this.get("action") === "object" &&
|
||||||
|
this.get("action").value
|
||||||
|
) {
|
||||||
|
this.get("action").value(this.get("actionParam"));
|
||||||
|
} else if (typeof this.get("action") === "function") {
|
||||||
this.get("action")(this.get("actionParam"));
|
this.get("action")(this.get("actionParam"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import computed from "ember-addons/ember-computed-decorators";
|
import computed from "ember-addons/ember-computed-decorators";
|
||||||
|
import { getTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
|
||||||
|
|
||||||
export default Ember.Component.extend({
|
export default Ember.Component.extend({
|
||||||
elementId: "topic-footer-buttons",
|
elementId: "topic-footer-buttons",
|
||||||
|
@ -11,6 +12,19 @@ export default Ember.Component.extend({
|
||||||
return this.siteSettings.enable_personal_messages && isPM;
|
return this.siteSettings.enable_personal_messages && isPM;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
buttons: getTopicFooterButtons(),
|
||||||
|
|
||||||
|
@computed("buttons.[]")
|
||||||
|
inlineButtons(buttons) {
|
||||||
|
return buttons.filter(button => !button.dropdown);
|
||||||
|
},
|
||||||
|
|
||||||
|
// topic.assigned_to_user is for backward plugin support
|
||||||
|
@computed("buttons.[]", "topic.assigned_to_user")
|
||||||
|
dropdownButtons(buttons) {
|
||||||
|
return buttons.filter(button => button.dropdown);
|
||||||
|
},
|
||||||
|
|
||||||
@computed("topic.isPrivateMessage")
|
@computed("topic.isPrivateMessage")
|
||||||
showNotificationsButton(isPM) {
|
showNotificationsButton(isPM) {
|
||||||
return !isPM || this.siteSettings.enable_personal_messages;
|
return !isPM || this.siteSettings.enable_personal_messages;
|
||||||
|
@ -50,17 +64,5 @@ export default Ember.Component.extend({
|
||||||
|
|
||||||
@computed("topic.message_archived")
|
@computed("topic.message_archived")
|
||||||
archiveLabel: archived =>
|
archiveLabel: archived =>
|
||||||
archived ? "topic.move_to_inbox.title" : "topic.archive_message.title",
|
archived ? "topic.move_to_inbox.title" : "topic.archive_message.title"
|
||||||
|
|
||||||
@computed("topic.bookmarked")
|
|
||||||
bookmarkClass: bookmarked =>
|
|
||||||
bookmarked ? "bookmark bookmarked" : "bookmark",
|
|
||||||
|
|
||||||
@computed("topic.bookmarked")
|
|
||||||
bookmarkLabel: bookmarked =>
|
|
||||||
bookmarked ? "bookmarked.clear_bookmarks" : "bookmarked.title",
|
|
||||||
|
|
||||||
@computed("topic.bookmarked")
|
|
||||||
bookmarkTitle: bookmarked =>
|
|
||||||
bookmarked ? "bookmarked.help.unbookmark" : "bookmarked.help.bookmark"
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,138 @@
|
||||||
|
import { registerTopicFooterButton } from "discourse/lib/register-topic-footer-button";
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "topic-footer-buttons",
|
||||||
|
|
||||||
|
initialize() {
|
||||||
|
registerTopicFooterButton({
|
||||||
|
id: "share",
|
||||||
|
icon: "link",
|
||||||
|
priority: 999,
|
||||||
|
label: "topic.share.title",
|
||||||
|
title: "topic.share.help",
|
||||||
|
action() {
|
||||||
|
this.appEvents.trigger(
|
||||||
|
"share:url",
|
||||||
|
this.get("topic.shareUrl"),
|
||||||
|
$("#topic-footer-buttons")
|
||||||
|
);
|
||||||
|
},
|
||||||
|
dropdown() {
|
||||||
|
return this.site.mobileView;
|
||||||
|
},
|
||||||
|
classNames: ["share"],
|
||||||
|
dependentKeys: ["topic.shareUrl", "topic.isPrivateMessage"],
|
||||||
|
displayed() {
|
||||||
|
return !this.get("topic.isPrivateMessage");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTopicFooterButton({
|
||||||
|
id: "flag",
|
||||||
|
icon: "flag",
|
||||||
|
priority: 998,
|
||||||
|
label: "topic.flag_topic.title",
|
||||||
|
title: "topic.flag_topic.help",
|
||||||
|
action: "showFlagTopic",
|
||||||
|
dropdown() {
|
||||||
|
return this.site.mobileView;
|
||||||
|
},
|
||||||
|
classNames: ["flag-topic"],
|
||||||
|
dependentKeys: ["topic.details.can_flag_topic", "topic.isPrivateMessage"],
|
||||||
|
displayed() {
|
||||||
|
return (
|
||||||
|
this.get("topic.details.can_flag_topic") &&
|
||||||
|
!this.get("topic.isPrivateMessage")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTopicFooterButton({
|
||||||
|
id: "invite",
|
||||||
|
icon: "users",
|
||||||
|
priority: 997,
|
||||||
|
label: "topic.invite_reply.title",
|
||||||
|
title: "topic.invite_reply.help",
|
||||||
|
action: "showInvite",
|
||||||
|
dropdown() {
|
||||||
|
return this.site.mobileView;
|
||||||
|
},
|
||||||
|
classNames: ["invite-topic"],
|
||||||
|
dependentKeys: ["canInviteTo", "inviteDisabled"],
|
||||||
|
displayed() {
|
||||||
|
return this.get("canInviteTo");
|
||||||
|
},
|
||||||
|
disabled() {
|
||||||
|
return this.get("inviteDisabled");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTopicFooterButton({
|
||||||
|
dependentKeys: ["topic.bookmarked", "topic.isPrivateMessage"],
|
||||||
|
id: "bookmark",
|
||||||
|
icon: "bookmark",
|
||||||
|
priority: 1000,
|
||||||
|
classNames() {
|
||||||
|
const bookmarked = this.get("topic.bookmarked");
|
||||||
|
return bookmarked ? ["bookmark", "bookmarked"] : ["bookmark"];
|
||||||
|
},
|
||||||
|
label() {
|
||||||
|
const bookmarked = this.get("topic.bookmarked");
|
||||||
|
return bookmarked ? "bookmarked.clear_bookmarks" : "bookmarked.title";
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
const bookmarked = this.get("topic.bookmarked");
|
||||||
|
return bookmarked
|
||||||
|
? "bookmarked.help.unbookmark"
|
||||||
|
: "bookmarked.help.bookmark";
|
||||||
|
},
|
||||||
|
action: "toggleBookmark",
|
||||||
|
dropdown() {
|
||||||
|
return this.site.mobileView;
|
||||||
|
},
|
||||||
|
displayed() {
|
||||||
|
return !this.get("topic.isPrivateMessage");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTopicFooterButton({
|
||||||
|
id: "archive",
|
||||||
|
priority: 1001,
|
||||||
|
icon() {
|
||||||
|
return this.get("archiveIcon");
|
||||||
|
},
|
||||||
|
label() {
|
||||||
|
return this.get("archiveLabel");
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
return this.get("archiveTitle");
|
||||||
|
},
|
||||||
|
action: "toggleArchiveMessage",
|
||||||
|
classNames: ["standard", "archive-topic"],
|
||||||
|
dependentKeys: [
|
||||||
|
"canArchive",
|
||||||
|
"archiveIcon",
|
||||||
|
"archiveLabel",
|
||||||
|
"archiveTitle",
|
||||||
|
"toggleArchiveMessage"
|
||||||
|
],
|
||||||
|
displayed() {
|
||||||
|
return this.get("canArchive");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
registerTopicFooterButton({
|
||||||
|
id: "edit-message",
|
||||||
|
priority: 750,
|
||||||
|
icon: "pencil-alt",
|
||||||
|
label: "topic.edit_message.title",
|
||||||
|
title: "topic.edit_message.help",
|
||||||
|
action: "editFirstPost",
|
||||||
|
classNames: ["edit-message"],
|
||||||
|
dependentKeys: ["editFirstPost", "showEditOnFooter"],
|
||||||
|
displayed() {
|
||||||
|
return this.get("showEditOnFooter");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
|
@ -19,6 +19,7 @@ import { addFlagProperty } from "discourse/components/site-header";
|
||||||
import { addPopupMenuOptionsCallback } from "discourse/controllers/composer";
|
import { addPopupMenuOptionsCallback } from "discourse/controllers/composer";
|
||||||
import { extraConnectorClass } from "discourse/lib/plugin-connectors";
|
import { extraConnectorClass } from "discourse/lib/plugin-connectors";
|
||||||
import { addPostSmallActionIcon } from "discourse/widgets/post-small-action";
|
import { addPostSmallActionIcon } from "discourse/widgets/post-small-action";
|
||||||
|
import { registerTopicFooterButton } from "discourse/lib/register-topic-footer-button";
|
||||||
import { addDiscoveryQueryParam } from "discourse/controllers/discovery-sortable";
|
import { addDiscoveryQueryParam } from "discourse/controllers/discovery-sortable";
|
||||||
import { addTagsHtmlCallback } from "discourse/lib/render-tags";
|
import { addTagsHtmlCallback } from "discourse/lib/render-tags";
|
||||||
import { addUserMenuGlyph } from "discourse/widgets/user-menu";
|
import { addUserMenuGlyph } from "discourse/widgets/user-menu";
|
||||||
|
@ -41,7 +42,7 @@ import Sharing from "discourse/lib/sharing";
|
||||||
import { addComposerUploadHandler } from "discourse/components/composer-editor";
|
import { addComposerUploadHandler } from "discourse/components/composer-editor";
|
||||||
|
|
||||||
// If you add any methods to the API ensure you bump up this number
|
// If you add any methods to the API ensure you bump up this number
|
||||||
const PLUGIN_API_VERSION = "0.8.27";
|
const PLUGIN_API_VERSION = "0.8.28";
|
||||||
|
|
||||||
class PluginApi {
|
class PluginApi {
|
||||||
constructor(version, container) {
|
constructor(version, container) {
|
||||||
|
@ -599,6 +600,21 @@ class PluginApi {
|
||||||
extraConnectorClass(`${outletName}/${connectorName}`, klass);
|
extraConnectorClass(`${outletName}/${connectorName}`, klass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a small icon to be used for custom small post actions
|
||||||
|
*
|
||||||
|
* ```javascript
|
||||||
|
* api.registerTopicFooterButton({
|
||||||
|
* key: "flag"
|
||||||
|
* icon: "flag"
|
||||||
|
* action: (context) => console.log(context.get("topic.id"))
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
**/
|
||||||
|
registerTopicFooterButton(action) {
|
||||||
|
registerTopicFooterButton(action);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a small icon to be used for custom small post actions
|
* Register a small icon to be used for custom small post actions
|
||||||
*
|
*
|
||||||
|
|
|
@ -0,0 +1,125 @@
|
||||||
|
let _topicFooterButtons = [];
|
||||||
|
|
||||||
|
export function registerTopicFooterButton(button) {
|
||||||
|
const defaultButton = {
|
||||||
|
// id of the button, required
|
||||||
|
id: null,
|
||||||
|
|
||||||
|
// icon displayed on the button
|
||||||
|
icon: null,
|
||||||
|
|
||||||
|
// local key path for title attribute
|
||||||
|
title: null,
|
||||||
|
translatedTitle: null,
|
||||||
|
|
||||||
|
// local key path for label
|
||||||
|
label: null,
|
||||||
|
translatedLabel: null,
|
||||||
|
|
||||||
|
// is this button disaplyed in the mobile dropdown or as an inline button ?
|
||||||
|
dropdown: false,
|
||||||
|
|
||||||
|
// css class appended to the button
|
||||||
|
classNames: [],
|
||||||
|
|
||||||
|
// computed properties which should force a button state refresh
|
||||||
|
// eg: ["topic.bookmarked", "topic.category_id"]
|
||||||
|
dependentKeys: [],
|
||||||
|
|
||||||
|
// should we display this button ?
|
||||||
|
displayed: true,
|
||||||
|
|
||||||
|
// is this button disabled ?
|
||||||
|
disabled: false,
|
||||||
|
|
||||||
|
// display order, higher comes first
|
||||||
|
priority: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizedButton = Object.assign(defaultButton, button);
|
||||||
|
|
||||||
|
if (!normalizedButton.id) {
|
||||||
|
Ember.error(`Attempted to register a topic button: ${button} with no id.`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!normalizedButton.icon && !normalizedButton.title) {
|
||||||
|
Ember.error(
|
||||||
|
`Attempted to register a topic button: ${
|
||||||
|
button.id
|
||||||
|
} with no icon or title.`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_topicFooterButtons.push(normalizedButton);
|
||||||
|
|
||||||
|
_topicFooterButtons = _topicFooterButtons.uniqBy("id");
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getTopicFooterButtons() {
|
||||||
|
const dependentKeys = [].concat(
|
||||||
|
..._topicFooterButtons.map(tfb => tfb.dependentKeys).filter(x => x)
|
||||||
|
);
|
||||||
|
|
||||||
|
const computedFunc = Ember.computed({
|
||||||
|
get() {
|
||||||
|
const _isFunction = descriptor =>
|
||||||
|
descriptor && typeof descriptor === "function";
|
||||||
|
|
||||||
|
const _compute = (button, property) => {
|
||||||
|
const field = button[property];
|
||||||
|
|
||||||
|
if (_isFunction(field)) {
|
||||||
|
return field.apply(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
return field;
|
||||||
|
};
|
||||||
|
|
||||||
|
return _topicFooterButtons
|
||||||
|
.filter(button => _compute(button, "displayed"))
|
||||||
|
.map(button => {
|
||||||
|
const computedButon = {};
|
||||||
|
|
||||||
|
computedButon.id = button.id;
|
||||||
|
|
||||||
|
const label = _compute(button, "label");
|
||||||
|
computedButon.label = label
|
||||||
|
? I18n.t(label)
|
||||||
|
: _compute(button, "translatedLabel");
|
||||||
|
|
||||||
|
const title = _compute(button, "title");
|
||||||
|
computedButon.title = title
|
||||||
|
? I18n.t(title)
|
||||||
|
: _compute(button, "translatedTitle");
|
||||||
|
|
||||||
|
computedButon.classNames = (
|
||||||
|
_compute(button, "classNames") || []
|
||||||
|
).join(" ");
|
||||||
|
|
||||||
|
computedButon.icon = _compute(button, "icon");
|
||||||
|
computedButon.disabled = _compute(button, "disabled");
|
||||||
|
computedButon.dropdown = _compute(button, "dropdown");
|
||||||
|
computedButon.priority = _compute(button, "priority");
|
||||||
|
|
||||||
|
if (_isFunction(button.action)) {
|
||||||
|
computedButon.action = () => button.action.apply(this);
|
||||||
|
} else {
|
||||||
|
const actionName = button.action;
|
||||||
|
computedButon.action = () => this[actionName]();
|
||||||
|
}
|
||||||
|
|
||||||
|
return computedButon;
|
||||||
|
})
|
||||||
|
.sortBy("priority")
|
||||||
|
.reverse();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return computedFunc.property.apply(computedFunc, dependentKeys);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function clearTopicFooterButtons() {
|
||||||
|
_topicFooterButtons = [];
|
||||||
|
}
|
|
@ -18,55 +18,20 @@
|
||||||
convertToPrivateMessage=convertToPrivateMessage}}
|
convertToPrivateMessage=convertToPrivateMessage}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#unless topic.isPrivateMessage}}
|
{{#if site.mobileView}}
|
||||||
{{#if site.mobileView}}
|
{{topic-footer-mobile-dropdown topic=topic content=dropdownButtons}}
|
||||||
{{topic-footer-mobile-dropdown topic=topic
|
|
||||||
showInvite=showInvite
|
|
||||||
showFlagTopic=showFlagTopic}}
|
|
||||||
{{else}}
|
|
||||||
{{d-button class=(concat "btn-default " bookmarkClass)
|
|
||||||
title=bookmarkTitle
|
|
||||||
label=bookmarkLabel
|
|
||||||
icon="bookmark"
|
|
||||||
action=toggleBookmark}}
|
|
||||||
|
|
||||||
{{share-button url=topic.shareUrl}}
|
|
||||||
|
|
||||||
{{#if topic.details.can_flag_topic}}
|
|
||||||
{{d-button class="btn-default flag-topic"
|
|
||||||
title="topic.flag_topic.help"
|
|
||||||
label="topic.flag_topic.title"
|
|
||||||
icon="flag"
|
|
||||||
action=showFlagTopic}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{/if}}
|
|
||||||
{{/unless}}
|
|
||||||
|
|
||||||
{{#if canInviteTo}}
|
|
||||||
{{d-button class="btn-default invite-topic"
|
|
||||||
title="topic.invite_reply.help"
|
|
||||||
label="topic.invite_reply.title"
|
|
||||||
icon="users"
|
|
||||||
action=showInvite
|
|
||||||
disabled=inviteDisabled}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
{{#if canArchive}}
|
{{#each inlineButtons as |button|}}
|
||||||
{{d-button class="btn-default standard archive-topic"
|
{{d-button
|
||||||
title=archiveTitle
|
id=(concat "topic-footer-button-" button.id)
|
||||||
label=archiveLabel
|
class=(concat "btn-default topic-footer-button " button.classNames)
|
||||||
icon=archiveIcon
|
action=button.action
|
||||||
action=toggleArchiveMessage}}
|
icon=button.icon
|
||||||
{{/if}}
|
translatedLabel=button.label
|
||||||
|
translatedTitle=button.title
|
||||||
{{#if showEditOnFooter}}
|
disabled=button.disabled}}
|
||||||
{{d-button class="btn-default edit-message"
|
{{/each}}
|
||||||
title="topic.edit_message.help"
|
|
||||||
label="topic.edit_message.title"
|
|
||||||
icon="pencil-alt"
|
|
||||||
action=editFirstPost}}
|
|
||||||
{{/if}}
|
|
||||||
|
|
||||||
{{plugin-outlet name="topic-footer-main-buttons-before-create"
|
{{plugin-outlet name="topic-footer-main-buttons-before-create"
|
||||||
args=(hash topic=topic)
|
args=(hash topic=topic)
|
||||||
|
|
|
@ -144,18 +144,16 @@ export default Ember.Component.extend(
|
||||||
didComputeAttributes() {},
|
didComputeAttributes() {},
|
||||||
|
|
||||||
willComputeContent(content) {
|
willComputeContent(content) {
|
||||||
return content;
|
return applyContentPluginApiCallbacks(
|
||||||
|
this.get("pluginApiIdentifiers"),
|
||||||
|
content,
|
||||||
|
this
|
||||||
|
);
|
||||||
},
|
},
|
||||||
computeContent(content) {
|
computeContent(content) {
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
_beforeDidComputeContent(content) {
|
_beforeDidComputeContent(content) {
|
||||||
content = applyContentPluginApiCallbacks(
|
|
||||||
this.get("pluginApiIdentifiers"),
|
|
||||||
content,
|
|
||||||
this
|
|
||||||
);
|
|
||||||
|
|
||||||
let existingCreatedComputedContent = [];
|
let existingCreatedComputedContent = [];
|
||||||
if (!this.get("allowContentReplacement")) {
|
if (!this.get("allowContentReplacement")) {
|
||||||
existingCreatedComputedContent = this.get("computedContent").filterBy(
|
existingCreatedComputedContent = this.get("computedContent").filterBy(
|
||||||
|
|
|
@ -16,7 +16,11 @@ export default Ember.Component.extend(UtilsMixin, {
|
||||||
"ariaLabel:aria-label",
|
"ariaLabel:aria-label",
|
||||||
"guid:data-guid"
|
"guid:data-guid"
|
||||||
],
|
],
|
||||||
classNameBindings: ["isHighlighted", "isSelected"],
|
classNameBindings: [
|
||||||
|
"isHighlighted",
|
||||||
|
"isSelected",
|
||||||
|
"computedContent.originalContent.classNames"
|
||||||
|
],
|
||||||
|
|
||||||
forceEscape: Ember.computed.alias("options.forceEscape"),
|
forceEscape: Ember.computed.alias("options.forceEscape"),
|
||||||
|
|
||||||
|
|
|
@ -117,7 +117,7 @@ export default SelectKitComponent.extend({
|
||||||
|
|
||||||
@computed("computedAsyncContent.[]", "computedValue")
|
@computed("computedAsyncContent.[]", "computedValue")
|
||||||
filteredAsyncComputedContent(computedAsyncContent, computedValue) {
|
filteredAsyncComputedContent(computedAsyncContent, computedValue) {
|
||||||
computedAsyncContent = computedAsyncContent.filter(c => {
|
computedAsyncContent = (computedAsyncContent || []).filter(c => {
|
||||||
return computedValue !== get(c, "value");
|
return computedValue !== get(c, "value");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -268,12 +268,18 @@ export default SelectKitComponent.extend({
|
||||||
if (this.validateSelect(computedContentItem)) {
|
if (this.validateSelect(computedContentItem)) {
|
||||||
this.willSelect(computedContentItem);
|
this.willSelect(computedContentItem);
|
||||||
this.clearFilter();
|
this.clearFilter();
|
||||||
this.setProperties({
|
|
||||||
highlighted: null,
|
|
||||||
computedValue: computedContentItem.value
|
|
||||||
});
|
|
||||||
|
|
||||||
run.next(() => this.mutateAttributes());
|
const action = computedContentItem.originalContent.action;
|
||||||
|
if (action) {
|
||||||
|
action();
|
||||||
|
} else {
|
||||||
|
this.setProperties({
|
||||||
|
highlighted: null,
|
||||||
|
computedValue: computedContentItem.value
|
||||||
|
});
|
||||||
|
|
||||||
|
run.next(() => this.mutateAttributes());
|
||||||
|
}
|
||||||
|
|
||||||
run.schedule("afterRender", () => {
|
run.schedule("afterRender", () => {
|
||||||
this.didSelect(computedContentItem);
|
this.didSelect(computedContentItem);
|
||||||
|
|
|
@ -6,101 +6,29 @@ export default ComboBoxComponent.extend({
|
||||||
filterable: false,
|
filterable: false,
|
||||||
autoFilterable: false,
|
autoFilterable: false,
|
||||||
allowInitialValueMutation: false,
|
allowInitialValueMutation: false,
|
||||||
|
allowAutoSelectFirst: false,
|
||||||
|
nameProperty: "label",
|
||||||
|
|
||||||
computeHeaderContent() {
|
computeHeaderContent() {
|
||||||
let content = this._super(...arguments);
|
const content = this._super(...arguments);
|
||||||
|
|
||||||
content.name = I18n.t("topic.controls");
|
content.name = I18n.t("topic.controls");
|
||||||
return content;
|
return content;
|
||||||
},
|
},
|
||||||
|
|
||||||
computeContent(content) {
|
mutateAttributes() {},
|
||||||
const topic = this.get("topic");
|
|
||||||
const details = topic.get("details");
|
|
||||||
|
|
||||||
if (details.get("can_invite_to")) {
|
willComputeContent(content) {
|
||||||
content.push({
|
content = this._super(content);
|
||||||
id: "invite",
|
|
||||||
icon: "users",
|
|
||||||
name: I18n.t("topic.invite_reply.title"),
|
|
||||||
__sk_row_type: "noopRow"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
// TODO: this is for backward compat reasons, should be removed
|
||||||
(topic.get("bookmarked") && !topic.get("bookmarking")) ||
|
// when plugins have been updated for long enough
|
||||||
(!topic.get("bookmarked") && topic.get("bookmarking"))
|
content.forEach(c => {
|
||||||
) {
|
if (c.name) {
|
||||||
content.push({
|
c.label = c.name;
|
||||||
id: "bookmark",
|
}
|
||||||
icon: "bookmark",
|
|
||||||
name: I18n.t("bookmarked.clear_bookmarks"),
|
|
||||||
__sk_row_type: "noopRow"
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
content.push({
|
|
||||||
id: "bookmark",
|
|
||||||
icon: "bookmark",
|
|
||||||
name: I18n.t("bookmarked.title"),
|
|
||||||
__sk_row_type: "noopRow"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
content.push({
|
|
||||||
id: "share",
|
|
||||||
icon: "link",
|
|
||||||
name: I18n.t("topic.share.title"),
|
|
||||||
__sk_row_type: "noopRow"
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if (details.get("can_flag_topic")) {
|
|
||||||
content.push({
|
|
||||||
id: "flag",
|
|
||||||
icon: "flag",
|
|
||||||
name: I18n.t("topic.flag_topic.title"),
|
|
||||||
__sk_row_type: "noopRow"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return content;
|
return content;
|
||||||
},
|
|
||||||
|
|
||||||
autoHighlight() {},
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
onSelect(value) {
|
|
||||||
const topic = this.get("topic");
|
|
||||||
|
|
||||||
if (!topic.get("id")) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const refresh = () => {
|
|
||||||
this._compute();
|
|
||||||
this.deselect();
|
|
||||||
};
|
|
||||||
|
|
||||||
switch (value) {
|
|
||||||
case "flag":
|
|
||||||
this.showFlagTopic();
|
|
||||||
refresh();
|
|
||||||
break;
|
|
||||||
case "bookmark":
|
|
||||||
topic.toggleBookmark().then(refresh());
|
|
||||||
break;
|
|
||||||
case "share":
|
|
||||||
this.appEvents.trigger(
|
|
||||||
"share:url",
|
|
||||||
topic.get("shareUrl"),
|
|
||||||
$("#topic-footer-buttons")
|
|
||||||
);
|
|
||||||
refresh();
|
|
||||||
break;
|
|
||||||
case "invite":
|
|
||||||
this.showInvite();
|
|
||||||
refresh();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -68,13 +68,15 @@ function onSelect(pluginApiIdentifiers, mutationFunction) {
|
||||||
export function applyContentPluginApiCallbacks(identifiers, content, context) {
|
export function applyContentPluginApiCallbacks(identifiers, content, context) {
|
||||||
identifiers.forEach(key => {
|
identifiers.forEach(key => {
|
||||||
(_prependContentCallbacks[key] || []).forEach(c => {
|
(_prependContentCallbacks[key] || []).forEach(c => {
|
||||||
content = c().concat(content);
|
content = c()
|
||||||
|
.concat(content)
|
||||||
|
.uniqBy("id");
|
||||||
});
|
});
|
||||||
(_appendContentCallbacks[key] || []).forEach(c => {
|
(_appendContentCallbacks[key] || []).forEach(c => {
|
||||||
content = content.concat(c());
|
content = content.concat(c()).uniqBy("id");
|
||||||
});
|
});
|
||||||
(_modifyContentCallbacks[key] || []).forEach(c => {
|
(_modifyContentCallbacks[key] || []).forEach(c => {
|
||||||
content = c(context, content);
|
content = c(context, content).uniqBy("id");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,9 @@
|
||||||
|
.topic-footer-mobile-dropdown {
|
||||||
|
.select-kit-row {
|
||||||
|
&.bookmarked {
|
||||||
|
.d-icon {
|
||||||
|
color: $tertiary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,46 @@
|
||||||
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
|
import { clearTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
|
||||||
|
import { acceptance } from "helpers/qunit-helpers";
|
||||||
|
|
||||||
|
let _test;
|
||||||
|
|
||||||
|
acceptance("Topic footer buttons mobile", {
|
||||||
|
loggedIn: true,
|
||||||
|
mobileView: true,
|
||||||
|
beforeEach() {
|
||||||
|
I18n.translations[I18n.locale].js.test = {
|
||||||
|
title: "My title",
|
||||||
|
label: "My Label"
|
||||||
|
};
|
||||||
|
|
||||||
|
withPluginApi("0.8.28", api => {
|
||||||
|
api.registerTopicFooterButton({
|
||||||
|
id: "my-button",
|
||||||
|
icon: "user",
|
||||||
|
label: "test.label",
|
||||||
|
title: "test.title",
|
||||||
|
dropdown: true,
|
||||||
|
action() {
|
||||||
|
_test = 2;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
afterEach() {
|
||||||
|
clearTopicFooterButtons();
|
||||||
|
_test = undefined;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QUnit.test("default", async assert => {
|
||||||
|
await visit("/t/internationalization-localization/280");
|
||||||
|
|
||||||
|
assert.equal(_test, null);
|
||||||
|
|
||||||
|
const subject = selectKit(".topic-footer-mobile-dropdown");
|
||||||
|
await subject.expand();
|
||||||
|
await subject.selectRowByValue("my-button");
|
||||||
|
|
||||||
|
assert.equal(_test, 2);
|
||||||
|
});
|
|
@ -0,0 +1,184 @@
|
||||||
|
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||||
|
import componentTest from "helpers/component-test";
|
||||||
|
import Topic from "discourse/models/topic";
|
||||||
|
import { clearTopicFooterButtons } from "discourse/lib/register-topic-footer-button";
|
||||||
|
|
||||||
|
const buildTopic = function() {
|
||||||
|
return Topic.create({
|
||||||
|
id: 1234,
|
||||||
|
title: "Qunit Test Topic"
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
moduleForComponent("topic-footer-buttons-desktop", {
|
||||||
|
integration: true,
|
||||||
|
beforeEach() {
|
||||||
|
I18n.translations[I18n.locale].js.test = {
|
||||||
|
title: "My title",
|
||||||
|
label: "My Label"
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
afterEach() {
|
||||||
|
clearTopicFooterButtons();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("default", {
|
||||||
|
template: "{{topic-footer-buttons topic=topic}}",
|
||||||
|
beforeEach() {
|
||||||
|
withPluginApi("0.8.28", api => {
|
||||||
|
api.registerTopicFooterButton({
|
||||||
|
id: "my-button",
|
||||||
|
icon: "user",
|
||||||
|
label: "test.label",
|
||||||
|
title: "test.title"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("topic", buildTopic());
|
||||||
|
},
|
||||||
|
async test(assert) {
|
||||||
|
const button = await find("#topic-footer-button-my-button");
|
||||||
|
assert.ok(exists(button), "it creates an inline button");
|
||||||
|
|
||||||
|
const icon = await button.find(".d-icon-user");
|
||||||
|
assert.ok(exists(icon), "the button has the correct icon");
|
||||||
|
|
||||||
|
const label = await button.find(".d-button-label");
|
||||||
|
assert.ok(exists(label), "the button has a label");
|
||||||
|
assert.equal(
|
||||||
|
label.text(),
|
||||||
|
I18n.t("test.label"),
|
||||||
|
"the button has the correct label"
|
||||||
|
);
|
||||||
|
|
||||||
|
const title = button.attr("title");
|
||||||
|
assert.equal(
|
||||||
|
title,
|
||||||
|
I18n.t("test.title"),
|
||||||
|
"the button has the correct title"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("priority", {
|
||||||
|
template: "{{topic-footer-buttons topic=topic}}",
|
||||||
|
beforeEach() {
|
||||||
|
withPluginApi("0.8.28", api => {
|
||||||
|
api.registerTopicFooterButton({
|
||||||
|
id: "my-second-button",
|
||||||
|
priority: 750,
|
||||||
|
icon: "user"
|
||||||
|
});
|
||||||
|
|
||||||
|
api.registerTopicFooterButton({
|
||||||
|
id: "my-third-button",
|
||||||
|
priority: 500,
|
||||||
|
icon: "flag"
|
||||||
|
});
|
||||||
|
|
||||||
|
api.registerTopicFooterButton({
|
||||||
|
id: "my-first-button",
|
||||||
|
priority: 1000,
|
||||||
|
icon: "times"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("topic", buildTopic());
|
||||||
|
},
|
||||||
|
async test(assert) {
|
||||||
|
const buttons = await find(".topic-footer-button");
|
||||||
|
const firstButton = find("#topic-footer-button-my-first-button");
|
||||||
|
const secondButton = find("#topic-footer-button-my-second-button");
|
||||||
|
const thirdButton = find("#topic-footer-button-my-third-button");
|
||||||
|
|
||||||
|
assert.ok(buttons.index(firstButton) < buttons.index(secondButton));
|
||||||
|
assert.ok(buttons.index(secondButton) < buttons.index(thirdButton));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("with functions", {
|
||||||
|
template: "{{topic-footer-buttons topic=topic}}",
|
||||||
|
beforeEach() {
|
||||||
|
withPluginApi("0.8.28", api => {
|
||||||
|
api.registerTopicFooterButton({
|
||||||
|
id: "my-button",
|
||||||
|
icon() {
|
||||||
|
return "user";
|
||||||
|
},
|
||||||
|
label() {
|
||||||
|
return "test.label";
|
||||||
|
},
|
||||||
|
title() {
|
||||||
|
return "test.title";
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("topic", buildTopic());
|
||||||
|
},
|
||||||
|
async test(assert) {
|
||||||
|
const button = await find("#topic-footer-button-my-button");
|
||||||
|
assert.ok(exists(button), "it creates an inline button");
|
||||||
|
|
||||||
|
const icon = await button.find(".d-icon-user");
|
||||||
|
assert.ok(exists(icon), "the button has the correct icon");
|
||||||
|
|
||||||
|
const label = await button.find(".d-button-label");
|
||||||
|
assert.ok(exists(label), "the button has a label");
|
||||||
|
assert.equal(
|
||||||
|
label.text(),
|
||||||
|
I18n.t("test.label"),
|
||||||
|
"the button has the correct label"
|
||||||
|
);
|
||||||
|
|
||||||
|
const title = button.attr("title");
|
||||||
|
assert.equal(
|
||||||
|
title,
|
||||||
|
I18n.t("test.title"),
|
||||||
|
"the button has the correct title"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("action", {
|
||||||
|
template: "<div id='test-action'></div>{{topic-footer-buttons topic=topic}}",
|
||||||
|
beforeEach() {
|
||||||
|
withPluginApi("0.8.28", api => {
|
||||||
|
api.registerTopicFooterButton({
|
||||||
|
id: "my-button",
|
||||||
|
icon: "flag",
|
||||||
|
action() {
|
||||||
|
$("#test-action").text(this.get("topic.title"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("topic", buildTopic());
|
||||||
|
},
|
||||||
|
async test(assert) {
|
||||||
|
await click("#topic-footer-button-my-button");
|
||||||
|
|
||||||
|
assert.equal(find("#test-action").text(), this.get("topic.title"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
componentTest("dropdown", {
|
||||||
|
template: "{{topic-footer-buttons topic=topic}}",
|
||||||
|
beforeEach() {
|
||||||
|
withPluginApi("0.8.28", api => {
|
||||||
|
api.registerTopicFooterButton({
|
||||||
|
id: "my-button",
|
||||||
|
icon: "flag",
|
||||||
|
dropdown: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
this.set("topic", buildTopic());
|
||||||
|
},
|
||||||
|
async test(assert) {
|
||||||
|
const button = await find("#topic-footer-button-my-button");
|
||||||
|
assert.notOk(exists(button), "it doesn’t create an inline button");
|
||||||
|
}
|
||||||
|
});
|
|
@ -36,27 +36,11 @@ componentTest("default", {
|
||||||
.value(),
|
.value(),
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
assert.equal(
|
|
||||||
this.get("subject")
|
|
||||||
.rowByIndex(0)
|
|
||||||
.name(),
|
|
||||||
"Bookmark"
|
|
||||||
);
|
|
||||||
assert.equal(
|
|
||||||
this.get("subject")
|
|
||||||
.rowByIndex(1)
|
|
||||||
.name(),
|
|
||||||
"Share"
|
|
||||||
);
|
|
||||||
assert.notOk(
|
assert.notOk(
|
||||||
this.get("subject")
|
this.get("subject")
|
||||||
.selectedRow()
|
.selectedRow()
|
||||||
.exists(),
|
.exists(),
|
||||||
"it doesn’t preselect first row"
|
"it doesn’t preselect first row"
|
||||||
);
|
);
|
||||||
|
|
||||||
await this.get("subject").selectRowByValue("share");
|
|
||||||
|
|
||||||
assert.equal(this.get("value"), null, "it resets the value");
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue
Block a user