mirror of
https://github.com/discourse/discourse.git
synced 2025-06-06 23:07:28 +08:00
FEATURE: modal for admins to edit Community section (#21668)
Allow admins to edit Community section. This includes drag and drop reorder, change names, delete and reset to default. Visual improvements introduced in edit community section modal are available in edit custom section form as well. For example: - drag and drop links to change their position; - smaller icon picker.
This commit is contained in:

committed by
GitHub

parent
7d9a823a55
commit
9f78ff5572
@ -7,51 +7,50 @@
|
|||||||
@class={{this.section.dragCss}}
|
@class={{this.section.dragCss}}
|
||||||
>
|
>
|
||||||
{{#each this.section.links as |link|}}
|
{{#each this.section.links as |link|}}
|
||||||
{{#if link.shouldDisplay}}
|
{{#if link.external}}
|
||||||
{{#if link.external}}
|
<Sidebar::SectionLink
|
||||||
<Sidebar::SectionLink
|
@shouldDisplay={{link.shouldDisplay}}
|
||||||
@linkName={{link.name}}
|
@linkName={{link.name}}
|
||||||
@content={{replace-emoji link.text}}
|
@content={{replace-emoji link.text}}
|
||||||
@prefixType="icon"
|
@prefixType="icon"
|
||||||
@prefixValue={{link.prefixValue}}
|
@prefixValue={{link.prefixValue}}
|
||||||
@href={{link.value}}
|
@href={{link.value}}
|
||||||
@class={{link.linkDragCss}}
|
@class={{link.linkDragCss}}
|
||||||
{{draggable
|
{{draggable
|
||||||
|
didStartDrag=link.didStartDrag
|
||||||
|
didEndDrag=link.didEndDrag
|
||||||
|
dragMove=link.dragMove
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
{{else}}
|
||||||
|
<Sidebar::SectionLink
|
||||||
|
@shouldDisplay={{link.shouldDisplay}}
|
||||||
|
@href={{link.href}}
|
||||||
|
@title={{link.title}}
|
||||||
|
@linkName={{link.name}}
|
||||||
|
@route={{link.route}}
|
||||||
|
@model={{link.model}}
|
||||||
|
@models={{link.models}}
|
||||||
|
@query={{link.query}}
|
||||||
|
@content={{replace-emoji link.text}}
|
||||||
|
@badgeText={{link.badgeText}}
|
||||||
|
@prefixType="icon"
|
||||||
|
@prefixValue={{link.prefixValue}}
|
||||||
|
@suffixCSSClass={{link.suffixCSSClass}}
|
||||||
|
@suffixValue={{link.suffixValue}}
|
||||||
|
@suffixType={{link.suffixType}}
|
||||||
|
@currentWhen={{link.currentWhen}}
|
||||||
|
@class={{link.linkDragCss}}
|
||||||
|
{{(if
|
||||||
|
link.didStartDrag
|
||||||
|
(modifier
|
||||||
|
"draggable"
|
||||||
didStartDrag=link.didStartDrag
|
didStartDrag=link.didStartDrag
|
||||||
didEndDrag=link.didEndDrag
|
didEndDrag=link.didEndDrag
|
||||||
dragMove=link.dragMove
|
dragMove=link.dragMove
|
||||||
}}
|
)
|
||||||
/>
|
)}}
|
||||||
{{else}}
|
/>
|
||||||
<Sidebar::SectionLink
|
|
||||||
@shouldDisplay={{link.shouldDisplay}}
|
|
||||||
@href={{link.href}}
|
|
||||||
@title={{link.title}}
|
|
||||||
@linkName={{link.name}}
|
|
||||||
@route={{link.route}}
|
|
||||||
@model={{link.model}}
|
|
||||||
@models={{link.models}}
|
|
||||||
@query={{link.query}}
|
|
||||||
@content={{replace-emoji link.text}}
|
|
||||||
@badgeText={{link.badgeText}}
|
|
||||||
@prefixType="icon"
|
|
||||||
@prefixValue={{link.prefixValue}}
|
|
||||||
@suffixCSSClass={{link.suffixCSSClass}}
|
|
||||||
@suffixValue={{link.suffixValue}}
|
|
||||||
@suffixType={{link.suffixType}}
|
|
||||||
@currentWhen={{link.currentWhen}}
|
|
||||||
@class={{link.linkDragCss}}
|
|
||||||
{{(if
|
|
||||||
link.didStartDrag
|
|
||||||
(modifier
|
|
||||||
"draggable"
|
|
||||||
didStartDrag=link.didStartDrag
|
|
||||||
didEndDrag=link.didEndDrag
|
|
||||||
dragMove=link.dragMove
|
|
||||||
)
|
|
||||||
)}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
|
@ -1,15 +1,29 @@
|
|||||||
<Sidebar::SectionLink
|
{{#if @sectionLink.external}}
|
||||||
@shouldDisplay={{@sectionLink.shouldDisplay}}
|
<Sidebar::SectionLink
|
||||||
@linkName={{@sectionLink.name}}
|
@shouldDisplay={{@sectionLink.shouldDisplay}}
|
||||||
@route={{@sectionLink.route}}
|
@linkName={{@sectionLink.name}}
|
||||||
@href={{@sectionLink.href}}
|
@content={{replace-emoji @sectionLink.text}}
|
||||||
@query={{@sectionLink.query}}
|
@prefixType="icon"
|
||||||
@title={{@sectionLink.title}}
|
@prefixValue={{@sectionLink.prefixValue}}
|
||||||
@content={{@sectionLink.text}}
|
@href={{@sectionLink.value}}
|
||||||
@currentWhen={{@sectionLink.currentWhen}}
|
/>
|
||||||
@badgeText={{@sectionLink.badgeText}}
|
{{else}}
|
||||||
@model={{@sectionLink.model}}
|
<Sidebar::SectionLink
|
||||||
@models={{@sectionLink.models}}
|
@shouldDisplay={{@sectionLink.shouldDisplay}}
|
||||||
@prefixType={{@sectionLink.prefixType}}
|
@href={{@sectionLink.href}}
|
||||||
@prefixValue={{@sectionLink.prefixValue}}
|
@title={{@sectionLink.title}}
|
||||||
/>
|
@linkName={{@sectionLink.name}}
|
||||||
|
@route={{@sectionLink.route}}
|
||||||
|
@model={{@sectionLink.model}}
|
||||||
|
@models={{@sectionLink.models}}
|
||||||
|
@query={{@sectionLink.query}}
|
||||||
|
@content={{replace-emoji @sectionLink.text}}
|
||||||
|
@badgeText={{@sectionLink.badgeText}}
|
||||||
|
@prefixType="icon"
|
||||||
|
@prefixValue={{@sectionLink.prefixValue}}
|
||||||
|
@suffixCSSClass={{@sectionLink.suffixCSSClass}}
|
||||||
|
@suffixValue={{@sectionLink.suffixValue}}
|
||||||
|
@suffixType={{@sectionLink.suffixType}}
|
||||||
|
@currentWhen={{@sectionLink.currentWhen}}
|
||||||
|
/>
|
||||||
|
{{/if}}
|
@ -0,0 +1,72 @@
|
|||||||
|
<div
|
||||||
|
class={{concat-class
|
||||||
|
"sidebar-section-form-link"
|
||||||
|
"row-wrapper"
|
||||||
|
this.dragCssClass
|
||||||
|
}}
|
||||||
|
draggable="true"
|
||||||
|
{{on "dragstart" this.dragHasStarted}}
|
||||||
|
{{on "dragover" this.dragOver}}
|
||||||
|
{{on "dragenter" this.dragEnter}}
|
||||||
|
{{on "dragleave" this.dragLeave}}
|
||||||
|
{{on "dragend" this.dragEnd}}
|
||||||
|
{{on "drop" this.dropItem}}
|
||||||
|
>
|
||||||
|
<div class="draggable" data-link-name={{@link.name}}>
|
||||||
|
{{d-icon "grip-lines"}}
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<IconPicker
|
||||||
|
@name="icon"
|
||||||
|
@value={{@link.icon}}
|
||||||
|
@options={{hash
|
||||||
|
maximum=1
|
||||||
|
caretDownIcon="caret-down"
|
||||||
|
caretUpIcon="caret-up"
|
||||||
|
icons=@link.icon
|
||||||
|
}}
|
||||||
|
class={{@link.iconCssClass}}
|
||||||
|
@onlyAvailable={{true}}
|
||||||
|
@onChange={{action (mut @link.icon)}}
|
||||||
|
/>
|
||||||
|
{{#if @link.invalidIconMessage}}
|
||||||
|
<div class="icon warning">
|
||||||
|
{{@link.invalidIconMessage}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<Input
|
||||||
|
name="link-name"
|
||||||
|
@type="text"
|
||||||
|
@value={{@link.name}}
|
||||||
|
class={{@link.nameCssClass}}
|
||||||
|
{{on "input" (action (mut @link.name) value="target.value")}}
|
||||||
|
/>
|
||||||
|
{{#if @link.invalidNameMessage}}
|
||||||
|
<div class="name warning">
|
||||||
|
{{@link.invalidNameMessage}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<div class="input-group">
|
||||||
|
<Input
|
||||||
|
name="link-url"
|
||||||
|
@type="text"
|
||||||
|
@value={{@link.value}}
|
||||||
|
class={{@link.valueCssClass}}
|
||||||
|
{{on "input" (action (mut @link.value) value="target.value")}}
|
||||||
|
/>
|
||||||
|
{{#if @link.invalidValueMessage}}
|
||||||
|
<div class="value warning">
|
||||||
|
{{@link.invalidValueMessage}}
|
||||||
|
</div>
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
|
<DButton
|
||||||
|
@icon="trash-alt"
|
||||||
|
@action={{action @deleteLink @link}}
|
||||||
|
@class="btn-flat delete-link"
|
||||||
|
@title="sidebar.sections.custom.links.delete"
|
||||||
|
/>
|
||||||
|
</div>
|
@ -0,0 +1,68 @@
|
|||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { action } from "@ember/object";
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
|
||||||
|
export default class SectionFormLink extends Component {
|
||||||
|
@tracked dragCssClass;
|
||||||
|
|
||||||
|
dragCount = 0;
|
||||||
|
|
||||||
|
isAboveElement(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
const target = event.currentTarget;
|
||||||
|
const domRect = target.getBoundingClientRect();
|
||||||
|
return event.offsetY < domRect.height / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
dragHasStarted(event) {
|
||||||
|
event.dataTransfer.effectAllowed = "move";
|
||||||
|
event.dataTransfer.setData("linkId", this.args.link.objectId);
|
||||||
|
this.dragCssClass = "dragging";
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
dragOver(event) {
|
||||||
|
event.preventDefault();
|
||||||
|
if (!this.dragCssClass) {
|
||||||
|
if (this.isAboveElement(event)) {
|
||||||
|
this.dragCssClass = "drag-above";
|
||||||
|
} else {
|
||||||
|
this.dragCssClass = "drag-below";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@action
|
||||||
|
dragEnter() {
|
||||||
|
this.dragCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
dragLeave() {
|
||||||
|
this.dragCount--;
|
||||||
|
if (
|
||||||
|
this.dragCount === 0 &&
|
||||||
|
(this.dragCssClass === "drag-above" || this.dragCssClass === "drag-below")
|
||||||
|
) {
|
||||||
|
this.dragCssClass = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
dropItem(event) {
|
||||||
|
event.stopPropagation();
|
||||||
|
this.dragCounter = 0;
|
||||||
|
this.args.reorderCallback(
|
||||||
|
parseInt(event.dataTransfer.getData("linkId"), 10),
|
||||||
|
this.args.link,
|
||||||
|
this.isAboveElement(event)
|
||||||
|
);
|
||||||
|
this.dragCssClass = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
dragEnd() {
|
||||||
|
this.dragCounter = 0;
|
||||||
|
this.dragCssClass = null;
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import { sanitize } from "discourse/lib/text";
|
|||||||
import { tracked } from "@glimmer/tracking";
|
import { 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";
|
||||||
|
|
||||||
const FULL_RELOAD_LINKS_REGEX = [
|
const FULL_RELOAD_LINKS_REGEX = [
|
||||||
/^\/my\/[a-z_\-\/]+$/,
|
/^\/my\/[a-z_\-\/]+$/,
|
||||||
@ -19,17 +20,30 @@ const FULL_RELOAD_LINKS_REGEX = [
|
|||||||
class Section {
|
class Section {
|
||||||
@tracked title;
|
@tracked title;
|
||||||
@tracked links;
|
@tracked links;
|
||||||
|
@tracked secondaryLinks;
|
||||||
|
|
||||||
constructor({ title, links, id, publicSection }) {
|
constructor({
|
||||||
|
title,
|
||||||
|
links,
|
||||||
|
secondaryLinks,
|
||||||
|
id,
|
||||||
|
publicSection,
|
||||||
|
sectionType,
|
||||||
|
}) {
|
||||||
this.title = title;
|
this.title = title;
|
||||||
this.public = publicSection;
|
this.public = publicSection;
|
||||||
|
this.sectionType = sectionType;
|
||||||
this.links = links;
|
this.links = links;
|
||||||
|
this.secondaryLinks = secondaryLinks;
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
get valid() {
|
get valid() {
|
||||||
|
const allLinks = this.links
|
||||||
|
.filter((link) => !link._destroy)
|
||||||
|
.concat(this.secondaryLinks?.filter((link) => !link._destroy) || []);
|
||||||
const validLinks =
|
const validLinks =
|
||||||
this.links.length > 0 && this.links.every((link) => link.valid);
|
allLinks.length > 0 && allLinks.every((link) => link.valid);
|
||||||
return this.validTitle && validLinks;
|
return this.validTitle && validLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +84,7 @@ class SectionLink {
|
|||||||
@tracked value;
|
@tracked value;
|
||||||
@tracked _destroy;
|
@tracked _destroy;
|
||||||
|
|
||||||
constructor({ router, icon, name, value, id }) {
|
constructor({ router, icon, name, value, id, objectId, segment }) {
|
||||||
this.router = router;
|
this.router = router;
|
||||||
this.icon = icon || "link";
|
this.icon = icon || "link";
|
||||||
this.name = name;
|
this.name = name;
|
||||||
@ -78,6 +92,8 @@ class SectionLink {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
this.httpHost = "http://" + window.location.host;
|
this.httpHost = "http://" + window.location.host;
|
||||||
this.httpsHost = "https://" + window.location.host;
|
this.httpsHost = "https://" + window.location.host;
|
||||||
|
this.objectId = objectId;
|
||||||
|
this.segment = segment;
|
||||||
}
|
}
|
||||||
|
|
||||||
get path() {
|
get path() {
|
||||||
@ -165,6 +181,10 @@ class SectionLink {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get isPrimary() {
|
||||||
|
return this.segment === "primary";
|
||||||
|
}
|
||||||
|
|
||||||
get #blankIcon() {
|
get #blankIcon() {
|
||||||
return isEmpty(this.icon);
|
return isEmpty(this.icon);
|
||||||
}
|
}
|
||||||
@ -221,6 +241,7 @@ export default Controller.extend(ModalFunctionality, {
|
|||||||
flashText: null,
|
flashText: null,
|
||||||
flashClass: null,
|
flashClass: null,
|
||||||
});
|
});
|
||||||
|
this.nextObjectId = 0;
|
||||||
this.model = this.initModel();
|
this.model = this.initModel();
|
||||||
},
|
},
|
||||||
|
|
||||||
@ -233,27 +254,48 @@ export default Controller.extend(ModalFunctionality, {
|
|||||||
return new Section({
|
return new Section({
|
||||||
title: this.model.title,
|
title: this.model.title,
|
||||||
publicSection: this.model.public,
|
publicSection: this.model.public,
|
||||||
links: A(
|
sectionType: this.model.section_type,
|
||||||
this.model.links.map(
|
links: this.model.links.reduce((acc, link) => {
|
||||||
(link) =>
|
if (link.segment === "primary") {
|
||||||
new SectionLink({
|
this.nextObjectId++;
|
||||||
router: this.router,
|
acc.push(this.initLink(link));
|
||||||
icon: link.icon,
|
}
|
||||||
name: link.name,
|
return acc;
|
||||||
value: link.value,
|
}, A()),
|
||||||
id: link.id,
|
secondaryLinks: this.model.links.reduce((acc, link) => {
|
||||||
})
|
if (link.segment === "secondary") {
|
||||||
)
|
this.nextObjectId++;
|
||||||
),
|
acc.push(this.initLink(link));
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, A()),
|
||||||
id: this.model.id,
|
id: this.model.id,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
return new Section({
|
return new Section({
|
||||||
links: A([new SectionLink({ router: this.router })]),
|
links: A([
|
||||||
|
new SectionLink({
|
||||||
|
router: this.router,
|
||||||
|
objectId: this.nextObjectId,
|
||||||
|
segment: "primary",
|
||||||
|
}),
|
||||||
|
]),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
initLink(link) {
|
||||||
|
return new SectionLink({
|
||||||
|
router: this.router,
|
||||||
|
icon: link.icon,
|
||||||
|
name: link.name,
|
||||||
|
value: link.value,
|
||||||
|
id: link.id,
|
||||||
|
objectId: this.nextObjectId,
|
||||||
|
segment: link.segment,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
create() {
|
create() {
|
||||||
return ajax(`/sidebar_sections`, {
|
return ajax(`/sidebar_sections`, {
|
||||||
type: "POST",
|
type: "POST",
|
||||||
@ -294,15 +336,18 @@ export default Controller.extend(ModalFunctionality, {
|
|||||||
data: JSON.stringify({
|
data: JSON.stringify({
|
||||||
title: this.model.title,
|
title: this.model.title,
|
||||||
public: this.model.public,
|
public: this.model.public,
|
||||||
links: this.model.links.map((link) => {
|
links: this.model.links
|
||||||
return {
|
.concat(this.model?.secondaryLinks || [])
|
||||||
id: link.id,
|
.map((link) => {
|
||||||
icon: link.icon,
|
return {
|
||||||
name: link.name,
|
id: link.id,
|
||||||
value: link.path,
|
icon: link.icon,
|
||||||
_destroy: link._destroy,
|
name: link.name,
|
||||||
};
|
value: link.path,
|
||||||
}),
|
segment: link.segment,
|
||||||
|
_destroy: link._destroy,
|
||||||
|
};
|
||||||
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
})
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
@ -329,23 +374,112 @@ export default Controller.extend(ModalFunctionality, {
|
|||||||
return this.model.links.filter((link) => !link._destroy);
|
return this.model.links.filter((link) => !link._destroy);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
get activeSecondaryLinks() {
|
||||||
|
return this.model.secondaryLinks?.filter((link) => !link._destroy);
|
||||||
|
},
|
||||||
|
|
||||||
get header() {
|
get header() {
|
||||||
return this.model.id
|
return this.model.id
|
||||||
? "sidebar.sections.custom.edit"
|
? "sidebar.sections.custom.edit"
|
||||||
: "sidebar.sections.custom.add";
|
: "sidebar.sections.custom.add";
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@bind
|
||||||
|
reorder(linkFromId, linkTo, above) {
|
||||||
|
if (linkFromId === linkTo.objectId) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let linkFrom = this.model.links.find(
|
||||||
|
(link) => link.objectId === linkFromId
|
||||||
|
);
|
||||||
|
if (!linkFrom) {
|
||||||
|
linkFrom = this.model.secondaryLinks.find(
|
||||||
|
(link) => link.objectId === linkFromId
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkFrom.isPrimary) {
|
||||||
|
this.model.links.removeObject(linkFrom);
|
||||||
|
} else {
|
||||||
|
this.model.secondaryLinks?.removeObject(linkFrom);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (linkTo.isPrimary) {
|
||||||
|
const toPosition = this.model.links.indexOf(linkTo);
|
||||||
|
linkFrom.segment = "primary";
|
||||||
|
this.model.links.insertAt(above ? toPosition : toPosition + 1, linkFrom);
|
||||||
|
} else {
|
||||||
|
linkFrom.segment = "secondary";
|
||||||
|
const toPosition = this.model.secondaryLinks.indexOf(linkTo);
|
||||||
|
this.model.secondaryLinks.insertAt(
|
||||||
|
above ? toPosition : toPosition + 1,
|
||||||
|
linkFrom
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
get canDelete() {
|
||||||
|
return this.model.id && !this.model.sectionType;
|
||||||
|
},
|
||||||
|
|
||||||
|
@bind
|
||||||
|
deleteLink(link) {
|
||||||
|
if (link.id) {
|
||||||
|
link._destroy = "1";
|
||||||
|
} else {
|
||||||
|
if (link.isPrimary) {
|
||||||
|
this.model.links.removeObject(link);
|
||||||
|
} else {
|
||||||
|
this.model.secondaryLinks.removeObject(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
addLink() {
|
addLink() {
|
||||||
this.model.links.pushObject(new SectionLink({ router: this.router }));
|
this.nextObjectId = this.nextObjectId + 1;
|
||||||
|
this.model.links.pushObject(
|
||||||
|
new SectionLink({
|
||||||
|
router: this.router,
|
||||||
|
objectId: this.nextObjectId,
|
||||||
|
segment: "primary",
|
||||||
|
})
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
deleteLink(link) {
|
addSecondaryLink() {
|
||||||
if (link.id) {
|
this.nextObjectId = this.nextObjectId + 1;
|
||||||
link._destroy = "1";
|
this.model.secondaryLinks.pushObject(
|
||||||
} else {
|
new SectionLink({
|
||||||
this.model.links.removeObject(link);
|
router: this.router,
|
||||||
}
|
objectId: this.nextObjectId,
|
||||||
|
segment: "secondary",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
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",
|
||||||
|
})
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
save() {
|
save() {
|
||||||
|
@ -9,6 +9,8 @@ export default class BaseCommunitySectionLink {
|
|||||||
router,
|
router,
|
||||||
siteSettings,
|
siteSettings,
|
||||||
inMoreDrawer,
|
inMoreDrawer,
|
||||||
|
overridenName,
|
||||||
|
overridenIcon,
|
||||||
} = {}) {
|
} = {}) {
|
||||||
this.router = router;
|
this.router = router;
|
||||||
this.topicTrackingState = topicTrackingState;
|
this.topicTrackingState = topicTrackingState;
|
||||||
@ -16,6 +18,8 @@ export default class BaseCommunitySectionLink {
|
|||||||
this.appEvents = appEvents;
|
this.appEvents = appEvents;
|
||||||
this.siteSettings = siteSettings;
|
this.siteSettings = siteSettings;
|
||||||
this.inMoreDrawer = inMoreDrawer;
|
this.inMoreDrawer = inMoreDrawer;
|
||||||
|
this.overridenName = overridenName;
|
||||||
|
this.overridenIcon = overridenIcon;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -105,10 +109,17 @@ export default class BaseCommunitySectionLink {
|
|||||||
/**
|
/**
|
||||||
* @returns {string} The name of the fontawesome icon to be displayed before the link. Defaults to "link".
|
* @returns {string} The name of the fontawesome icon to be displayed before the link. Defaults to "link".
|
||||||
*/
|
*/
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
return "link";
|
return "link";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {string} The name of the fontawesome icon to be displayed before the link.
|
||||||
|
*/
|
||||||
|
get prefixValue() {
|
||||||
|
return this.overridenIcon || this.defaultPrefixValue;
|
||||||
|
}
|
||||||
|
|
||||||
_notImplemented() {
|
_notImplemented() {
|
||||||
throw "not implemented";
|
throw "not implemented";
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,13 @@ export default class AboutSectionLink extends BaseSectionLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return I18n.t("sidebar.sections.community.links.about.content");
|
return I18n.t(
|
||||||
|
`sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`,
|
||||||
|
{ defaultValue: this.overridenName }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
return "info-circle";
|
return "info-circle";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,17 @@ export default class BadgesSectionLink extends BaseSectionLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return I18n.t("sidebar.sections.community.links.badges.content");
|
return I18n.t(
|
||||||
|
`sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`,
|
||||||
|
{ defaultValue: this.overridenName }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get shouldDisplay() {
|
get shouldDisplay() {
|
||||||
return this.siteSettings.enable_badges;
|
return this.siteSettings.enable_badges;
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
return "certificate";
|
return "certificate";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,10 @@ export default class EverythingSectionLink extends BaseSectionLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return I18n.t("sidebar.sections.community.links.everything.content");
|
return I18n.t(
|
||||||
|
`sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`,
|
||||||
|
{ defaultValue: this.overridenName }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get currentWhen() {
|
get currentWhen() {
|
||||||
@ -92,7 +95,7 @@ export default class EverythingSectionLink extends BaseSectionLink {
|
|||||||
return "discovery.latest";
|
return "discovery.latest";
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
return "layer-group";
|
return "layer-group";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,10 +20,13 @@ export default class FAQSectionLink extends BaseSectionLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return I18n.t("sidebar.sections.community.links.faq.content");
|
return I18n.t(
|
||||||
|
`sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`,
|
||||||
|
{ defaultValue: this.overridenName }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
return "question-circle";
|
return "question-circle";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,14 +16,17 @@ export default class GroupsSectionLink extends BaseSectionLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return I18n.t("sidebar.sections.community.links.groups.content");
|
return I18n.t(
|
||||||
|
`sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`,
|
||||||
|
{ defaultValue: this.overridenName }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get shouldDisplay() {
|
get shouldDisplay() {
|
||||||
return this.siteSettings.enable_group_directory;
|
return this.siteSettings.enable_group_directory;
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
return "user-friends";
|
return "user-friends";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,10 @@ export default class UsersSectionLink extends BaseSectionLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return I18n.t("sidebar.sections.community.links.users.content");
|
return I18n.t(
|
||||||
|
`sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`,
|
||||||
|
{ defaultValue: this.overridenName }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get shouldDisplay() {
|
get shouldDisplay() {
|
||||||
@ -26,7 +29,7 @@ export default class UsersSectionLink extends BaseSectionLink {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
return "users";
|
return "users";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,6 +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";
|
||||||
|
|
||||||
const LINKS_IN_BOTH_SEGMENTS = ["/review"];
|
const LINKS_IN_BOTH_SEGMENTS = ["/review"];
|
||||||
|
|
||||||
@ -111,13 +112,23 @@ export default class CommunitySection {
|
|||||||
const sectionLinkClass = SPECIAL_LINKS_MAP[link.value];
|
const sectionLinkClass = SPECIAL_LINKS_MAP[link.value];
|
||||||
|
|
||||||
if (sectionLinkClass) {
|
if (sectionLinkClass) {
|
||||||
return this.#initializeSectionLink(sectionLinkClass, inMoreDrawer);
|
return this.#initializeSectionLink(
|
||||||
|
sectionLinkClass,
|
||||||
|
inMoreDrawer,
|
||||||
|
link.name,
|
||||||
|
link.scon
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
return new SectionLink(link, this, this.router);
|
return new SectionLink(link, this, this.router);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#initializeSectionLink(sectionLinkClass, inMoreDrawer) {
|
#initializeSectionLink(
|
||||||
|
sectionLinkClass,
|
||||||
|
inMoreDrawer,
|
||||||
|
overridenName,
|
||||||
|
overridenIcon
|
||||||
|
) {
|
||||||
if (this.router.isDestroying) {
|
if (this.router.isDestroying) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -128,28 +139,48 @@ export default class CommunitySection {
|
|||||||
router: this.router,
|
router: this.router,
|
||||||
siteSettings: this.siteSettings,
|
siteSettings: this.siteSettings,
|
||||||
inMoreDrawer,
|
inMoreDrawer,
|
||||||
|
overridenName,
|
||||||
|
overridenIcon,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
get decoratedTitle() {
|
get decoratedTitle() {
|
||||||
return I18n.t(
|
return I18n.t(
|
||||||
`sidebar.sections.${this.section.title.toLowerCase()}.header_link_text`
|
`sidebar.sections.${this.section.title.toLowerCase()}.header_link_text`,
|
||||||
|
{ defaultValue: this.section.title }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get headerActions() {
|
get headerActions() {
|
||||||
|
if (this.currentUser?.admin) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
action: this.editSection,
|
||||||
|
title: I18n.t(
|
||||||
|
"sidebar.sections.community.header_action_edit_section_title"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
}
|
||||||
if (this.currentUser) {
|
if (this.currentUser) {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
action: this.composeTopic,
|
action: this.composeTopic,
|
||||||
title: I18n.t("sidebar.sections.community.header_action_title"),
|
title: I18n.t(
|
||||||
|
"sidebar.sections.community.header_action_create_topic_title"
|
||||||
|
),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get headerActionIcon() {
|
get headerActionIcon() {
|
||||||
return "plus";
|
return this.currentUser?.admin ? "pencil-alt" : "plus";
|
||||||
|
}
|
||||||
|
|
||||||
|
@action
|
||||||
|
editSection() {
|
||||||
|
showModal("sidebar-section-form", { model: this.section });
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -16,14 +16,17 @@ export default class AdminSectionLink extends BaseSectionLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return I18n.t("sidebar.sections.community.links.admin.content");
|
return I18n.t(
|
||||||
|
`sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`,
|
||||||
|
{ defaultValue: this.overridenName }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get shouldDisplay() {
|
get shouldDisplay() {
|
||||||
return this.currentUser?.staff;
|
return !!this.currentUser?.staff;
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
return "wrench";
|
return "wrench";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -71,7 +71,12 @@ export default class MyPostsSectionLink extends BaseSectionLink {
|
|||||||
if (this._hasDraft && this.currentUser?.new_new_view_enabled) {
|
if (this._hasDraft && this.currentUser?.new_new_view_enabled) {
|
||||||
return I18n.t("sidebar.sections.community.links.my_posts.content_drafts");
|
return I18n.t("sidebar.sections.community.links.my_posts.content_drafts");
|
||||||
} else {
|
} else {
|
||||||
return I18n.t("sidebar.sections.community.links.my_posts.content");
|
return I18n.t(
|
||||||
|
`sidebar.sections.community.links.${this.overridenName
|
||||||
|
.toLowerCase()
|
||||||
|
.replace(" ", "/")}.content`,
|
||||||
|
{ defaultValue: this.overridenName }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +95,7 @@ export default class MyPostsSectionLink extends BaseSectionLink {
|
|||||||
return this.draftCount > 0;
|
return this.draftCount > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
if (this._hasDraft && this.currentUser?.new_new_view_enabled) {
|
if (this._hasDraft && this.currentUser?.new_new_view_enabled) {
|
||||||
return "pencil-alt";
|
return "pencil-alt";
|
||||||
}
|
}
|
||||||
|
@ -53,7 +53,10 @@ export default class ReviewSectionLink extends BaseSectionLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
get text() {
|
get text() {
|
||||||
return I18n.t("sidebar.sections.community.links.review.content");
|
return I18n.t(
|
||||||
|
`sidebar.sections.community.links.${this.overridenName.toLowerCase()}.content`,
|
||||||
|
{ defaultValue: this.overridenName }
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get shouldDisplay() {
|
get shouldDisplay() {
|
||||||
@ -70,7 +73,7 @@ export default class ReviewSectionLink extends BaseSectionLink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get prefixValue() {
|
get defaultPrefixValue() {
|
||||||
return "flag";
|
return "flag";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,67 +22,23 @@
|
|||||||
</div>
|
</div>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
{{#each this.activeLinks as |link|}}
|
<div class="row-wrapper header">
|
||||||
<div class="row-wrapper">
|
<div class="input-group link-icon">
|
||||||
<div class="input-group">
|
<label>{{i18n "sidebar.sections.custom.links.icon.label"}}</label>
|
||||||
<label for="link-name">{{i18n
|
|
||||||
"sidebar.sections.custom.links.icon.label"
|
|
||||||
}}</label>
|
|
||||||
<IconPicker
|
|
||||||
@name="icon"
|
|
||||||
@value={{link.icon}}
|
|
||||||
@options={{hash maximum=1}}
|
|
||||||
class={{link.iconCssClass}}
|
|
||||||
@onlyAvailable={{true}}
|
|
||||||
@onChange={{action (mut link.icon)}}
|
|
||||||
/>
|
|
||||||
{{#if link.invalidIconMessage}}
|
|
||||||
<div class="icon warning">
|
|
||||||
{{link.invalidIconMessage}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="link-name">{{i18n
|
|
||||||
"sidebar.sections.custom.links.name.label"
|
|
||||||
}}</label>
|
|
||||||
<Input
|
|
||||||
name="link-name"
|
|
||||||
@type="text"
|
|
||||||
@value={{link.name}}
|
|
||||||
class={{link.nameCssClass}}
|
|
||||||
{{on "input" (action (mut link.name) value="target.value")}}
|
|
||||||
/>
|
|
||||||
{{#if link.invalidNameMessage}}
|
|
||||||
<div class="name warning">
|
|
||||||
{{link.invalidNameMessage}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<div class="input-group">
|
|
||||||
<label for="link-url">{{i18n
|
|
||||||
"sidebar.sections.custom.links.value.label"
|
|
||||||
}}</label>
|
|
||||||
<Input
|
|
||||||
name="link-url"
|
|
||||||
@type="text"
|
|
||||||
@value={{link.value}}
|
|
||||||
class={{link.valueCssClass}}
|
|
||||||
{{on "input" (action (mut link.value) value="target.value")}}
|
|
||||||
/>
|
|
||||||
{{#if link.invalidValueMessage}}
|
|
||||||
<div class="value warning">
|
|
||||||
{{link.invalidValueMessage}}
|
|
||||||
</div>
|
|
||||||
{{/if}}
|
|
||||||
</div>
|
|
||||||
<DButton
|
|
||||||
@icon="trash-alt"
|
|
||||||
@action={{action "deleteLink" link}}
|
|
||||||
@class="btn-flat delete-link"
|
|
||||||
@title="sidebar.sections.custom.links.delete"
|
|
||||||
/>
|
|
||||||
</div>
|
</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}}
|
{{/each}}
|
||||||
<DButton
|
<DButton
|
||||||
@action={{action "addLink"}}
|
@action={{action "addLink"}}
|
||||||
@ -90,9 +46,40 @@
|
|||||||
@title="sidebar.sections.custom.links.add"
|
@title="sidebar.sections.custom.links.add"
|
||||||
@icon="plus"
|
@icon="plus"
|
||||||
@label="sidebar.sections.custom.links.add"
|
@label="sidebar.sections.custom.links.add"
|
||||||
|
@ariaLabel="sidebar.sections.custom.links.add"
|
||||||
/>
|
/>
|
||||||
{{#if this.currentUser.staff}}
|
{{#if this.model.sectionType}}
|
||||||
<div class="row-wrapper">
|
<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 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}}
|
||||||
|
{{/if}}
|
||||||
|
{{#if (and this.currentUser.staff (not this.model.sectionType))}}
|
||||||
|
<div class="row-wrapper mark-public-wrapper">
|
||||||
<label class="checkbox-label">
|
<label class="checkbox-label">
|
||||||
<Input
|
<Input
|
||||||
@type="checkbox"
|
@type="checkbox"
|
||||||
@ -112,15 +99,17 @@
|
|||||||
@action={{action "save"}}
|
@action={{action "save"}}
|
||||||
@class="btn-primary"
|
@class="btn-primary"
|
||||||
@label="sidebar.sections.custom.save"
|
@label="sidebar.sections.custom.save"
|
||||||
|
@ariaLabel="sidebar.sections.custom.save"
|
||||||
@disabled={{not this.model.valid}}
|
@disabled={{not this.model.valid}}
|
||||||
/>
|
/>
|
||||||
{{#if this.model.id}}
|
{{#if this.canDelete}}
|
||||||
<DButton
|
<DButton
|
||||||
@icon="trash-alt"
|
@icon="trash-alt"
|
||||||
@id="delete-section"
|
@id="delete-section"
|
||||||
@class="btn-danger delete"
|
@class="btn-danger delete"
|
||||||
@action={{action "delete"}}
|
@action={{action "delete"}}
|
||||||
@label="sidebar.sections.custom.delete"
|
@label="sidebar.sections.custom.delete"
|
||||||
|
@ariaLabel="sidebar.sections.custom.delete"
|
||||||
/>
|
/>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
@ -25,6 +25,7 @@ acceptance("Sidebar - Logged on user - Community Section", function (needs) {
|
|||||||
tracked_tags: ["tag1"],
|
tracked_tags: ["tag1"],
|
||||||
watched_tags: ["tag2"],
|
watched_tags: ["tag2"],
|
||||||
watching_first_post_tags: ["tag3"],
|
watching_first_post_tags: ["tag3"],
|
||||||
|
admin: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
needs.settings({
|
needs.settings({
|
||||||
|
@ -75,7 +75,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 331,
|
id: 331,
|
||||||
name: "Info",
|
name: "About",
|
||||||
value: "/about",
|
value: "/about",
|
||||||
icon: "info-circle",
|
icon: "info-circle",
|
||||||
external: false,
|
external: false,
|
||||||
|
@ -723,7 +723,7 @@ export default {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 331,
|
id: 331,
|
||||||
name: "Info",
|
name: "About",
|
||||||
value: "/about",
|
value: "/about",
|
||||||
icon: "info-circle",
|
icon: "info-circle",
|
||||||
external: false,
|
external: false,
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
.sidebar-section-link-prefix.icon {
|
.sidebar-section-link-prefix.icon {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
}
|
}
|
||||||
|
.sidebar-section[data-section-name="community"]
|
||||||
|
.sidebar-section-link-prefix.icon {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
-webkit-touch-callout: none !important;
|
-webkit-touch-callout: none !important;
|
||||||
|
@ -106,8 +106,21 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.sidebar-section-form-modal {
|
.sidebar-section-form-modal {
|
||||||
|
.draggable {
|
||||||
|
cursor: move;
|
||||||
|
align-self: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
-webkit-user-drag: element;
|
||||||
|
-khtml-user-drag: element;
|
||||||
|
-moz-user-drag: element;
|
||||||
|
-o-user-drag: element;
|
||||||
|
user-drag: element;
|
||||||
|
}
|
||||||
|
.dragging {
|
||||||
|
opacity: 0.4;
|
||||||
|
}
|
||||||
.modal-inner-container {
|
.modal-inner-container {
|
||||||
width: var(--modal-max-width);
|
width: var(--modal-max-width);
|
||||||
}
|
}
|
||||||
@ -122,17 +135,52 @@
|
|||||||
}
|
}
|
||||||
.row-wrapper {
|
.row-wrapper {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: auto auto auto 2em;
|
grid-template-columns: 25px 60px auto auto 2em;
|
||||||
gap: 1em;
|
gap: 1em;
|
||||||
margin-top: 1em;
|
padding: 0.5em 1px;
|
||||||
|
-webkit-user-drag: none;
|
||||||
|
-khtml-user-drag: none;
|
||||||
|
-moz-user-drag: none;
|
||||||
|
-o-user-drag: none;
|
||||||
|
user-drag: none;
|
||||||
|
cursor: default;
|
||||||
|
|
||||||
|
&.header {
|
||||||
|
padding-bottom: 0;
|
||||||
|
padding-top: 1em;
|
||||||
|
label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
.link-url {
|
||||||
|
margin-left: -1em;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.drag-above {
|
||||||
|
border-top: 1px dotted #666;
|
||||||
|
margin-top: -1px;
|
||||||
|
}
|
||||||
|
&.drag-below {
|
||||||
|
border-bottom: 1px dotted #666;
|
||||||
|
padding-bottom: calc(0.5em - 1px);
|
||||||
|
}
|
||||||
|
.link-icon {
|
||||||
|
grid-column: 1 / span 2;
|
||||||
|
padding-left: calc(25px + 1em);
|
||||||
|
}
|
||||||
|
&.mark-public-wrapper {
|
||||||
|
label {
|
||||||
|
grid-column: 1 / -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.delete-link {
|
.delete-link {
|
||||||
height: 1em;
|
height: 1em;
|
||||||
align-self: end;
|
align-self: center;
|
||||||
margin-bottom: 0.75em;
|
|
||||||
margin-right: 1em;
|
margin-right: 1em;
|
||||||
}
|
}
|
||||||
.btn-flat.add-link {
|
.btn-flat.add-link,
|
||||||
|
.btn-flat.reset-link {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
margin-left: -0.65em;
|
margin-left: -0.65em;
|
||||||
&:active,
|
&:active,
|
||||||
@ -148,6 +196,9 @@
|
|||||||
color: var(--tertiary-hover);
|
color: var(--tertiary-hover);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.btn-flat.reset-link {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
.modal-footer {
|
.modal-footer {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
@ -156,4 +207,13 @@
|
|||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.select-kit.multi-select .multi-select-header .formatted-selection {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.modal-inner-container .select-kit {
|
||||||
|
width: 60px;
|
||||||
|
}
|
||||||
|
.select-kit.is-expanded .select-kit-body {
|
||||||
|
width: 220px !important;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -59,7 +59,7 @@ class SidebarSectionsController < ApplicationController
|
|||||||
Site.clear_anon_cache!
|
Site.clear_anon_cache!
|
||||||
end
|
end
|
||||||
|
|
||||||
render_serialized(sidebar_section, SidebarSectionSerializer)
|
render_serialized(sidebar_section.reload, SidebarSectionSerializer)
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
render_json_error(e.record.errors.full_messages.first)
|
render_json_error(e.record.errors.full_messages.first)
|
||||||
rescue Discourse::InvalidAccess
|
rescue Discourse::InvalidAccess
|
||||||
|
@ -4413,10 +4413,13 @@ en:
|
|||||||
save: "Save"
|
save: "Save"
|
||||||
delete: "Delete"
|
delete: "Delete"
|
||||||
delete_confirm: "Are you sure you want to delete this section?"
|
delete_confirm: "Are you sure you want to delete this section?"
|
||||||
|
reset_confirm: "Are you sure you want to reset this section to default?"
|
||||||
public: "Make this section public and visible to everyone"
|
public: "Make this section public and visible to everyone"
|
||||||
|
more_menu: "More menu"
|
||||||
links:
|
links:
|
||||||
add: "Add another link"
|
add: "Add another link"
|
||||||
delete: "Delete link"
|
delete: "Delete link"
|
||||||
|
reset: "Reset to default"
|
||||||
icon:
|
icon:
|
||||||
label: "Icon"
|
label: "Icon"
|
||||||
validation:
|
validation:
|
||||||
@ -4473,7 +4476,8 @@ en:
|
|||||||
configure_defaults: "Configure defaults"
|
configure_defaults: "Configure defaults"
|
||||||
community:
|
community:
|
||||||
header_link_text: "Community"
|
header_link_text: "Community"
|
||||||
header_action_title: "Create a topic"
|
header_action_create_topic_title: "Create a topic"
|
||||||
|
header_action_edit_section_title: "Edit Community section"
|
||||||
links:
|
links:
|
||||||
about:
|
about:
|
||||||
content: "About"
|
content: "About"
|
||||||
|
@ -114,25 +114,17 @@ describe "Custom sidebar sections", type: :system, js: true do
|
|||||||
sign_in user
|
sign_in user
|
||||||
visit("/latest")
|
visit("/latest")
|
||||||
|
|
||||||
within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(1)") do
|
expect(sidebar.primary_section_links("my-section")).to eq(
|
||||||
expect(sidebar).to have_section_link("Sidebar Tags")
|
["Sidebar Tags", "Sidebar Categories"],
|
||||||
end
|
)
|
||||||
|
|
||||||
within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(2)") do
|
|
||||||
expect(sidebar).to have_section_link("Sidebar Categories")
|
|
||||||
end
|
|
||||||
|
|
||||||
tags_link = find(".sidebar-section-link[data-link-name='Sidebar Tags']")
|
tags_link = find(".sidebar-section-link[data-link-name='Sidebar Tags']")
|
||||||
categories_link = find(".sidebar-section-link[data-link-name='Sidebar Categories']")
|
categories_link = find(".sidebar-section-link[data-link-name='Sidebar Categories']")
|
||||||
tags_link.drag_to(categories_link, html5: true, delay: 0.4)
|
tags_link.drag_to(categories_link, html5: true, delay: 0.4)
|
||||||
|
|
||||||
within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(1)") do
|
expect(sidebar.primary_section_links("my-section")).to eq(
|
||||||
expect(sidebar).to have_section_link("Sidebar Categories")
|
["Sidebar Categories", "Sidebar Tags"],
|
||||||
end
|
)
|
||||||
|
|
||||||
within("[data-section-name='my-section'] .sidebar-section-link-wrapper:nth-child(2)") do
|
|
||||||
expect(sidebar).to have_section_link("Sidebar Tags")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "does not allow the user to edit public section" do
|
it "does not allow the user to edit public section" do
|
||||||
@ -201,6 +193,29 @@ describe "Custom sidebar sections", type: :system, js: true do
|
|||||||
expect(sidebar).to have_no_section("Edited public section")
|
expect(sidebar).to have_no_section("Edited public section")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "allows admin to edit community section and reset to default" do
|
||||||
|
sign_in admin
|
||||||
|
visit("/latest")
|
||||||
|
|
||||||
|
sidebar.edit_custom_section("Community")
|
||||||
|
section_modal.fill_name("Edited community section")
|
||||||
|
section_modal.everything_link.drag_to(section_modal.review_link, delay: 0.4)
|
||||||
|
section_modal.save
|
||||||
|
|
||||||
|
expect(sidebar).to have_section("Edited community section")
|
||||||
|
expect(sidebar.primary_section_links("edited-community-section")).to eq(
|
||||||
|
["My Posts", "Everything", "Admin", "More"],
|
||||||
|
)
|
||||||
|
|
||||||
|
sidebar.edit_custom_section("Edited community section")
|
||||||
|
section_modal.reset
|
||||||
|
|
||||||
|
expect(sidebar).to have_section("Community")
|
||||||
|
expect(sidebar.primary_section_links("community")).to eq(
|
||||||
|
["Everything", "My Posts", "Admin", "More"],
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
it "shows anonymous public sections" do
|
it "shows anonymous public sections" do
|
||||||
sidebar_section = Fabricate(:sidebar_section, title: "Public section", public: true)
|
sidebar_section = Fabricate(:sidebar_section, title: "Public section", public: true)
|
||||||
sidebar_url_1 = Fabricate(:sidebar_url, name: "Sidebar Tags", value: "/tags")
|
sidebar_url_1 = Fabricate(:sidebar_url, name: "Sidebar Tags", value: "/tags")
|
||||||
|
@ -63,6 +63,10 @@ module PageObjects
|
|||||||
find(SIDEBAR_WRAPPER_SELECTOR).has_no_button?(name)
|
find(SIDEBAR_WRAPPER_SELECTOR).has_no_button?(name)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def primary_section_links(slug)
|
||||||
|
all("[data-section-name='#{slug}'] .sidebar-section-link-wrapper").map(&:text)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def section_link_present?(name, href: nil, active: false, present:)
|
def section_link_present?(name, href: nil, active: false, present:)
|
||||||
|
@ -25,7 +25,7 @@ module PageObjects
|
|||||||
|
|
||||||
def click_community_header_button
|
def click_community_header_button
|
||||||
page.click_button(
|
page.click_button(
|
||||||
I18n.t("js.sidebar.sections.community.header_action_title"),
|
I18n.t("js.sidebar.sections.community.header_action_create_topic_title"),
|
||||||
class: "sidebar-section-header-button",
|
class: "sidebar-section-header-button",
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
@ -28,6 +28,11 @@ module PageObjects
|
|||||||
find(".dialog-container .btn-primary").click
|
find(".dialog-container .btn-primary").click
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def reset
|
||||||
|
find(".reset-link").click
|
||||||
|
find(".dialog-footer .btn-primary").click
|
||||||
|
end
|
||||||
|
|
||||||
def save
|
def save
|
||||||
find("#save-section").click
|
find("#save-section").click
|
||||||
end
|
end
|
||||||
@ -39,9 +44,18 @@ module PageObjects
|
|||||||
def has_disabled_save?
|
def has_disabled_save?
|
||||||
find_button("Save", disabled: true)
|
find_button("Save", disabled: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
def has_enabled_save?
|
def has_enabled_save?
|
||||||
find_button("Save", disabled: false)
|
find_button("Save", disabled: false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def everything_link
|
||||||
|
find(".draggable[data-link-name='Everything']")
|
||||||
|
end
|
||||||
|
|
||||||
|
def review_link
|
||||||
|
find(".draggable[data-link-name='Review']")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
Reference in New Issue
Block a user