mirror of
https://github.com/discourse/discourse.git
synced 2025-02-25 18:55:32 -06:00
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:
@@ -1,5 +1,9 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { LinkTo } from "@ember/routing";
|
||||
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 AdminPluginConfigMetadata from "./admin-plugin-config-metadata";
|
||||
import AdminPluginConfigTopNav from "./admin-plugin-config-top-nav";
|
||||
@@ -26,6 +30,30 @@ export default class AdminPluginConfigPage extends Component {
|
||||
<AdminPluginConfigTopNav />
|
||||
{{/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}} />
|
||||
|
||||
<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">
|
||||
{{#if this.model.length}}
|
||||
<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
|
||||
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-controls {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
&__main-area {
|
||||
.admin-detail {
|
||||
padding-top: 15px;
|
||||
@@ -107,10 +103,6 @@
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.admin-plugins-list-container {
|
||||
margin-top: 1em;
|
||||
}
|
||||
|
||||
.admin-plugin-filtered-site-settings {
|
||||
&__filter {
|
||||
width: 100%;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
@import "badges";
|
||||
@import "banner";
|
||||
@import "d-breadcrumbs";
|
||||
@import "bookmark-list";
|
||||
@import "bookmark-modal";
|
||||
@import "bookmark-menu";
|
||||
|
||||
19
app/assets/stylesheets/common/components/d-breadcrumbs.scss
Normal file
19
app/assets/stylesheets/common/components/d-breadcrumbs.scss
Normal file
@@ -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: ">";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user