mirror of
https://gh.wpcy.net/https://github.com/discourse/discourse.git
synced 2026-05-23 14:55:15 +08:00
This change does two things: Modernizes the admin badges UI implementation. (Routes + controllers → components + services.) Adds a Settings tab to the new badges page. For all intents and purposes, this change is a lift-and-shift modernization. The addition of the settings tab is trivial once that is covered.
576 lines
16 KiB
Text
Vendored
576 lines
16 KiB
Text
Vendored
import Component from "@glimmer/component";
|
|
import { cached, tracked } from "@glimmer/tracking";
|
|
import { concat, fn, hash } from "@ember/helper";
|
|
import { action, getProperties } from "@ember/object";
|
|
import { LinkTo } from "@ember/routing";
|
|
import { service } from "@ember/service";
|
|
import Form from "discourse/components/form";
|
|
import PluginOutlet from "discourse/components/plugin-outlet";
|
|
import icon from "discourse/helpers/d-icon";
|
|
import iconOrImage from "discourse/helpers/icon-or-image";
|
|
import routeAction from "discourse/helpers/route-action";
|
|
import { ajax } from "discourse/lib/ajax";
|
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
|
import getURL from "discourse/lib/get-url";
|
|
import { i18n } from "discourse-i18n";
|
|
import AdminBadgesList from "admin/components/admin-badges-list";
|
|
import BadgePreviewModal from "admin/components/modal/badge-preview";
|
|
|
|
const FORM_FIELDS = [
|
|
"allow_title",
|
|
"multiple_grant",
|
|
"listable",
|
|
"auto_revoke",
|
|
"enabled",
|
|
"show_posts",
|
|
"target_posts",
|
|
"name",
|
|
"description",
|
|
"long_description",
|
|
"icon",
|
|
"image_upload_id",
|
|
"image_url",
|
|
"query",
|
|
"badge_grouping_id",
|
|
"trigger",
|
|
"badge_type_id",
|
|
"show_in_post_header",
|
|
];
|
|
|
|
export default class AdminBadgesShow extends Component {
|
|
@service adminBadges;
|
|
@service dialog;
|
|
@service modal;
|
|
@service router;
|
|
@service siteSettings;
|
|
@service toasts;
|
|
|
|
@tracked previewLoading = false;
|
|
|
|
get badges() {
|
|
return this.adminBadges.badges;
|
|
}
|
|
|
|
get badgeTypes() {
|
|
return this.adminBadges.badgeTypes;
|
|
}
|
|
|
|
get badgeGroupings() {
|
|
return this.adminBadges.badgeGroupings;
|
|
}
|
|
|
|
@action
|
|
currentBadgeGrouping(data) {
|
|
return this.adminBadges.badgeGroupings.find(
|
|
(bg) => bg.id === data.badge_grouping_id
|
|
)?.name;
|
|
}
|
|
|
|
get badgeTriggers() {
|
|
return this.adminBadges.badgeTriggers;
|
|
}
|
|
|
|
get readOnly() {
|
|
return this.args.badge.system;
|
|
}
|
|
|
|
get textCustomizationPrefix() {
|
|
return `badges.${this.args.badge.i18n_name}.`;
|
|
}
|
|
|
|
hasQuery(query) {
|
|
return query?.trim?.()?.length > 0;
|
|
}
|
|
|
|
// Form methods.
|
|
@cached
|
|
get formData() {
|
|
const data = getProperties(this.args.badge, ...FORM_FIELDS);
|
|
|
|
if (data.icon === "") {
|
|
data.icon = undefined;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
|
|
@action
|
|
postHeaderDescription(data) {
|
|
return this.disableBadgeOnPosts(data) && !data.system;
|
|
}
|
|
|
|
@action
|
|
disableBadgeOnPosts(data) {
|
|
const { listable, show_posts } = data;
|
|
return !listable || !show_posts;
|
|
}
|
|
|
|
@action
|
|
onSetImage(upload, { set }) {
|
|
if (upload) {
|
|
set("image_upload_id", upload.id);
|
|
set("image_url", getURL(upload.url));
|
|
set("icon", null);
|
|
} else {
|
|
set("image_upload_id", "");
|
|
set("image_url", "");
|
|
}
|
|
}
|
|
|
|
@action
|
|
onSetIcon(value, { set }) {
|
|
set("icon", value);
|
|
set("image_upload_id", "");
|
|
set("image_url", "");
|
|
}
|
|
|
|
@action
|
|
showPreview(badge, explain, event) {
|
|
event?.preventDefault();
|
|
this.preview(badge, explain);
|
|
}
|
|
|
|
@action
|
|
async preview(badge, explain) {
|
|
try {
|
|
this.previewLoading = true;
|
|
const model = await ajax("/admin/badges/preview.json", {
|
|
type: "POST",
|
|
data: {
|
|
sql: badge.query,
|
|
target_posts: !!badge.target_posts,
|
|
trigger: badge.trigger,
|
|
explain,
|
|
},
|
|
});
|
|
|
|
this.modal.show(BadgePreviewModal, { model: { badge: model } });
|
|
} catch (e) {
|
|
// eslint-disable-next-line no-console
|
|
console.error(e);
|
|
this.dialog.alert("Network error");
|
|
} finally {
|
|
this.previewLoading = false;
|
|
}
|
|
}
|
|
|
|
@action
|
|
validateForm(data, { addError, removeError }) {
|
|
if (!data.icon && !data.image_url) {
|
|
addError("icon", {
|
|
title: "Icon",
|
|
message: i18n("admin.badges.icon_or_image"),
|
|
});
|
|
addError("image_url", {
|
|
title: "Image",
|
|
message: i18n("admin.badges.icon_or_image"),
|
|
});
|
|
} else {
|
|
removeError("image_url");
|
|
removeError("icon");
|
|
}
|
|
}
|
|
|
|
@action
|
|
async handleSubmit(formData) {
|
|
let fields = FORM_FIELDS;
|
|
|
|
if (formData.system) {
|
|
const protectedFields = this.protectedSystemFields || [];
|
|
fields = fields.filter((f) => !protectedFields.includes(f));
|
|
}
|
|
|
|
const data = {};
|
|
fields.forEach(function (field) {
|
|
data[field] = formData[field];
|
|
});
|
|
|
|
const newBadge = !this.args.badge.id;
|
|
|
|
try {
|
|
const badge = await this.args.badge.save(data);
|
|
|
|
this.toasts.success({ data: { message: i18n("saved") } });
|
|
|
|
if (newBadge) {
|
|
const adminBadges = this.adminBadges.badges;
|
|
if (!adminBadges.includes(badge)) {
|
|
adminBadges.push(badge);
|
|
}
|
|
return this.router.transitionTo("adminBadges.show", badge.id);
|
|
}
|
|
} catch (error) {
|
|
return popupAjaxError(error);
|
|
}
|
|
}
|
|
|
|
@action
|
|
registerApi(api) {
|
|
this.formApi = api;
|
|
}
|
|
|
|
@action
|
|
async handleDelete() {
|
|
if (!this.args.badge?.id) {
|
|
return this.router.transitionTo("adminBadges.index");
|
|
}
|
|
|
|
return this.dialog.yesNoConfirm({
|
|
message: i18n("admin.badges.delete_confirm"),
|
|
didConfirm: async () => {
|
|
try {
|
|
await this.formApi.reset();
|
|
await this.args.badge.destroy();
|
|
this.adminBadges.badges = this.adminBadges.badges.filter(
|
|
(badge) => badge.id !== this.args.badge.id
|
|
);
|
|
this.router.transitionTo("adminBadges.index");
|
|
} catch {
|
|
this.dialog.alert(i18n("generic_error"));
|
|
}
|
|
},
|
|
});
|
|
}
|
|
|
|
<template>
|
|
<AdminBadgesList @badges={{this.badges}} />
|
|
{{#if @badge}}
|
|
<Form
|
|
@data={{this.formData}}
|
|
@onSubmit={{this.handleSubmit}}
|
|
@validate={{this.validateForm}}
|
|
@onRegisterApi={{this.registerApi}}
|
|
class="badge-form current-badge content-body"
|
|
as |form data|
|
|
>
|
|
|
|
<h2 class="current-badge-header">
|
|
{{iconOrImage data}}
|
|
<span class="badge-display-name">{{data.name}}</span>
|
|
</h2>
|
|
|
|
<form.Field
|
|
@name="enabled"
|
|
@validation="required"
|
|
@title={{i18n "admin.badges.status"}}
|
|
as |field|
|
|
>
|
|
<field.Question
|
|
@yesLabel={{i18n "admin.badges.enabled"}}
|
|
@noLabel={{i18n "admin.badges.disabled"}}
|
|
/>
|
|
</form.Field>
|
|
|
|
{{#if this.readOnly}}
|
|
<form.Container data-name="name" @title={{i18n "admin.badges.name"}}>
|
|
<span class="readonly-field">
|
|
{{@badge.name}}
|
|
</span>
|
|
<LinkTo
|
|
@route="adminSiteText"
|
|
@query={{hash q=(concat this.textCustomizationPrefix "name")}}
|
|
>
|
|
{{icon "pencil"}}
|
|
</LinkTo>
|
|
</form.Container>
|
|
{{else}}
|
|
<form.Field
|
|
@title={{i18n "admin.badges.name"}}
|
|
@name="name"
|
|
@disabled={{this.readOnly}}
|
|
@validation="required"
|
|
as |field|
|
|
>
|
|
<field.Input />
|
|
</form.Field>
|
|
{{/if}}
|
|
|
|
<form.Section @title="Design">
|
|
<form.Field
|
|
@name="badge_type_id"
|
|
@title={{i18n "admin.badges.badge_type"}}
|
|
@validation="required"
|
|
@disabled={{this.readOnly}}
|
|
as |field|
|
|
>
|
|
<field.Select as |select|>
|
|
{{#each this.badgeTypes as |badgeType|}}
|
|
<select.Option @value={{badgeType.id}}>
|
|
{{badgeType.name}}
|
|
</select.Option>
|
|
{{/each}}
|
|
</field.Select>
|
|
</form.Field>
|
|
|
|
<form.ConditionalContent
|
|
@activeName={{if data.image_url "upload-image" "choose-icon"}}
|
|
as |cc|
|
|
>
|
|
<cc.Conditions as |Condition|>
|
|
<Condition @name="choose-icon">
|
|
{{i18n "admin.badges.select_an_icon"}}
|
|
</Condition>
|
|
<Condition @name="upload-image">
|
|
{{i18n "admin.badges.upload_an_image"}}
|
|
</Condition>
|
|
</cc.Conditions>
|
|
<cc.Contents as |Content|>
|
|
<Content @name="choose-icon">
|
|
<form.Field
|
|
@title={{i18n "admin.badges.icon"}}
|
|
@showTitle={{false}}
|
|
@name="icon"
|
|
@onSet={{this.onSetIcon}}
|
|
@format="small"
|
|
as |field|
|
|
>
|
|
<field.Icon />
|
|
</form.Field>
|
|
</Content>
|
|
<Content @name="upload-image">
|
|
<form.Field
|
|
@name="image_url"
|
|
@showTitle={{false}}
|
|
@title={{i18n "admin.badges.image"}}
|
|
@onSet={{this.onSetImage}}
|
|
as |field|
|
|
>
|
|
<field.Image @type="badge_image" />
|
|
</form.Field>
|
|
</Content>
|
|
</cc.Contents>
|
|
</form.ConditionalContent>
|
|
|
|
{{#if this.readOnly}}
|
|
<form.Container
|
|
data-name="description"
|
|
@title={{i18n "admin.badges.description"}}
|
|
>
|
|
<span class="readonly-field">
|
|
{{@badge.description}}
|
|
</span>
|
|
<LinkTo
|
|
@route="adminSiteText"
|
|
@query={{hash
|
|
q=(concat this.textCustomizationPrefix "description")
|
|
}}
|
|
>
|
|
{{icon "pencil"}}
|
|
</LinkTo>
|
|
</form.Container>
|
|
{{else}}
|
|
<form.Field
|
|
@title={{i18n "admin.badges.description"}}
|
|
@name="description"
|
|
@disabled={{this.readOnly}}
|
|
as |field|
|
|
>
|
|
<field.Textarea />
|
|
</form.Field>
|
|
{{/if}}
|
|
|
|
{{#if this.readOnly}}
|
|
<form.Container
|
|
data-name="long_description"
|
|
@title={{i18n "admin.badges.long_description"}}
|
|
>
|
|
<span class="readonly-field">
|
|
{{@badge.long_description}}
|
|
</span>
|
|
|
|
<LinkTo
|
|
@route="adminSiteText"
|
|
@query={{hash
|
|
q=(concat this.textCustomizationPrefix "long_description")
|
|
}}
|
|
>
|
|
{{icon "pencil"}}
|
|
</LinkTo>
|
|
</form.Container>
|
|
{{else}}
|
|
<form.Field
|
|
@name="long_description"
|
|
@title={{i18n "admin.badges.long_description"}}
|
|
@disabled={{this.readOnly}}
|
|
as |field|
|
|
>
|
|
<field.Textarea />
|
|
</form.Field>
|
|
{{/if}}
|
|
</form.Section>
|
|
|
|
{{#if this.siteSettings.enable_badge_sql}}
|
|
<form.Section @title="Query">
|
|
<form.Field
|
|
@name="query"
|
|
@title={{i18n "admin.badges.query"}}
|
|
@disabled={{this.readOnly}}
|
|
as |field|
|
|
>
|
|
<field.Code @lang="sql" />
|
|
</form.Field>
|
|
|
|
{{#if (this.hasQuery data.query)}}
|
|
<form.Container>
|
|
<form.Button
|
|
@isLoading={{this.previewLoading}}
|
|
@label="admin.badges.preview.link_text"
|
|
class="preview-badge"
|
|
@action={{fn this.showPreview data "false"}}
|
|
/>
|
|
<form.Button
|
|
@isLoading={{this.previewLoading}}
|
|
@label="admin.badges.preview.plan_text"
|
|
class="preview-badge-plan"
|
|
@action={{fn this.showPreview data "true"}}
|
|
/>
|
|
</form.Container>
|
|
|
|
<form.CheckboxGroup as |group|>
|
|
<group.Field
|
|
@name="auto_revoke"
|
|
@disabled={{this.readOnly}}
|
|
@showTitle={{false}}
|
|
@title={{i18n "admin.badges.auto_revoke"}}
|
|
as |field|
|
|
>
|
|
<field.Checkbox />
|
|
</group.Field>
|
|
|
|
<group.Field
|
|
@name="target_posts"
|
|
@disabled={{this.readOnly}}
|
|
@title={{i18n "admin.badges.target_posts"}}
|
|
@showTitle={{false}}
|
|
as |field|
|
|
>
|
|
<field.Checkbox />
|
|
</group.Field>
|
|
</form.CheckboxGroup>
|
|
|
|
<form.Field
|
|
@name="trigger"
|
|
@disabled={{this.readOnly}}
|
|
@validation="required"
|
|
@title={{i18n "admin.badges.trigger"}}
|
|
as |field|
|
|
>
|
|
<field.Select as |select|>
|
|
{{#each this.badgeTriggers as |badgeTrigger|}}
|
|
<select.Option @value={{badgeTrigger.id}}>
|
|
{{badgeTrigger.name}}
|
|
</select.Option>
|
|
{{/each}}
|
|
</field.Select>
|
|
</form.Field>
|
|
{{/if}}
|
|
</form.Section>
|
|
{{/if}}
|
|
|
|
<form.Section @title="Settings">
|
|
<form.Field
|
|
@name="badge_grouping_id"
|
|
@disabled={{this.readOnly}}
|
|
@validation="required"
|
|
@title={{i18n "admin.badges.badge_grouping"}}
|
|
as |field|
|
|
>
|
|
<field.Menu @selection={{this.currentBadgeGrouping data}} as |menu|>
|
|
{{#each this.badgeGroupings as |grouping|}}
|
|
<menu.Item @value={{grouping.id}}>{{grouping.name}}</menu.Item>
|
|
{{/each}}
|
|
<menu.Divider />
|
|
<menu.Item @action={{routeAction "editGroupings"}}>Add new group</menu.Item>
|
|
</field.Menu>
|
|
</form.Field>
|
|
|
|
<form.CheckboxGroup
|
|
@title={{i18n "admin.badges.usage_heading"}}
|
|
as |group|
|
|
>
|
|
<group.Field
|
|
@title={{i18n "admin.badges.allow_title"}}
|
|
@showTitle={{false}}
|
|
@name="allow_title"
|
|
@format="full"
|
|
as |field|
|
|
>
|
|
<field.Checkbox />
|
|
</group.Field>
|
|
|
|
<group.Field
|
|
@title={{i18n "admin.badges.multiple_grant"}}
|
|
@showTitle={{false}}
|
|
@name="multiple_grant"
|
|
@disabled={{this.readOnly}}
|
|
@format="full"
|
|
as |field|
|
|
>
|
|
<field.Checkbox />
|
|
</group.Field>
|
|
</form.CheckboxGroup>
|
|
|
|
<form.CheckboxGroup
|
|
@title={{i18n "admin.badges.visibility_heading"}}
|
|
as |group|
|
|
>
|
|
<group.Field
|
|
@title={{i18n "admin.badges.listable"}}
|
|
@showTitle={{false}}
|
|
@name="listable"
|
|
@disabled={{this.readOnly}}
|
|
@format="full"
|
|
as |field|
|
|
>
|
|
<field.Checkbox />
|
|
</group.Field>
|
|
|
|
<group.Field
|
|
@title={{i18n "admin.badges.show_posts"}}
|
|
@showTitle={{false}}
|
|
@name="show_posts"
|
|
@disabled={{this.readOnly}}
|
|
@format="full"
|
|
as |field|
|
|
>
|
|
<field.Checkbox />
|
|
</group.Field>
|
|
|
|
<group.Field
|
|
@title={{i18n "admin.badges.show_in_post_header"}}
|
|
@showTitle={{false}}
|
|
@name="show_in_post_header"
|
|
@disabled={{this.disableBadgeOnPosts data}}
|
|
@format="full"
|
|
as |field|
|
|
>
|
|
<field.Checkbox>
|
|
{{#if (this.postHeaderDescription data)}}
|
|
{{i18n "admin.badges.show_in_post_header_disabled"}}
|
|
{{/if}}
|
|
</field.Checkbox>
|
|
</group.Field>
|
|
</form.CheckboxGroup>
|
|
</form.Section>
|
|
|
|
<PluginOutlet
|
|
@name="admin-above-badge-buttons"
|
|
@outletArgs={{hash badge=this.buffered form=form}}
|
|
/>
|
|
|
|
<form.Actions>
|
|
<form.Submit />
|
|
|
|
{{#unless this.readOnly}}
|
|
<form.Button
|
|
@action={{this.handleDelete}}
|
|
class="badge-form__delete-badge-btn btn-danger"
|
|
>
|
|
{{i18n "admin.badges.delete"}}
|
|
</form.Button>
|
|
{{/unless}}
|
|
</form.Actions>
|
|
</Form>
|
|
{{/if}}
|
|
</template>
|
|
}
|