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

View File

@ -1,16 +1,17 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
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 {
@service capabilities;
@service currentUser;
@service modal;
@service site;
@service siteSettings;
@service currentUser;
@action
addSection() {
showModal("sidebar-section-form");
this.modal.show(SidebarSectionForm);
}
}

View File

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

View File

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