FEATURE: Introduce DBreadcrumbs components (#27049)
This commit introduces the following components: * DBreadcrumbsContainer - The wrapper template-only component, which renders all DBreadcrumbsItem components on the page. * DBreadcrumbsItem - The component that registers a LinkTo for the breadcrumb trail. The breadcrumb > trail > will show based on the order these items are rendered on the page. * BreadcrumbsService - Manages the DBreadcrumbsContainer elements on the page via DBreadcrumbsContainerModifier. * DBreadcrumbsContainerModifier - Handles registering DBreadcrumbsContainer elements with the BreadcrumbsService and deregistering them. For now, we will only use these breadcrumbs in the admin section of Discourse, and this initial commit only uses them in admin/plugins. This is heavily based off of https://github.com/Bagaar/ember-breadcrumbs, but will be further modified for our needs.
This commit is contained in:
parent
f2cdc3b2a4
commit
1239178f49
|
@ -1,5 +1,9 @@
|
||||||
import Component from "@glimmer/component";
|
import Component from "@glimmer/component";
|
||||||
|
import { LinkTo } from "@ember/routing";
|
||||||
import { inject as service } from "@ember/service";
|
import { inject as service } from "@ember/service";
|
||||||
|
import DBreadcrumbsContainer from "discourse/components/d-breadcrumbs-container";
|
||||||
|
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||||
|
import i18n from "discourse-common/helpers/i18n";
|
||||||
import AdminPluginConfigArea from "./admin-plugin-config-area";
|
import AdminPluginConfigArea from "./admin-plugin-config-area";
|
||||||
import AdminPluginConfigMetadata from "./admin-plugin-config-metadata";
|
import AdminPluginConfigMetadata from "./admin-plugin-config-metadata";
|
||||||
import AdminPluginConfigTopNav from "./admin-plugin-config-top-nav";
|
import AdminPluginConfigTopNav from "./admin-plugin-config-top-nav";
|
||||||
|
@ -26,6 +30,30 @@ export default class AdminPluginConfigPage extends Component {
|
||||||
<AdminPluginConfigTopNav />
|
<AdminPluginConfigTopNav />
|
||||||
{{/if}}
|
{{/if}}
|
||||||
|
|
||||||
|
<DBreadcrumbsContainer />
|
||||||
|
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo @route="admin" class={{linkClass}}>
|
||||||
|
{{i18n "admin_title"}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo @route="adminPlugins" class={{linkClass}}>
|
||||||
|
{{i18n "admin.plugins.title"}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo
|
||||||
|
@route="adminPlugins.show"
|
||||||
|
@model={{@plugin}}
|
||||||
|
class={{linkClass}}
|
||||||
|
>
|
||||||
|
{{@plugin.nameTitleized}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
|
||||||
<AdminPluginConfigMetadata @plugin={{@plugin}} />
|
<AdminPluginConfigMetadata @plugin={{@plugin}} />
|
||||||
|
|
||||||
<div class="admin-plugin-config-page__content">
|
<div class="admin-plugin-config-page__content">
|
||||||
|
|
|
@ -1,3 +1,17 @@
|
||||||
|
<DBreadcrumbsContainer />
|
||||||
|
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo @route="admin" class={{linkClass}}>
|
||||||
|
{{i18n "admin_title"}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo @route="adminPlugins" class={{linkClass}}>
|
||||||
|
{{i18n "admin.plugins.title"}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
|
||||||
<div class="admin-plugins-list-container">
|
<div class="admin-plugins-list-container">
|
||||||
{{#if this.model.length}}
|
{{#if this.model.length}}
|
||||||
<h2>{{i18n "admin.plugins.installed"}}</h2>
|
<h2>{{i18n "admin.plugins.installed"}}</h2>
|
||||||
|
|
|
@ -1,3 +1,13 @@
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo
|
||||||
|
@route="adminPlugins.show.settings"
|
||||||
|
@model={{@model.plugin}}
|
||||||
|
class={{linkClass}}
|
||||||
|
>
|
||||||
|
{{i18n "admin.plugins.change_settings_short"}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
class="content-body admin-plugin-config-area__settings admin-detail pull-left"
|
class="content-body admin-plugin-config-area__settings admin-detail pull-left"
|
||||||
>
|
>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
import concatClass from "discourse/helpers/concat-class";
|
||||||
|
import dBreadcrumbsContainerModifier from "discourse/modifiers/d-breadcrumbs-container-modifier";
|
||||||
|
|
||||||
|
const DBreadcrumbsContainer = <template>
|
||||||
|
<ul
|
||||||
|
class="d-breadcrumbs"
|
||||||
|
{{dBreadcrumbsContainerModifier
|
||||||
|
itemClass=(concatClass "d-breadcrumbs__item" @additionalItemClasses)
|
||||||
|
linkClass=(concatClass "d-breadcrumbs__link" @additionalLinkClasses)
|
||||||
|
}}
|
||||||
|
...attributes
|
||||||
|
>
|
||||||
|
{{yield}}
|
||||||
|
</ul>
|
||||||
|
</template>;
|
||||||
|
|
||||||
|
export default DBreadcrumbsContainer;
|
|
@ -0,0 +1,16 @@
|
||||||
|
import Component from "@glimmer/component";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
|
||||||
|
export default class DBreadcrumbsItem extends Component {
|
||||||
|
@service breadcrumbsService;
|
||||||
|
|
||||||
|
<template>
|
||||||
|
{{#each this.breadcrumbsService.containers as |container|}}
|
||||||
|
{{#in-element container.element insertBefore=null}}
|
||||||
|
<li class={{container.itemClass}} ...attributes>
|
||||||
|
{{yield container.linkClass}}
|
||||||
|
</li>
|
||||||
|
{{/in-element}}
|
||||||
|
{{/each}}
|
||||||
|
</template>
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { registerDestructor } from "@ember/destroyable";
|
||||||
|
import { inject as service } from "@ember/service";
|
||||||
|
import Modifier from "ember-modifier";
|
||||||
|
|
||||||
|
export default class DBreadcrumbsContainerModifier extends Modifier {
|
||||||
|
@service breadcrumbsService;
|
||||||
|
|
||||||
|
container = null;
|
||||||
|
|
||||||
|
modify(element, _, { itemClass, linkClass }) {
|
||||||
|
if (this.container) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.container = { element, itemClass, linkClass };
|
||||||
|
|
||||||
|
this.breadcrumbsService.registerContainer(this.container);
|
||||||
|
|
||||||
|
registerDestructor(this, unregisterContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unregisterContainer(instance) {
|
||||||
|
if (instance.container) {
|
||||||
|
instance.breadcrumbsService.unregisterContainer(instance.container);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { tracked } from "@glimmer/tracking";
|
||||||
|
import { warn } from "@ember/debug";
|
||||||
|
import Service from "@ember/service";
|
||||||
|
|
||||||
|
export default class BreadcrumbsService extends Service {
|
||||||
|
@tracked containers = [];
|
||||||
|
#containers = [];
|
||||||
|
|
||||||
|
registerContainer(container) {
|
||||||
|
if (this.#isContainerRegistered(container)) {
|
||||||
|
warn(
|
||||||
|
"[BreadcrumbsService] A breadcrumb container with the same DOM element has already been registered before."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#containers = [...this.#containers, container];
|
||||||
|
|
||||||
|
this.containers = this.#containers;
|
||||||
|
}
|
||||||
|
|
||||||
|
unregisterContainer(container) {
|
||||||
|
if (!this.#isContainerRegistered(container)) {
|
||||||
|
warn(
|
||||||
|
"[BreadcrumbsService] No breadcrumb container was found with this DOM element."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.#containers = this.#containers.filter((registeredContainer) => {
|
||||||
|
return container.element !== registeredContainer.element;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.containers = this.#containers;
|
||||||
|
}
|
||||||
|
|
||||||
|
#isContainerRegistered(container) {
|
||||||
|
return this.#containers.some((registeredContainer) => {
|
||||||
|
return container.element === registeredContainer.element;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,64 @@
|
||||||
|
import { render } from "@ember/test-helpers";
|
||||||
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
|
import { module, test } from "qunit";
|
||||||
|
import { setupRenderingTest } from "discourse/tests/helpers/component-test";
|
||||||
|
|
||||||
|
module(
|
||||||
|
"Component | DBreadcrumbsContainer and DBreadcrumbsItem",
|
||||||
|
function (hooks) {
|
||||||
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
|
test("it renders a DBreadcrumbsContainer with multiple DBreadcrumbsItems", async function (assert) {
|
||||||
|
await render(hbs`
|
||||||
|
<DBreadcrumbsContainer />
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo @route="admin" class={{linkClass}}>
|
||||||
|
{{i18n "admin_title"}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo @route="about" class={{linkClass}}>
|
||||||
|
{{i18n "about.simple_title"}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(".d-breadcrumbs .d-breadcrumbs__item .d-breadcrumbs__link")
|
||||||
|
.exists({ count: 2 });
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it renders a DBreadcrumbsItem with additional link and item classes", async function (assert) {
|
||||||
|
await render(hbs`
|
||||||
|
<DBreadcrumbsContainer @additionalLinkClasses="some-class" @additionalItemClasses="other-class" />
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo @route="admin" class={{linkClass}}>
|
||||||
|
{{i18n "admin_title"}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom(".d-breadcrumbs .d-breadcrumbs__item.other-class").exists();
|
||||||
|
assert
|
||||||
|
.dom(
|
||||||
|
".d-breadcrumbs .d-breadcrumbs__item .d-breadcrumbs__link.some-class"
|
||||||
|
)
|
||||||
|
.exists();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("it renders multiple DBreadcrumbsContainer elements with the same DBreadcrumbsItem links", async function (assert) {
|
||||||
|
await render(hbs`
|
||||||
|
<DBreadcrumbsContainer />
|
||||||
|
<DBreadcrumbsContainer />
|
||||||
|
<DBreadcrumbsItem as |linkClass|>
|
||||||
|
<LinkTo @route="admin" class={{linkClass}}>
|
||||||
|
{{i18n "admin_title"}}
|
||||||
|
</LinkTo>
|
||||||
|
</DBreadcrumbsItem>
|
||||||
|
`);
|
||||||
|
|
||||||
|
assert.dom(".d-breadcrumbs").exists({ count: 2 });
|
||||||
|
assert.dom(".d-breadcrumbs .d-breadcrumbs__item").exists({ count: 2 });
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
|
@ -84,10 +84,6 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-plugin-config-page {
|
.admin-plugin-config-page {
|
||||||
.admin-controls {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
&__main-area {
|
&__main-area {
|
||||||
.admin-detail {
|
.admin-detail {
|
||||||
padding-top: 15px;
|
padding-top: 15px;
|
||||||
|
@ -107,10 +103,6 @@
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.admin-plugins-list-container {
|
|
||||||
margin-top: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-plugin-filtered-site-settings {
|
.admin-plugin-filtered-site-settings {
|
||||||
&__filter {
|
&__filter {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
@import "badges";
|
@import "badges";
|
||||||
@import "banner";
|
@import "banner";
|
||||||
|
@import "d-breadcrumbs";
|
||||||
@import "bookmark-list";
|
@import "bookmark-list";
|
||||||
@import "bookmark-modal";
|
@import "bookmark-modal";
|
||||||
@import "bookmark-menu";
|
@import "bookmark-menu";
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
.d-breadcrumbs {
|
||||||
|
display: flex;
|
||||||
|
margin: 1em 0 0.5em 0;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
list-style-type: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__link,
|
||||||
|
&__link:visited {
|
||||||
|
color: var(--primary-medium);
|
||||||
|
}
|
||||||
|
|
||||||
|
li:not(:last-child) a::after {
|
||||||
|
display: inline;
|
||||||
|
padding: 0 0.25em;
|
||||||
|
content: ">";
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue