DEV: Switch edit sidebar section modal to component (#22430)

Why this change?

A new component based API for modals was introduced in
b3a23bd9d6. This commit moves the edit
sidebar section modal to the new API.

Reviewer notes
No functionality or visual change is introduced in this PR.
This commit is contained in:
Krzysztof Kotlarek 2023-07-06 11:42:25 +10:00 committed by GitHub
parent 0744d242c6
commit fee3ebd812
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 267 additions and 259 deletions

View File

@ -0,0 +1,122 @@
<DModal
@closeModal={{@closeModal}}
@flash={{this.flash}}
@flashType={{this.flashType}}
@title={{i18n this.header}}
class="sidebar-section-form-modal"
>
<:body>
<form class="form-horizontal">
<div class="input-group">
<label for="section-name">{{i18n
"sidebar.sections.custom.title.label"
}}</label>
<Input
name="section-name"
@type="text"
@value={{this.transformedModel.title}}
class={{this.transformedModel.titleCssClass}}
{{on
"input"
(action (mut this.transformedModel.title) value="target.value")
}}
/>
{{#if this.transformedModel.invalidTitleMessage}}
<div class="title warning">
{{this.transformedModel.invalidTitleMessage}}
</div>
{{/if}}
</div>
<div class="row-wrapper header">
<div class="input-group link-icon">
<label>{{i18n "sidebar.sections.custom.links.icon.label"}}</label>
</div>
<div class="input-group link-name">
<label>{{i18n "sidebar.sections.custom.links.name.label"}}</label>
</div>
<div class="input-group link-url">
<label>{{i18n "sidebar.sections.custom.links.value.label"}}</label>
</div>
</div>
{{#each this.activeLinks as |link|}}
<Sidebar::SectionFormLink
@link={{link}}
@deleteLink={{this.deleteLink}}
@reorderCallback={{this.reorder}}
/>
{{/each}}
<DButton
@action={{action "addLink"}}
@class="btn-flat btn-text add-link"
@title="sidebar.sections.custom.links.add"
@icon="plus"
@label="sidebar.sections.custom.links.add"
@ariaLabel="sidebar.sections.custom.links.add"
/>
{{#if this.transformedModel.sectionType}}
<hr />
<h3>{{i18n "sidebar.sections.custom.more_menu"}}</h3>
{{#each this.activeSecondaryLinks as |link|}}
<Sidebar::SectionFormLink
@link={{link}}
@deleteLink={{this.deleteLink}}
@reorderCallback={{this.reorder}}
/>
{{/each}}
<DButton
@action={{action "addSecondaryLink"}}
@class="btn-flat btn-text add-link"
@title="sidebar.sections.custom.links.add"
@icon="plus"
@label="sidebar.sections.custom.links.add"
@ariaLabel="sidebar.sections.custom.links.add"
/>
{{/if}}
{{#if
(and this.currentUser.staff (not this.transformedModel.sectionType))
}}
<div class="row-wrapper mark-public-wrapper">
<label class="checkbox-label">
<Input
@type="checkbox"
@checked={{this.transformedModel.public}}
class="mark-public"
/>
{{i18n "sidebar.sections.custom.public"}}
</label>
</div>
{{/if}}
</form>
</:body>
<:footer>
<DButton
@id="save-section"
@action={{action "save"}}
@class="btn-primary"
@label="sidebar.sections.custom.save"
@ariaLabel="sidebar.sections.custom.save"
@disabled={{not this.transformedModel.valid}}
/>
{{#if this.canDelete}}
<DButton
@icon="trash-alt"
@id="delete-section"
@class="btn-danger delete"
@action={{action "delete"}}
@label="sidebar.sections.custom.delete"
@ariaLabel="sidebar.sections.custom.delete"
/>
{{/if}}
{{#if this.transformedModel.sectionType}}
<DButton
@action={{action "resetToDefault"}}
@class="btn-flat btn-text reset-link"
@icon="undo"
@title="sidebar.sections.custom.links.reset"
@label="sidebar.sections.custom.links.reset"
@ariaLabel="sidebar.sections.custom.links.reset"
/>
{{/if}}
</:footer>
</DModal>

View File

@ -1,15 +1,15 @@
import Controller from "@ember/controller"; import Component from "@ember/component";
import ModalFunctionality from "discourse/mixins/modal-functionality";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import { isEmpty } from "@ember/utils"; import { isEmpty } from "@ember/utils";
import { extractError } from "discourse/lib/ajax-error"; import { extractError } from "discourse/lib/ajax-error";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import I18n from "I18n"; import I18n from "I18n";
import { sanitize } from "discourse/lib/text"; import { sanitize } from "discourse/lib/text";
import { tracked } from "@glimmer/tracking"; import { cached, tracked } from "@glimmer/tracking";
import { A } from "@ember/array"; import { A } from "@ember/array";
import { SIDEBAR_SECTION, SIDEBAR_URL } from "discourse/lib/constants"; import { SIDEBAR_SECTION, SIDEBAR_URL } from "discourse/lib/constants";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import { action } from "@ember/object";
const FULL_RELOAD_LINKS_REGEX = [ const FULL_RELOAD_LINKS_REGEX = [
/^\/my\/[a-z_\-\/]+$/, /^\/my\/[a-z_\-\/]+$/,
@ -232,24 +232,17 @@ class SectionLink {
} }
} }
export default Controller.extend(ModalFunctionality, { export default class SidebarSectionForm extends Component {
dialog: service(), @service dialog;
router: service(), @service router;
onShow() { @tracked flash;
this.setProperties({ @tracked flashType;
flashText: null,
flashClass: null,
});
this.nextObjectId = 0;
this.model = this.initModel();
},
onClose() { nextObjectId = 0;
this.model = null;
},
initModel() { @cached
get transformedModel() {
if (this.model) { if (this.model) {
return new Section({ return new Section({
title: this.model.title, title: this.model.title,
@ -282,7 +275,7 @@ export default Controller.extend(ModalFunctionality, {
]), ]),
}); });
} }
}, }
initLink(link) { initLink(link) {
return new SectionLink({ return new SectionLink({
@ -294,7 +287,7 @@ export default Controller.extend(ModalFunctionality, {
objectId: this.nextObjectId, objectId: this.nextObjectId,
segment: link.segment, segment: link.segment,
}); });
}, }
create() { create() {
return ajax(`/sidebar_sections`, { return ajax(`/sidebar_sections`, {
@ -302,9 +295,9 @@ export default Controller.extend(ModalFunctionality, {
contentType: "application/json", contentType: "application/json",
dataType: "json", dataType: "json",
data: JSON.stringify({ data: JSON.stringify({
title: this.model.title, title: this.transformedModel.title,
public: this.model.public, public: this.transformedModel.public,
links: this.model.links.map((link) => { links: this.transformedModel.links.map((link) => {
return { return {
icon: link.icon, icon: link.icon,
name: link.name, name: link.name,
@ -318,26 +311,24 @@ export default Controller.extend(ModalFunctionality, {
"sidebar_sections", "sidebar_sections",
this.currentUser.sidebar_sections.concat(data.sidebar_section) this.currentUser.sidebar_sections.concat(data.sidebar_section)
); );
this.send("closeModal"); this.closeModal();
}) })
.catch((e) => .catch((e) => {
this.setProperties({ this.flash = sanitize(extractError(e));
flashText: sanitize(extractError(e)), this.flashType = "error";
flashClass: "error", });
}) }
);
},
update() { update() {
return ajax(`/sidebar_sections/${this.model.id}`, { return ajax(`/sidebar_sections/${this.transformedModel.id}`, {
type: "PUT", type: "PUT",
contentType: "application/json", contentType: "application/json",
dataType: "json", dataType: "json",
data: JSON.stringify({ data: JSON.stringify({
title: this.model.title, title: this.transformedModel.title,
public: this.model.public, public: this.transformedModel.public,
links: this.model.links links: this.transformedModel.links
.concat(this.model?.secondaryLinks || []) .concat(this.transformedModel?.secondaryLinks || [])
.map((link) => { .map((link) => {
return { return {
id: link.id, id: link.id,
@ -360,67 +351,70 @@ export default Controller.extend(ModalFunctionality, {
} }
); );
this.currentUser.set("sidebar_sections", newSidebarSections); this.currentUser.set("sidebar_sections", newSidebarSections);
this.send("closeModal"); this.closeModal();
}) })
.catch((e) => .catch((e) => {
this.setProperties({ this.flash = sanitize(extractError(e));
flashText: sanitize(extractError(e)), this.flashType = "error";
flashClass: "error", });
}) }
);
},
get activeLinks() { get activeLinks() {
return this.model.links.filter((link) => !link._destroy); return this.transformedModel.links.filter((link) => !link._destroy);
}, }
get activeSecondaryLinks() { get activeSecondaryLinks() {
return this.model.secondaryLinks?.filter((link) => !link._destroy); return this.transformedModel.secondaryLinks?.filter(
}, (link) => !link._destroy
);
}
get header() { get header() {
return this.model.id return this.transformedModel.id
? "sidebar.sections.custom.edit" ? "sidebar.sections.custom.edit"
: "sidebar.sections.custom.add"; : "sidebar.sections.custom.add";
}, }
@bind @bind
reorder(linkFromId, linkTo, above) { reorder(linkFromId, linkTo, above) {
if (linkFromId === linkTo.objectId) { if (linkFromId === linkTo.objectId) {
return; return;
} }
let linkFrom = this.model.links.find( let linkFrom = this.transformedModel.links.find(
(link) => link.objectId === linkFromId (link) => link.objectId === linkFromId
); );
if (!linkFrom) { if (!linkFrom) {
linkFrom = this.model.secondaryLinks.find( linkFrom = this.transformedModel.secondaryLinks.find(
(link) => link.objectId === linkFromId (link) => link.objectId === linkFromId
); );
} }
if (linkFrom.isPrimary) { if (linkFrom.isPrimary) {
this.model.links.removeObject(linkFrom); this.transformedModel.links.removeObject(linkFrom);
} else { } else {
this.model.secondaryLinks?.removeObject(linkFrom); this.transformedModel.secondaryLinks?.removeObject(linkFrom);
} }
if (linkTo.isPrimary) { if (linkTo.isPrimary) {
const toPosition = this.model.links.indexOf(linkTo); const toPosition = this.transformedModel.links.indexOf(linkTo);
linkFrom.segment = "primary"; linkFrom.segment = "primary";
this.model.links.insertAt(above ? toPosition : toPosition + 1, linkFrom); this.transformedModel.links.insertAt(
above ? toPosition : toPosition + 1,
linkFrom
);
} else { } else {
linkFrom.segment = "secondary"; linkFrom.segment = "secondary";
const toPosition = this.model.secondaryLinks.indexOf(linkTo); const toPosition = this.transformedModel.secondaryLinks.indexOf(linkTo);
this.model.secondaryLinks.insertAt( this.transformedModel.secondaryLinks.insertAt(
above ? toPosition : toPosition + 1, above ? toPosition : toPosition + 1,
linkFrom linkFrom
); );
} }
}, }
get canDelete() { get canDelete() {
return this.model.id && !this.model.sectionType; return this.transformedModel.id && !this.transformedModel.sectionType;
}, }
@bind @bind
deleteLink(link) { deleteLink(link) {
@ -428,88 +422,88 @@ export default Controller.extend(ModalFunctionality, {
link._destroy = "1"; link._destroy = "1";
} else { } else {
if (link.isPrimary) { if (link.isPrimary) {
this.model.links.removeObject(link); this.transformedModel.links.removeObject(link);
} else { } else {
this.model.secondaryLinks.removeObject(link); this.transformedModel.secondaryLinks.removeObject(link);
} }
} }
}, }
actions: { @action
addLink() { addLink() {
this.nextObjectId = this.nextObjectId + 1; this.nextObjectId = this.nextObjectId + 1;
this.model.links.pushObject( this.transformedModel.links.pushObject(
new SectionLink({ new SectionLink({
router: this.router, router: this.router,
objectId: this.nextObjectId, objectId: this.nextObjectId,
segment: "primary", segment: "primary",
})
);
}
@action
addSecondaryLink() {
this.nextObjectId = this.nextObjectId + 1;
this.transformedModel.secondaryLinks.pushObject(
new SectionLink({
router: this.router,
objectId: this.nextObjectId,
segment: "secondary",
})
);
}
@action
resetToDefault() {
return this.dialog.yesNoConfirm({
message: I18n.t("sidebar.sections.custom.reset_confirm"),
didConfirm: () => {
return ajax(`/sidebar_sections/reset/${this.transformedModel.id}`, {
type: "PUT",
}) })
); .then((data) => {
}, this.currentUser.sidebar_sections.shiftObject();
this.currentUser.sidebar_sections.unshiftObject(
data["sidebar_section"]
);
this.closeModal();
})
.catch((e) => {
this.flash = sanitize(extractError(e));
this.flashType = "error";
});
},
});
}
addSecondaryLink() { @action
this.nextObjectId = this.nextObjectId + 1; save() {
this.model.secondaryLinks.pushObject( this.transformedModel.id ? this.update() : this.create();
new SectionLink({ }
router: this.router,
objectId: this.nextObjectId, @action
segment: "secondary", delete() {
return this.dialog.yesNoConfirm({
message: I18n.t("sidebar.sections.custom.delete_confirm"),
didConfirm: () => {
return ajax(`/sidebar_sections/${this.transformedModel.id}`, {
type: "DELETE",
}) })
); .then(() => {
}, const newSidebarSections = this.currentUser.sidebar_sections.filter(
(section) => {
resetToDefault() { return section.id !== this.transformedModel.id;
return this.dialog.yesNoConfirm({ }
message: I18n.t("sidebar.sections.custom.reset_confirm"),
didConfirm: () => {
return ajax(`/sidebar_sections/reset/${this.model.id}`, {
type: "PUT",
})
.then((data) => {
this.currentUser.sidebar_sections.shiftObject();
this.currentUser.sidebar_sections.unshiftObject(
data["sidebar_section"]
);
this.send("closeModal");
})
.catch((e) =>
this.setProperties({
flashText: sanitize(extractError(e)),
flashClass: "error",
})
); );
},
});
},
save() { this.currentUser.set("sidebar_sections", newSidebarSections);
this.model.id ? this.update() : this.create(); this.closeModal();
},
delete() {
return this.dialog.yesNoConfirm({
message: I18n.t("sidebar.sections.custom.delete_confirm"),
didConfirm: () => {
return ajax(`/sidebar_sections/${this.model.id}`, {
type: "DELETE",
}) })
.then(() => { .catch((e) => {
const newSidebarSections = this.flash = sanitize(extractError(e));
this.currentUser.sidebar_sections.filter((section) => { this.flashType = "error";
return section.id !== this.model.id; });
}); },
});
this.currentUser.set("sidebar_sections", newSidebarSections); }
this.send("closeModal"); }
})
.catch((e) =>
this.setProperties({
flashText: sanitize(extractError(e)),
flashClass: "error",
})
);
},
});
},
},
});

View File

@ -1,16 +1,17 @@
import Component from "@glimmer/component"; import Component from "@glimmer/component";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { action } from "@ember/object"; import { action } from "@ember/object";
import showModal from "discourse/lib/show-modal"; import SidebarSectionForm from "discourse/components/modal/sidebar-section-form";
export default class SidebarFooter extends Component { export default class SidebarFooter extends Component {
@service capabilities; @service capabilities;
@service currentUser;
@service modal;
@service site; @service site;
@service siteSettings; @service siteSettings;
@service currentUser;
@action @action
addSection() { addSection() {
showModal("sidebar-section-form"); this.modal.show(SidebarSectionForm);
} }
} }

View File

@ -20,7 +20,7 @@ import {
customSectionLinks, customSectionLinks,
secondaryCustomSectionLinks, secondaryCustomSectionLinks,
} from "discourse/lib/sidebar/custom-community-section-links"; } from "discourse/lib/sidebar/custom-community-section-links";
import showModal from "discourse/lib/show-modal"; import SidebarSectionForm from "discourse/components/modal/sidebar-section-form";
const SPECIAL_LINKS_MAP = { const SPECIAL_LINKS_MAP = {
"/latest": EverythingSectionLink, "/latest": EverythingSectionLink,
@ -37,9 +37,10 @@ const SPECIAL_LINKS_MAP = {
export default class CommunitySection { export default class CommunitySection {
@service appEvents; @service appEvents;
@service currentUser; @service currentUser;
@service modal;
@service router; @service router;
@service topicTrackingState;
@service siteSettings; @service siteSettings;
@service topicTrackingState;
@tracked links; @tracked links;
@tracked moreLinks; @tracked moreLinks;
@ -173,7 +174,9 @@ export default class CommunitySection {
@action @action
editSection() { editSection() {
showModal("sidebar-section-form", { model: this.section }); return this.modal.show(SidebarSectionForm, {
model: this.section,
});
} }
@action @action

View File

@ -1,14 +1,15 @@
import I18n from "I18n"; import I18n from "I18n";
import showModal from "discourse/lib/show-modal";
import SectionLink from "discourse/lib/sidebar/section-link"; import SectionLink from "discourse/lib/sidebar/section-link";
import { tracked } from "@glimmer/tracking"; import { tracked } from "@glimmer/tracking";
import { setOwner } from "@ember/application"; import { setOwner } from "@ember/application";
import { inject as service } from "@ember/service"; import { inject as service } from "@ember/service";
import { bind } from "discourse-common/utils/decorators"; import { bind } from "discourse-common/utils/decorators";
import { ajax } from "discourse/lib/ajax"; import { ajax } from "discourse/lib/ajax";
import SidebarSectionForm from "discourse/components/modal/sidebar-section-form";
export default class Section { export default class Section {
@service currentUser; @service currentUser;
@service modal;
@service router; @service router;
@tracked dragCss; @tracked dragCss;
@ -40,7 +41,9 @@ export default class Section {
return [ return [
{ {
action: () => { action: () => {
return showModal("sidebar-section-form", { model: this.section }); return this.modal.show(SidebarSectionForm, {
model: this.section,
});
}, },
title: I18n.t("sidebar.sections.custom.edit"), title: I18n.t("sidebar.sections.custom.edit"),
}, },

View File

@ -1,115 +0,0 @@
{{#if this.flashText}}
<div id="modal-alert" role="alert" class="alert alert-{{this.flashClass}}">
{{this.flashText}}
</div>
{{/if}}
<DModalBody @title={{this.header}}>
<form class="form-horizontal">
<div class="input-group">
<label for="section-name">{{i18n
"sidebar.sections.custom.title.label"
}}</label>
<Input
name="section-name"
@type="text"
@value={{this.model.title}}
class={{this.model.titleCssClass}}
{{on "input" (action (mut this.model.title) value="target.value")}}
/>
{{#if this.model.invalidTitleMessage}}
<div class="title warning">
{{this.model.invalidTitleMessage}}
</div>
{{/if}}
</div>
<div class="row-wrapper header">
<div class="input-group link-icon">
<label>{{i18n "sidebar.sections.custom.links.icon.label"}}</label>
</div>
<div class="input-group link-name">
<label>{{i18n "sidebar.sections.custom.links.name.label"}}</label>
</div>
<div class="input-group link-url">
<label>{{i18n "sidebar.sections.custom.links.value.label"}}</label>
</div>
</div>
{{#each this.activeLinks as |link|}}
<Sidebar::SectionFormLink
@link={{link}}
@deleteLink={{this.deleteLink}}
@reorderCallback={{this.reorder}}
/>
{{/each}}
<DButton
@action={{action "addLink"}}
@class="btn-flat btn-text add-link"
@title="sidebar.sections.custom.links.add"
@icon="plus"
@label="sidebar.sections.custom.links.add"
@ariaLabel="sidebar.sections.custom.links.add"
/>
{{#if this.model.sectionType}}
<hr />
<h3>{{i18n "sidebar.sections.custom.more_menu"}}</h3>
{{#each this.activeSecondaryLinks as |link|}}
<Sidebar::SectionFormLink
@link={{link}}
@deleteLink={{this.deleteLink}}
@reorderCallback={{this.reorder}}
/>
{{/each}}
<DButton
@action={{action "addSecondaryLink"}}
@class="btn-flat btn-text add-link"
@title="sidebar.sections.custom.links.add"
@icon="plus"
@label="sidebar.sections.custom.links.add"
@ariaLabel="sidebar.sections.custom.links.add"
/>
{{/if}}
{{#if (and this.currentUser.staff (not this.model.sectionType))}}
<div class="row-wrapper mark-public-wrapper">
<label class="checkbox-label">
<Input
@type="checkbox"
@checked={{this.model.public}}
class="mark-public"
/>
{{i18n "sidebar.sections.custom.public"}}
</label>
</div>
{{/if}}
</form>
</DModalBody>
<div class="modal-footer">
<DButton
@id="save-section"
@action={{action "save"}}
@class="btn-primary"
@label="sidebar.sections.custom.save"
@ariaLabel="sidebar.sections.custom.save"
@disabled={{not this.model.valid}}
/>
{{#if this.canDelete}}
<DButton
@icon="trash-alt"
@id="delete-section"
@class="btn-danger delete"
@action={{action "delete"}}
@label="sidebar.sections.custom.delete"
@ariaLabel="sidebar.sections.custom.delete"
/>
{{/if}}
{{#if this.model.sectionType}}
<DButton
@action={{action "resetToDefault"}}
@class="btn-flat btn-text reset-link"
@icon="undo"
@title="sidebar.sections.custom.links.reset"
@label="sidebar.sections.custom.links.reset"
@ariaLabel="sidebar.sections.custom.links.reset"
/>
{{/if}}
</div>