From 1239178f496cba5d864adb7c118b17902b8b72dc Mon Sep 17 00:00:00 2001 From: Martin Brennan Date: Mon, 20 May 2024 14:25:54 +1000 Subject: [PATCH] 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. --- .../components/admin-plugin-config-page.gjs | 28 ++++++++ .../admin/addon/templates/plugins-index.hbs | 14 ++++ .../addon/templates/plugins-show-settings.hbs | 10 +++ .../components/d-breadcrumbs-container.gjs | 17 +++++ .../app/components/d-breadcrumbs-item.gjs | 16 +++++ .../d-breadcrumbs-container-modifier.js | 27 ++++++++ .../app/services/breadcrumbs-service.js | 40 ++++++++++++ .../components/d-breadcrumbs-test.js | 64 +++++++++++++++++++ .../stylesheets/common/admin/plugins.scss | 8 --- .../stylesheets/common/components/_index.scss | 1 + .../common/components/d-breadcrumbs.scss | 19 ++++++ 11 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 app/assets/javascripts/discourse/app/components/d-breadcrumbs-container.gjs create mode 100644 app/assets/javascripts/discourse/app/components/d-breadcrumbs-item.gjs create mode 100644 app/assets/javascripts/discourse/app/modifiers/d-breadcrumbs-container-modifier.js create mode 100644 app/assets/javascripts/discourse/app/services/breadcrumbs-service.js create mode 100644 app/assets/javascripts/discourse/tests/integration/components/d-breadcrumbs-test.js create mode 100644 app/assets/stylesheets/common/components/d-breadcrumbs.scss diff --git a/app/assets/javascripts/admin/addon/components/admin-plugin-config-page.gjs b/app/assets/javascripts/admin/addon/components/admin-plugin-config-page.gjs index 608d6e12b0c..5856ce2a97a 100644 --- a/app/assets/javascripts/admin/addon/components/admin-plugin-config-page.gjs +++ b/app/assets/javascripts/admin/addon/components/admin-plugin-config-page.gjs @@ -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 { {{/if}} + + + + + {{i18n "admin_title"}} + + + + + + {{i18n "admin.plugins.title"}} + + + + + + {{@plugin.nameTitleized}} + + +
diff --git a/app/assets/javascripts/admin/addon/templates/plugins-index.hbs b/app/assets/javascripts/admin/addon/templates/plugins-index.hbs index f8478b36b40..ab19b70abda 100644 --- a/app/assets/javascripts/admin/addon/templates/plugins-index.hbs +++ b/app/assets/javascripts/admin/addon/templates/plugins-index.hbs @@ -1,3 +1,17 @@ + + + + + {{i18n "admin_title"}} + + + + + + {{i18n "admin.plugins.title"}} + + +
{{#if this.model.length}}

{{i18n "admin.plugins.installed"}}

diff --git a/app/assets/javascripts/admin/addon/templates/plugins-show-settings.hbs b/app/assets/javascripts/admin/addon/templates/plugins-show-settings.hbs index f59b7530dbc..c26d05b09e3 100644 --- a/app/assets/javascripts/admin/addon/templates/plugins-show-settings.hbs +++ b/app/assets/javascripts/admin/addon/templates/plugins-show-settings.hbs @@ -1,3 +1,13 @@ + + + {{i18n "admin.plugins.change_settings_short"}} + + +
diff --git a/app/assets/javascripts/discourse/app/components/d-breadcrumbs-container.gjs b/app/assets/javascripts/discourse/app/components/d-breadcrumbs-container.gjs new file mode 100644 index 00000000000..f1420655b33 --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/d-breadcrumbs-container.gjs @@ -0,0 +1,17 @@ +import concatClass from "discourse/helpers/concat-class"; +import dBreadcrumbsContainerModifier from "discourse/modifiers/d-breadcrumbs-container-modifier"; + +const DBreadcrumbsContainer = ; + +export default DBreadcrumbsContainer; diff --git a/app/assets/javascripts/discourse/app/components/d-breadcrumbs-item.gjs b/app/assets/javascripts/discourse/app/components/d-breadcrumbs-item.gjs new file mode 100644 index 00000000000..5d2ecca03bf --- /dev/null +++ b/app/assets/javascripts/discourse/app/components/d-breadcrumbs-item.gjs @@ -0,0 +1,16 @@ +import Component from "@glimmer/component"; +import { inject as service } from "@ember/service"; + +export default class DBreadcrumbsItem extends Component { + @service breadcrumbsService; + + +} diff --git a/app/assets/javascripts/discourse/app/modifiers/d-breadcrumbs-container-modifier.js b/app/assets/javascripts/discourse/app/modifiers/d-breadcrumbs-container-modifier.js new file mode 100644 index 00000000000..58a96380791 --- /dev/null +++ b/app/assets/javascripts/discourse/app/modifiers/d-breadcrumbs-container-modifier.js @@ -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); + } +} diff --git a/app/assets/javascripts/discourse/app/services/breadcrumbs-service.js b/app/assets/javascripts/discourse/app/services/breadcrumbs-service.js new file mode 100644 index 00000000000..1a3f19466a9 --- /dev/null +++ b/app/assets/javascripts/discourse/app/services/breadcrumbs-service.js @@ -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; + }); + } +} diff --git a/app/assets/javascripts/discourse/tests/integration/components/d-breadcrumbs-test.js b/app/assets/javascripts/discourse/tests/integration/components/d-breadcrumbs-test.js new file mode 100644 index 00000000000..2d6b93f3b6a --- /dev/null +++ b/app/assets/javascripts/discourse/tests/integration/components/d-breadcrumbs-test.js @@ -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` + + + + {{i18n "admin_title"}} + + + + + {{i18n "about.simple_title"}} + + + `); + + 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` + + + + {{i18n "admin_title"}} + + + `); + + 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` + + + + + {{i18n "admin_title"}} + + + `); + + assert.dom(".d-breadcrumbs").exists({ count: 2 }); + assert.dom(".d-breadcrumbs .d-breadcrumbs__item").exists({ count: 2 }); + }); + } +); diff --git a/app/assets/stylesheets/common/admin/plugins.scss b/app/assets/stylesheets/common/admin/plugins.scss index 24a50d056ee..11fc4d3ab7f 100644 --- a/app/assets/stylesheets/common/admin/plugins.scss +++ b/app/assets/stylesheets/common/admin/plugins.scss @@ -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%; diff --git a/app/assets/stylesheets/common/components/_index.scss b/app/assets/stylesheets/common/components/_index.scss index d93c2f3c03c..37c865aa8c5 100644 --- a/app/assets/stylesheets/common/components/_index.scss +++ b/app/assets/stylesheets/common/components/_index.scss @@ -1,5 +1,6 @@ @import "badges"; @import "banner"; +@import "d-breadcrumbs"; @import "bookmark-list"; @import "bookmark-modal"; @import "bookmark-menu"; diff --git a/app/assets/stylesheets/common/components/d-breadcrumbs.scss b/app/assets/stylesheets/common/components/d-breadcrumbs.scss new file mode 100644 index 00000000000..e714c61071b --- /dev/null +++ b/app/assets/stylesheets/common/components/d-breadcrumbs.scss @@ -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: ">"; + } +}